iobroker.loxone 3.0.1 → 4.0.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.
Files changed (185) hide show
  1. package/LICENSE +183 -183
  2. package/README.md +417 -411
  3. package/admin/i18n/de.json +15 -0
  4. package/admin/i18n/en.json +15 -0
  5. package/admin/i18n/es.json +18 -0
  6. package/admin/i18n/fr.json +18 -0
  7. package/admin/i18n/it.json +18 -0
  8. package/admin/i18n/nl.json +18 -0
  9. package/admin/i18n/pl.json +18 -0
  10. package/admin/i18n/pt.json +18 -0
  11. package/admin/i18n/ru.json +18 -0
  12. package/admin/i18n/uk.json +18 -0
  13. package/admin/i18n/zh-cn.json +18 -0
  14. package/admin/jsonConfig.json +96 -0
  15. package/io-package.json +49 -38
  16. package/package.json +36 -49
  17. package/src/controls/AalEmergency.ts +90 -0
  18. package/src/controls/AalSmartAlarm.ts +99 -0
  19. package/src/controls/Alarm.ts +146 -0
  20. package/src/controls/AlarmClock.ts +137 -0
  21. package/src/controls/Application.ts +13 -0
  22. package/src/controls/AudioZone.ts +227 -0
  23. package/src/controls/AudioZoneV2.ts +135 -0
  24. package/src/controls/CentralAlarm.ts +59 -0
  25. package/src/controls/CentralAudioZone.ts +41 -0
  26. package/src/controls/CentralGate.ts +53 -0
  27. package/src/controls/CentralJalousie.ts +67 -0
  28. package/src/controls/CentralLightController.ts +45 -0
  29. package/src/controls/ColorPickerV2.ts +34 -0
  30. package/src/controls/Colorpicker.ts +30 -0
  31. package/src/controls/ColorpickerBase.ts +326 -0
  32. package/src/controls/Daytimer.ts +201 -0
  33. package/src/controls/Dimmer.ts +64 -0
  34. package/src/controls/EIBDimmer.ts +61 -0
  35. package/src/controls/Fronius.ts +217 -0
  36. package/src/controls/Gate.ts +150 -0
  37. package/src/controls/Hourcounter.ts +115 -0
  38. package/src/controls/IRCDaytimer.ts +4 -0
  39. package/src/controls/IRCV2Daytimer.ts +4 -0
  40. package/src/controls/IRoomControllerV2.ts +595 -0
  41. package/src/controls/InfoOnlyAnalog.ts +56 -0
  42. package/src/controls/InfoOnlyDigital.ts +95 -0
  43. package/src/controls/InfoOnlyText.ts +52 -0
  44. package/{build/controls/Intercom.js → src/controls/Intercom.ts} +27 -11
  45. package/src/controls/Jalousie.ts +219 -0
  46. package/src/controls/LightController.ts +112 -0
  47. package/src/controls/LightControllerV2.ts +246 -0
  48. package/src/controls/MailBox.ts +92 -0
  49. package/src/controls/Meter.ts +94 -0
  50. package/src/controls/None.ts +29 -0
  51. package/src/controls/PresenceDetector.ts +47 -0
  52. package/src/controls/Pushbutton.ts +55 -0
  53. package/src/controls/Radio.ts +61 -0
  54. package/{build/controls/Remote.js → src/controls/Remote.ts} +19 -11
  55. package/src/controls/Slider.ts +78 -0
  56. package/src/controls/SmokeAlarm.ts +163 -0
  57. package/src/controls/Switch.ts +46 -0
  58. package/src/controls/SystemScheme.ts +13 -0
  59. package/src/controls/TextInput.ts +96 -0
  60. package/src/controls/TextState.ts +37 -0
  61. package/src/controls/TimedSwitch.ts +75 -0
  62. package/src/controls/Tracker.ts +30 -0
  63. package/{build/controls/Unknown.js → src/controls/Unknown.ts} +18 -11
  64. package/src/controls/UpDownAnalog.ts +78 -0
  65. package/{build/controls/ValueSelector.js → src/controls/ValueSelector.ts} +21 -9
  66. package/src/controls/WindowMonitor.ts +139 -0
  67. package/src/controls/all-controls.ts +99 -0
  68. package/src/controls/control-base.ts +28 -0
  69. package/src/lib/adapter-config.d.ts +22 -0
  70. package/src/loxone-handler-base.ts +285 -0
  71. package/src/lxcommunicator.d.ts +1 -0
  72. package/src/main.test.ts +24 -0
  73. package/{build/main.js → src/main.ts} +545 -270
  74. package/src/structure-file.d.ts +154 -0
  75. package/src/weather-server-handler.ts +266 -0
  76. package/admin/admin.d.ts +0 -58
  77. package/admin/index_m.html +0 -126
  78. package/admin/style.css +0 -32
  79. package/admin/words.js +0 -25
  80. package/build/controls/AalEmergency.js +0 -45
  81. package/build/controls/AalEmergency.js.map +0 -1
  82. package/build/controls/AalSmartAlarm.js +0 -48
  83. package/build/controls/AalSmartAlarm.js.map +0 -1
  84. package/build/controls/Alarm.js +0 -81
  85. package/build/controls/Alarm.js.map +0 -1
  86. package/build/controls/AlarmClock.js +0 -59
  87. package/build/controls/AlarmClock.js.map +0 -1
  88. package/build/controls/Application.js +0 -11
  89. package/build/controls/Application.js.map +0 -1
  90. package/build/controls/AudioZone.js +0 -116
  91. package/build/controls/AudioZone.js.map +0 -1
  92. package/build/controls/CentralAlarm.js +0 -30
  93. package/build/controls/CentralAlarm.js.map +0 -1
  94. package/build/controls/CentralAudioZone.js +0 -22
  95. package/build/controls/CentralAudioZone.js.map +0 -1
  96. package/build/controls/CentralGate.js +0 -30
  97. package/build/controls/CentralGate.js.map +0 -1
  98. package/build/controls/CentralJalousie.js +0 -39
  99. package/build/controls/CentralJalousie.js.map +0 -1
  100. package/build/controls/CentralLightController.js +0 -27
  101. package/build/controls/CentralLightController.js.map +0 -1
  102. package/build/controls/ColorPickerV2.js +0 -24
  103. package/build/controls/ColorPickerV2.js.map +0 -1
  104. package/build/controls/Colorpicker.js +0 -20
  105. package/build/controls/Colorpicker.js.map +0 -1
  106. package/build/controls/ColorpickerBase.js +0 -263
  107. package/build/controls/ColorpickerBase.js.map +0 -1
  108. package/build/controls/Daytimer.js +0 -119
  109. package/build/controls/Daytimer.js.map +0 -1
  110. package/build/controls/Dimmer.js +0 -34
  111. package/build/controls/Dimmer.js.map +0 -1
  112. package/build/controls/EIBDimmer.js +0 -31
  113. package/build/controls/EIBDimmer.js.map +0 -1
  114. package/build/controls/Fronius.js +0 -64
  115. package/build/controls/Fronius.js.map +0 -1
  116. package/build/controls/Gate.js +0 -104
  117. package/build/controls/Gate.js.map +0 -1
  118. package/build/controls/Hourcounter.js +0 -46
  119. package/build/controls/Hourcounter.js.map +0 -1
  120. package/build/controls/IRCDaytimer.js +0 -9
  121. package/build/controls/IRCDaytimer.js.map +0 -1
  122. package/build/controls/IRCV2Daytimer.js +0 -9
  123. package/build/controls/IRCV2Daytimer.js.map +0 -1
  124. package/build/controls/IRoomControllerV2.js +0 -371
  125. package/build/controls/IRoomControllerV2.js.map +0 -1
  126. package/build/controls/InfoOnlyAnalog.js +0 -38
  127. package/build/controls/InfoOnlyAnalog.js.map +0 -1
  128. package/build/controls/InfoOnlyDigital.js +0 -65
  129. package/build/controls/InfoOnlyDigital.js.map +0 -1
  130. package/build/controls/InfoOnlyText.js +0 -35
  131. package/build/controls/InfoOnlyText.js.map +0 -1
  132. package/build/controls/Intercom.js.map +0 -1
  133. package/build/controls/Jalousie.js +0 -153
  134. package/build/controls/Jalousie.js.map +0 -1
  135. package/build/controls/LightController.js +0 -68
  136. package/build/controls/LightController.js.map +0 -1
  137. package/build/controls/LightControllerV2.js +0 -195
  138. package/build/controls/LightControllerV2.js.map +0 -1
  139. package/build/controls/MailBox.js +0 -41
  140. package/build/controls/MailBox.js.map +0 -1
  141. package/build/controls/Meter.js +0 -52
  142. package/build/controls/Meter.js.map +0 -1
  143. package/build/controls/None.js +0 -23
  144. package/build/controls/None.js.map +0 -1
  145. package/build/controls/PresenceDetector.js +0 -29
  146. package/build/controls/PresenceDetector.js.map +0 -1
  147. package/build/controls/Pushbutton.js +0 -35
  148. package/build/controls/Pushbutton.js.map +0 -1
  149. package/build/controls/Radio.js +0 -39
  150. package/build/controls/Radio.js.map +0 -1
  151. package/build/controls/Remote.js.map +0 -1
  152. package/build/controls/Slider.js +0 -44
  153. package/build/controls/Slider.js.map +0 -1
  154. package/build/controls/SmokeAlarm.js +0 -85
  155. package/build/controls/SmokeAlarm.js.map +0 -1
  156. package/build/controls/Switch.js +0 -31
  157. package/build/controls/Switch.js.map +0 -1
  158. package/build/controls/SystemScheme.js +0 -11
  159. package/build/controls/SystemScheme.js.map +0 -1
  160. package/build/controls/TextInput.js +0 -55
  161. package/build/controls/TextInput.js.map +0 -1
  162. package/build/controls/TextState.js +0 -20
  163. package/build/controls/TextState.js.map +0 -1
  164. package/build/controls/TimedSwitch.js +0 -36
  165. package/build/controls/TimedSwitch.js.map +0 -1
  166. package/build/controls/Tracker.js +0 -20
  167. package/build/controls/Tracker.js.map +0 -1
  168. package/build/controls/Unknown.js.map +0 -1
  169. package/build/controls/UpDownAnalog.js +0 -44
  170. package/build/controls/UpDownAnalog.js.map +0 -1
  171. package/build/controls/ValueSelector.js.map +0 -1
  172. package/build/controls/WindowMonitor.js +0 -84
  173. package/build/controls/WindowMonitor.js.map +0 -1
  174. package/build/controls/control-base.js +0 -12
  175. package/build/controls/control-base.js.map +0 -1
  176. package/build/loxone-handler-base.js +0 -186
  177. package/build/loxone-handler-base.js.map +0 -1
  178. package/build/main.js.map +0 -1
  179. package/build/weather-server-handler.js +0 -120
  180. package/build/weather-server-handler.js.map +0 -1
  181. package/doc/details-missing-control-type.png +0 -0
  182. package/doc/log-missing-control-type.png +0 -0
  183. package/doc/loxone-config-display-diagnostics.png +0 -0
  184. package/doc/loxone-config-info-only-digital.png +0 -0
  185. package/doc/loxone-config-use-in-visualization.png +0 -0
@@ -1,56 +1,150 @@
1
- "use strict";
2
- /*
3
- * Created with @iobroker/create-adapter v1.26.0
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Loxone = void 0;
7
- const utils = require("@iobroker/adapter-core");
8
- const axios_1 = require("axios");
9
- const LxCommunicator = require("lxcommunicator");
10
- const uuid_1 = require("uuid");
11
- const Unknown_1 = require("./controls/Unknown");
12
- const weather_server_handler_1 = require("./weather-server-handler");
13
- const FormData = require("form-data");
14
- const Queue = require("queue-fifo");
1
+ /* eslint-disable @typescript-eslint/no-redundant-type-constituents */
2
+ import * as utils from '@iobroker/adapter-core';
3
+ import type * as SentryNode from '@sentry/node';
4
+ import type { EventProcessor } from '@sentry/types';
5
+ import axios from 'axios';
6
+ import FormData from 'form-data';
7
+ import * as LxCommunicator from 'lxcommunicator';
8
+ import Queue from 'queue-fifo';
9
+ import { v4 } from 'uuid';
10
+ import { Unknown } from './controls/Unknown';
11
+ import type { ControlBase, ControlType } from './controls/control-base';
12
+ import type {
13
+ Control,
14
+ Controls,
15
+ GlobalStates,
16
+ OperatingModes,
17
+ StructureFile,
18
+ WeatherServer,
19
+ } from './structure-file.ts';
20
+ import { WeatherServerHandler } from './weather-server-handler';
21
+ import { AllControls } from './controls/all-controls';
22
+
15
23
  const WebSocketConfig = LxCommunicator.WebSocketConfig;
24
+
25
+ export type OldStateValue = ioBroker.StateValue | null | undefined;
26
+ export type CurrentStateValue = ioBroker.StateValue | null;
27
+
28
+ export type StateChangeListener = (oldValue: OldStateValue, newValue: CurrentStateValue) => void;
29
+ export type StateChangeListenerOpts = {
30
+ /** Don't call if new/old vals are the same & automatically ack state change */
31
+ notIfEqual?: boolean;
32
+ /** Convert state values to integers */
33
+ convertToInt?: boolean;
34
+ /** Values below minInt will be set to this value */
35
+ minInt?: number;
36
+ /** Values above maxInt will be set to this value */
37
+ maxInt?: number;
38
+ /** Override default timeout */
39
+ ackTimeoutMs?: number;
40
+ /** Acknowledge ourself (don't wait for Loxone) */
41
+ selfAck?: boolean;
42
+ };
43
+
44
+ export type StateChangeListenEntry = {
45
+ /** The listener function to call on state change */
46
+ listener: StateChangeListener;
47
+ /** Options for this listener */
48
+ opts?: StateChangeListenerOpts;
49
+ /** Queued value if a state change is in progress */
50
+ queuedVal: ioBroker.StateValue | null;
51
+ /** Timer for ack timeout */
52
+ ackTimer: ioBroker.Timeout | undefined;
53
+ };
54
+
16
55
  // Log warnings if no ack event from Loxone in this time
17
56
  // TODO: should this be configurable?
18
57
  const ackTimeoutMs = 500;
58
+
19
59
  // Period between connection attempts
20
60
  const reconnectTimeoutMs = 5000;
21
- class Loxone extends utils.Adapter {
22
- constructor(options = {}) {
61
+
62
+ export type StateEventHandler = (value: any) => Promise<void> | void;
63
+ export type StateEventRegistration = {
64
+ /** The name of the event handler used for removal */
65
+ name?: string;
66
+ /** The event handler function */
67
+ handler: StateEventHandler;
68
+ };
69
+ export type NamedStateEventHandler = (id: string, value: any) => Promise<void>;
70
+ export type LoxoneEvent = {
71
+ /** The UUID of the event */
72
+ uuid: string;
73
+ /** The event data */
74
+ evt: any;
75
+ };
76
+ export type Sentry = typeof SentryNode;
77
+
78
+ export type FormatInfoDetailsCallback = ((src: InfoDetailsEntryMap) => ioBroker.StateValue) | null;
79
+ export type InfoDetailsEntry = {
80
+ /** The event counter. */
81
+ count: number;
82
+ /** The last value received for this event. */
83
+ lastValue?: any;
84
+ };
85
+ export type InfoDetailsEntryMap = Map<string, InfoDetailsEntry>;
86
+ export type InfoEntry = {
87
+ /** The current state value */
88
+ value: ioBroker.StateValue;
89
+ /** The last set state value */
90
+ lastSet: ioBroker.StateValue;
91
+ /** Timer for state value reset */
92
+ timer: ioBroker.Timeout | undefined;
93
+ /** Map of detailed information entries */
94
+ detailsMap?: InfoDetailsEntryMap;
95
+ };
96
+
97
+ /**
98
+ * The Loxone adapter main class.
99
+ */
100
+ export class Loxone extends utils.Adapter {
101
+ private uuid = '';
102
+ private socket?: any;
103
+ private existingObjects: Record<string, ioBroker.Object> = {};
104
+ private currentStateValues: Record<string, CurrentStateValue> = {};
105
+ private operatingModes: OperatingModes = {};
106
+ private foundRooms: Record<string, string[]> = {};
107
+ private foundCats: Record<string, string[]> = {};
108
+
109
+ private stateChangeListeners: Record<string, StateChangeListenEntry> = {};
110
+ private stateEventHandlers: Record<string, StateEventRegistration[]> = {};
111
+
112
+ private readonly eventsQueue = new Queue<LoxoneEvent>();
113
+ private runQueue = false;
114
+ private queueRunning = false;
115
+
116
+ public readonly reportedMissingControls = new Set<string>();
117
+ private readonly reportedUnsupportedStateChanges = new Set<string>();
118
+ private reconnectTimer?: ioBroker.Timeout;
119
+ private connectionInProgress = false;
120
+ private lxConnected = false;
121
+
122
+ private info: Map<string, InfoEntry>;
123
+
124
+ /**
125
+ * Creates an instance of the Loxone adapter.
126
+ *
127
+ * @param options The adapter options.
128
+ */
129
+ public constructor(options: Partial<utils.AdapterOptions> = {}) {
23
130
  super({
24
- dirname: __dirname.indexOf('node_modules') !== -1 ? undefined : __dirname + '/../',
131
+ dirname: __dirname.indexOf('node_modules') !== -1 ? undefined : `${__dirname}/../`,
25
132
  ...options,
26
133
  name: 'loxone',
27
134
  });
28
- this.uuid = '';
29
- this.existingObjects = {};
30
- this.currentStateValues = {};
31
- this.operatingModes = {};
32
- this.foundRooms = {};
33
- this.foundCats = {};
34
- this.stateChangeListeners = {};
35
- this.stateEventHandlers = {};
36
- this.eventsQueue = new Queue();
37
- this.runQueue = false;
38
- this.queueRunning = false;
39
- this.reportedMissingControls = new Set();
40
- this.reportedUnsupportedStateChanges = new Set();
41
- this.connectionInProgress = false;
42
- this.lxConnected = false;
43
135
  this.on('ready', this.onReady.bind(this));
44
136
  this.on('stateChange', this.onStateChange.bind(this));
45
137
  this.on('unload', this.onUnload.bind(this));
46
- this.info = new Map();
138
+ this.info = new Map<string, InfoEntry>();
47
139
  }
140
+
48
141
  /**
49
142
  * Is called when databases are connected and adapter received configuration.
50
143
  */
51
- async onReady() {
144
+ private async onReady(): Promise<void> {
52
145
  // Init info
53
146
  await this.initInfoStates();
147
+
54
148
  // store all current (acknowledged) state values
55
149
  const allStates = await this.getStatesAsync('*');
56
150
  for (const id in allStates) {
@@ -58,47 +152,58 @@ class Loxone extends utils.Adapter {
58
152
  this.currentStateValues[id] = allStates[id].val;
59
153
  }
60
154
  }
155
+
61
156
  // store all existing objects for later use
62
157
  this.existingObjects = await this.getAdapterObjectsAsync();
158
+
63
159
  // Reset the connection indicator during startup
64
160
  this.setConnectionState(false);
65
- this.uuid = (0, uuid_1.v4)();
161
+ this.uuid = v4();
66
162
  // connect to Loxone Miniserver
67
- const webSocketConfig = new WebSocketConfig(WebSocketConfig.protocol.WS, this.uuid, 'iobroker', WebSocketConfig.permission.APP, false);
68
- const handleAnyEvent = (uuid, evt) => {
163
+ const webSocketConfig = new WebSocketConfig(
164
+ WebSocketConfig.protocol.WS,
165
+ this.uuid,
166
+ 'iobroker',
167
+ WebSocketConfig.permission.APP,
168
+ false,
169
+ );
170
+
171
+ const handleAnyEvent = (uuid: string, evt: any): void => {
69
172
  this.log.silly(`received update event: ${JSON.stringify(evt)}: ${uuid}`);
70
173
  this.eventsQueue.enqueue({ uuid, evt });
71
- this.handleEventQueue().catch((e) => {
72
- var _a;
174
+ this.handleEventQueue().catch(e => {
73
175
  this.log.error(`Unhandled error in event ${uuid}: ${e}`);
74
- (_a = this.getSentry()) === null || _a === void 0 ? void 0 : _a.captureException(e, { extra: { uuid, evt } });
176
+ this.getSentry()?.captureException(e, { extra: { uuid, evt } });
75
177
  });
76
178
  };
179
+
77
180
  webSocketConfig.delegate = {
78
- socketOnDataProgress: (socket, progress) => {
79
- this.log.debug('data progress ' + progress);
181
+ socketOnDataProgress: (socket: any, progress: any) => {
182
+ this.log.debug(`data progress ${progress}`);
80
183
  },
81
- socketOnTokenConfirmed: (_socket, _response) => {
184
+ socketOnTokenConfirmed: (_socket: any, _response: any) => {
82
185
  this.log.debug('token confirmed');
83
186
  },
84
- socketOnTokenReceived: (_socket, _result) => {
187
+ socketOnTokenReceived: (_socket: any, _result: any) => {
85
188
  this.log.debug('token received');
86
189
  },
87
- socketOnConnectionClosed: (socket, code) => {
88
- this.log.info('Socket closed ' + code);
190
+ socketOnConnectionClosed: (socket: any, code: string) => {
191
+ this.log.info(`Socket closed ${code}`);
89
192
  this.setConnectionState(false);
193
+
90
194
  // Stop queue and clear it. Issue a warning if it isn't empty.
91
195
  this.runQueue = false;
92
196
  if (this.eventsQueue.size() > 0) {
93
- this.log.warn('Event queue is not empty. Discarding ' + this.eventsQueue.size() + ' items');
197
+ this.log.warn(`Event queue is not empty. Discarding ${this.eventsQueue.size()} items`);
94
198
  }
95
199
  // Yes - I know this could go in the 'if' above but here 'just in case' ;)
96
200
  this.eventsQueue.clear();
201
+
97
202
  if (code != LxCommunicator.SupportCode.WEBSOCKET_MANUAL_CLOSE) {
98
203
  this.reconnect();
99
204
  }
100
205
  },
101
- socketOnEventReceived: (socket, events, type) => {
206
+ socketOnEventReceived: (socket: any, events: any, type: number) => {
102
207
  this.log.silly(`socket event received ${type} ${JSON.stringify(events)}`);
103
208
  this.incInfoState('info.messagesReceived');
104
209
  for (const evt of events) {
@@ -109,9 +214,6 @@ class Loxone extends utils.Adapter {
109
214
  case LxCommunicator.BinaryEvent.Type.EVENTTEXT:
110
215
  handleAnyEvent(evt.uuid, evt.text);
111
216
  break;
112
- case LxCommunicator.BinaryEvent.Type.EVENT:
113
- handleAnyEvent(evt.uuid, evt);
114
- break;
115
217
  case LxCommunicator.BinaryEvent.Type.WEATHER:
116
218
  handleAnyEvent(evt.uuid, evt);
117
219
  break;
@@ -122,16 +224,18 @@ class Loxone extends utils.Adapter {
122
224
  },
123
225
  };
124
226
  this.socket = new LxCommunicator.WebSocket(webSocketConfig);
227
+
125
228
  await this.connect();
229
+
126
230
  this.subscribeStates('*');
127
231
  }
128
- async loadStructureFile() {
129
- let file;
232
+
233
+ private async loadStructureFile(): Promise<boolean> {
234
+ let file: StructureFile;
130
235
  try {
131
236
  const fileString = await this.socket.send('data/LoxAPP3.json');
132
237
  file = JSON.parse(fileString);
133
- }
134
- catch (error) {
238
+ } catch {
135
239
  // do not stringify error, it can contain circular references
136
240
  this.log.error(`Couldn't get structure file`);
137
241
  return false;
@@ -141,205 +245,235 @@ class Loxone extends utils.Adapter {
141
245
  const sentry = this.getSentry();
142
246
  if (sentry) {
143
247
  // add a global event processor to upload the structure file (only once)
144
- sentry.addGlobalEventProcessor(this.createSentryEventProcessor(file));
248
+ sentry.addEventProcessor(this.createSentryEventProcessor(file));
145
249
  }
250
+
146
251
  try {
147
252
  await this.loadStructureFileAsync(file);
148
253
  this.log.debug('structure file successfully loaded');
149
- }
150
- catch (error) {
254
+ } catch (error) {
151
255
  // do not stringify error, it can contain circular references
152
256
  this.log.error(`Couldn't load structure file`);
153
- sentry === null || sentry === void 0 ? void 0 : sentry.captureException(error, { extra: { file } });
257
+ sentry?.captureException(error, { extra: { file } });
154
258
  return false;
155
259
  }
260
+
156
261
  return true; // Success
157
262
  }
158
- async connect() {
263
+
264
+ private async connect(): Promise<void> {
159
265
  if (this.connectionInProgress) {
160
266
  this.log.warn('Connection already in progress');
161
- }
162
- else {
267
+ } else {
163
268
  this.log.info('Trying to connect');
164
269
  this.connectionInProgress = true;
270
+
165
271
  let success = true; // Assume success
272
+
166
273
  try {
167
- await this.socket.open(this.config.host + ':' + this.config.port, this.config.username, this.config.password);
168
- }
169
- catch (error) {
274
+ await this.socket.open(
275
+ `${this.config.host}:${this.config.port}`,
276
+ this.config.username,
277
+ this.config.password,
278
+ );
279
+ } catch {
170
280
  // do not stringify error, it can contain circular references
171
281
  this.log.error(`Couldn't open socket`);
172
282
  success = false;
173
283
  }
284
+
174
285
  if (success) {
175
286
  success = await this.loadStructureFile();
176
287
  }
288
+
177
289
  if (success) {
178
290
  try {
179
291
  await this.socket.send('jdev/sps/enablebinstatusupdate');
180
- }
181
- catch (error) {
292
+ } catch {
182
293
  // do not stringify error, it can contain circular references
183
294
  this.log.error(`Couldn't enable status updates`);
184
295
  success = false;
185
296
  }
186
297
  }
298
+
187
299
  this.connectionInProgress = false;
300
+
188
301
  if (!success) {
189
302
  this.log.debug('Connection failed - will retry after delay');
190
303
  this.socket.close();
191
304
  this.reconnect();
192
- }
193
- else {
305
+ } else {
194
306
  // We are ready, let's set the connection indicator
195
307
  this.setConnectionState(true);
196
308
  }
197
309
  }
198
310
  }
199
- reconnect() {
311
+
312
+ private reconnect(): void {
200
313
  if (this.reconnectTimer) {
201
314
  this.log.debug('Reconnect called while timer already running');
202
- }
203
- else if (this.connectionInProgress) {
315
+ } else if (this.connectionInProgress) {
204
316
  this.log.debug('Reconnect called while connection in progress');
205
- }
206
- else {
317
+ } else {
207
318
  this.reconnectTimer = this.setTimeout(() => {
208
319
  delete this.reconnectTimer;
209
- this.connect().catch((e) => {
320
+ this.connect().catch(e => {
210
321
  this.log.error(`Couldn't reconnect: ${e}`);
211
322
  this.reconnect();
212
323
  });
213
324
  }, reconnectTimeoutMs);
214
325
  }
215
326
  }
216
- setConnectionState(connected) {
327
+
328
+ private setConnectionState(connected: boolean): void {
217
329
  this.lxConnected = connected;
218
- this.setState('info.connection', this.lxConnected, true);
330
+ this.setState('info.connection', this.lxConnected, true).catch(e => this.log.warn(e));
219
331
  }
332
+
220
333
  /**
221
334
  * Is called when adapter shuts down - callback has to be called under any circumstances!
335
+ *
336
+ * @param callback the callback to call when cleanup is done
222
337
  */
223
- onUnload(callback) {
338
+ private onUnload(callback: () => void): void {
224
339
  try {
225
340
  if (this.socket) {
226
341
  this.socket.close();
227
342
  delete this.socket;
228
343
  }
229
344
  callback();
230
- }
231
- catch (e) {
345
+ } catch {
232
346
  callback();
233
347
  }
234
348
  this.flushInfoStates();
235
349
  // TODO: clear queued state change timers
236
350
  }
351
+
237
352
  /**
238
- * Is called if a subscribed state changes
353
+ * Is called if a subscribed state changes.
354
+ *
355
+ * @param id the id of the state
356
+ * @param state the state object
239
357
  */
240
- async onStateChange(id, state) {
358
+ private async onStateChange(id: string, state: ioBroker.State | null | undefined): Promise<void> {
241
359
  // Warning: state can be null if it was deleted!
242
360
  if (!id || !state || state.ack) {
243
361
  // Do nothing
244
- }
245
- else if (id.includes('.info.')) {
362
+ } else if (id.includes('.info.')) {
246
363
  // Ignore info changes
247
364
  // TODO: can this be done better by ignoring '.info.' in subscribeStates?
248
- }
249
- else {
365
+ } else {
250
366
  this.log.debug(`stateChange ${id} ${JSON.stringify(state)}`);
251
- if (!this.stateChangeListeners.hasOwnProperty(id)) {
252
- const msg = 'Unsupported state change: ' + id;
367
+ if (!(id in this.stateChangeListeners)) {
368
+ const msg = `Unsupported state change: ${id}`;
253
369
  this.log.error(msg);
254
370
  if (!this.reportedUnsupportedStateChanges.has(id)) {
255
371
  this.reportedUnsupportedStateChanges.add(id);
256
372
  const sentry = this.getSentry();
257
- sentry === null || sentry === void 0 ? void 0 : sentry.withScope((scope) => {
373
+ sentry?.withScope(scope => {
258
374
  scope.setExtra('state', state);
259
375
  sentry.captureMessage(msg, 'warning');
260
376
  });
261
377
  }
262
- }
263
- else if (!this.lxConnected) {
378
+ } else if (!this.lxConnected) {
264
379
  this.log.warn(`stateChange ${id} while disconnected, discarding`);
265
380
  this.incInfoState('info.stateChangesDiscarded');
266
- }
267
- else {
381
+ } else {
268
382
  const stateChangeListener = this.stateChangeListeners[id];
269
383
  if (stateChangeListener.ackTimer) {
270
384
  // Ack timer is running: we didn't get a reply from the previous command yet
271
385
  if (stateChangeListener.queuedVal !== null) {
272
386
  // Already a queued state change: we're going to have to discard that and replace with latest
273
- this.log.warn(`State change in progress for ${id}, discarding ${stateChangeListener.queuedVal}`);
387
+ this.log.warn(
388
+ `State change in progress for ${id}, discarding ${stateChangeListener.queuedVal}`,
389
+ );
274
390
  this.incInfoState('info.stateChangesDiscarded');
275
- }
276
- else {
391
+ } else {
277
392
  // Nothing queued, so this will only be delayed (at least for now)
278
393
  this.log.warn(`State change in progress for ${id}, delaying ${state.val}`);
279
394
  this.incInfoState('info.stateChangesDelayed');
280
395
  }
281
396
  stateChangeListener.queuedVal = state.val;
282
- }
283
- else {
397
+ } else {
284
398
  // Ack timer is not running, so we're all good to handle this
285
399
  await this.handleStateChange(id, stateChangeListener, state.val);
286
400
  }
287
401
  }
288
402
  }
289
403
  }
290
- convertStateToInt(value) {
404
+
405
+ /**
406
+ * Convert a state value to an integer.
407
+ *
408
+ * @param value The state value to convert.
409
+ * @returns The integer representation of the state value.
410
+ */
411
+ public convertStateToInt(value: OldStateValue): number {
291
412
  return !value ? 0 : parseInt(value.toString());
292
413
  }
293
- async handleStateChange(id, stateChangeListener, val) {
294
- var _a, _b, _c, _d, _e, _f, _g, _h;
295
- if ((_a = stateChangeListener.opts) === null || _a === void 0 ? void 0 : _a.convertToInt) {
414
+
415
+ private async handleStateChange(
416
+ id: string,
417
+ stateChangeListener: StateChangeListenEntry,
418
+ val: ioBroker.StateValue,
419
+ ): Promise<void> {
420
+ if (stateChangeListener.opts?.convertToInt) {
296
421
  // Convert any values to ints within range if necessary.
297
422
  val = this.convertStateToInt(val);
298
- if (((_b = stateChangeListener.opts) === null || _b === void 0 ? void 0 : _b.minInt) !== undefined && val < stateChangeListener.opts.minInt) {
423
+ if (stateChangeListener.opts?.minInt !== undefined && val < stateChangeListener.opts.minInt) {
299
424
  val = stateChangeListener.opts.minInt;
300
425
  }
301
- if (((_c = stateChangeListener.opts) === null || _c === void 0 ? void 0 : _c.maxInt) !== undefined && val > stateChangeListener.opts.maxInt) {
426
+ if (stateChangeListener.opts?.maxInt !== undefined && val > stateChangeListener.opts.maxInt) {
302
427
  val = stateChangeListener.opts.maxInt;
303
428
  }
304
429
  }
305
- if (((_d = stateChangeListener.opts) === null || _d === void 0 ? void 0 : _d.notIfEqual) && this.currentStateValues[id] === val) {
430
+ if (stateChangeListener.opts?.notIfEqual && this.currentStateValues[id] === val) {
306
431
  // new/old values are the same so don't send update.
307
432
  // However, ack the state change as we have 'handled' this (by doing nothing)
308
433
  this.log.debug(`State value is unchanged, no listener+self-ack: ${id} ${val}`);
309
434
  await this.setStateAck(id, val);
310
- }
311
- else {
312
- if (!((_e = stateChangeListener.opts) === null || _e === void 0 ? void 0 : _e.selfAck)) {
435
+ } else {
436
+ if (!stateChangeListener.opts?.selfAck) {
313
437
  // Set ack timer before calling listener
314
- stateChangeListener.ackTimer = this.setTimeout(async (id, stateChangeListener) => {
315
- this.log.warn(`Timeout for ack ${id}`);
316
- this.incInfoState('info.ackTimeouts', id);
317
- stateChangeListener.ackTimer = null;
318
- // Even though this is a timeout, handle any change that may have been delayed waiting for this
319
- await this.handleDelayedStateChange(id, stateChangeListener);
320
- }, ((_f = stateChangeListener.opts) === null || _f === void 0 ? void 0 : _f.ackTimeoutMs) ? (_g = stateChangeListener.opts) === null || _g === void 0 ? void 0 : _g.ackTimeoutMs : ackTimeoutMs, id, stateChangeListener);
438
+ stateChangeListener.ackTimer = this.setTimeout(
439
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
440
+ // @ts-ignore
441
+ async (id: string, stateChangeListener: StateChangeListenEntry) => {
442
+ this.log.warn(`Timeout for ack ${id}`);
443
+ this.incInfoState('info.ackTimeouts', id);
444
+ stateChangeListener.ackTimer = undefined;
445
+ // Even though this is a timeout, handle any change that may have been delayed waiting for this
446
+ await this.handleDelayedStateChange(id, stateChangeListener);
447
+ },
448
+ stateChangeListener.opts?.ackTimeoutMs ? stateChangeListener.opts?.ackTimeoutMs : ackTimeoutMs,
449
+ id,
450
+ stateChangeListener,
451
+ );
321
452
  }
453
+
322
454
  // Change will be handled by listener
323
455
  stateChangeListener.listener(this.currentStateValues[id], val);
324
- if ((_h = stateChangeListener.opts) === null || _h === void 0 ? void 0 : _h.selfAck) {
456
+
457
+ if (stateChangeListener.opts?.selfAck) {
325
458
  // Loxone is not expected to send an event to acknowledge this so just do it ourself
326
459
  this.log.debug(`Self-ack: ${id} ${val}`);
327
460
  await this.setStateAck(id, val);
328
461
  }
329
462
  }
330
463
  }
331
- async handleDelayedStateChange(id, stateChangeListener) {
464
+
465
+ private async handleDelayedStateChange(id: string, stateChangeListener: StateChangeListenEntry): Promise<void> {
332
466
  if (stateChangeListener.queuedVal !== null) {
333
467
  this.log.debug(`Handling delayed state: ${id} ${stateChangeListener.queuedVal}`);
334
468
  await this.handleStateChange(id, stateChangeListener, stateChangeListener.queuedVal);
335
469
  stateChangeListener.queuedVal = null;
336
470
  }
337
471
  }
338
- createSentryEventProcessor(data) {
339
- const sentry = this.getSentry();
340
- let attachmentEventId;
341
- return async (event) => {
342
- var _a;
472
+
473
+ private createSentryEventProcessor(data: StructureFile): EventProcessor {
474
+ const sentry = this.getSentry()!;
475
+ let attachmentEventId: string | undefined;
476
+ return async (event: SentryNode.Event) => {
343
477
  try {
344
478
  if (attachmentEventId) {
345
479
  // structure file was already added
@@ -353,28 +487,34 @@ class Loxone extends utils.Adapter {
353
487
  }
354
488
  return event;
355
489
  }
356
- const dsn = (_a = sentry.getCurrentHub().getClient()) === null || _a === void 0 ? void 0 : _a.getDsn();
490
+ const dsn = sentry.getCurrentHub().getClient()?.getDsn();
357
491
  if (!dsn || !event.event_id) {
358
492
  return event;
359
493
  }
494
+
360
495
  attachmentEventId = event.event_id;
496
+
361
497
  const { host, path, projectId, port, protocol, publicKey } = dsn;
362
- const endpoint = `${protocol}://${host}${port !== '' ? `:${port}` : ''}${path !== '' ? `/${path}` : ''}/api/${projectId}/events/${attachmentEventId}/attachments/?sentry_key=${publicKey}&sentry_version=7&sentry_client=custom-javascript`;
498
+ const endpoint = `${protocol}://${host}${port !== '' ? `:${port}` : ''}${
499
+ path !== '' ? `/${path}` : ''
500
+ }/api/${projectId}/events/${attachmentEventId}/attachments/?sentry_key=${publicKey}&sentry_version=7&sentry_client=custom-javascript`;
501
+
363
502
  const form = new FormData();
364
503
  form.append('att', JSON.stringify(data, null, 2), {
365
504
  contentType: 'application/json',
366
505
  filename: 'LoxAPP3.json',
367
506
  });
368
- await axios_1.default.post(endpoint, form, { headers: form.getHeaders() });
507
+ await axios.post(endpoint, form, { headers: form.getHeaders() });
369
508
  return event;
370
- }
371
- catch (ex) {
509
+ } catch (ex: any) {
372
510
  this.log.error(`Couldn't upload structure file attachment to sentry: ${ex}`);
373
511
  }
512
+
374
513
  return event;
375
514
  };
376
515
  }
377
- async loadStructureFileAsync(data) {
516
+
517
+ private async loadStructureFileAsync(data: StructureFile): Promise<void> {
378
518
  this.stateEventHandlers = {};
379
519
  this.foundRooms = {};
380
520
  this.foundCats = {};
@@ -384,12 +524,19 @@ class Loxone extends utils.Adapter {
384
524
  await this.loadEnumsAsync(data.rooms, 'enum.rooms', this.foundRooms, this.config.syncRooms);
385
525
  await this.loadEnumsAsync(data.cats, 'enum.functions', this.foundCats, this.config.syncFunctions);
386
526
  await this.loadWeatherServerAsync(data.weatherServer);
527
+
387
528
  // replay all queued events
388
529
  this.runQueue = true;
389
530
  await this.handleEventQueue();
390
531
  }
391
- async loadGlobalStatesAsync(globalStates) {
392
- const globalStateInfos = {
532
+
533
+ private async loadGlobalStatesAsync(globalStates: GlobalStates): Promise<void> {
534
+ interface GlobalStateInfo {
535
+ type: ioBroker.CommonType;
536
+ role: string;
537
+ handler: (name: string, value: ioBroker.StateValue) => Promise<void>;
538
+ }
539
+ const globalStateInfos: Record<string, GlobalStateInfo> = {
393
540
  operatingMode: {
394
541
  type: 'number',
395
542
  role: 'value',
@@ -421,11 +568,12 @@ class Loxone extends utils.Adapter {
421
568
  handler: (name, value) => this.setStateAck(name, value === 1),
422
569
  },
423
570
  };
424
- const defaultInfo = {
571
+ const defaultInfo: GlobalStateInfo = {
425
572
  type: 'string',
426
573
  role: 'text',
427
574
  handler: (name, value) => this.setStateAck(name, `${value}`),
428
575
  };
576
+
429
577
  // special case for operating mode (text)
430
578
  await this.updateObjectAsync('operatingMode-text', {
431
579
  type: 'state',
@@ -440,37 +588,43 @@ class Loxone extends utils.Adapter {
440
588
  uuid: globalStates.operatingMode,
441
589
  },
442
590
  });
591
+
443
592
  for (const globalStateName in globalStates) {
444
- const info = globalStateInfos.hasOwnProperty(globalStateName)
445
- ? globalStateInfos[globalStateName]
446
- : defaultInfo;
447
- await this.updateStateObjectAsync(globalStateName, {
448
- name: globalStateName,
449
- read: true,
450
- write: false,
451
- type: info.type,
452
- role: info.role,
453
- }, globalStates[globalStateName], info.handler);
593
+ const info = globalStateInfos[globalStateName] ?? defaultInfo;
594
+ await this.updateStateObjectAsync(
595
+ globalStateName,
596
+ {
597
+ name: globalStateName,
598
+ read: true,
599
+ write: false,
600
+ type: info.type,
601
+ role: info.role,
602
+ },
603
+ globalStates[globalStateName],
604
+ info.handler,
605
+ );
454
606
  }
455
607
  }
456
- async setOperatingMode(name, value) {
608
+
609
+ private async setOperatingMode(name: string, value: any): Promise<void> {
457
610
  await this.setStateAck(name, value);
458
- await this.setStateAck(name + '-text', this.operatingModes[value]);
611
+ await this.setStateAck(`${name}-text`, this.operatingModes[value]);
459
612
  }
460
- async loadControlsAsync(controls) {
461
- var _a;
613
+
614
+ private async loadControlsAsync(controls: Controls): Promise<void> {
462
615
  let hasUnsupported = false;
463
616
  for (const uuid in controls) {
464
617
  const control = controls[uuid];
465
- if (!control.hasOwnProperty('type')) {
618
+ if (!('type' in control)) {
466
619
  continue;
467
620
  }
621
+
468
622
  try {
469
623
  await this.loadControlAsync('device', uuid, control);
470
- }
471
- catch (e) {
624
+ } catch (e: any) {
472
625
  this.log.info(`Currently unsupported control type ${control.type}: ${e}`);
473
- (_a = this.getSentry()) === null || _a === void 0 ? void 0 : _a.captureException(e, { extra: { uuid, control } });
626
+ this.getSentry()?.captureException(e, { extra: { uuid, control } });
627
+
474
628
  if (!hasUnsupported) {
475
629
  hasUnsupported = true;
476
630
  await this.updateObjectAsync('Unsupported', {
@@ -482,7 +636,8 @@ class Loxone extends utils.Adapter {
482
636
  native: {},
483
637
  });
484
638
  }
485
- await this.updateObjectAsync('Unsupported.' + uuid, {
639
+
640
+ await this.updateObjectAsync(`Unsupported.${uuid}`, {
486
641
  type: 'state',
487
642
  common: {
488
643
  name: control.name,
@@ -496,73 +651,93 @@ class Loxone extends utils.Adapter {
496
651
  }
497
652
  }
498
653
  }
499
- async loadSubControlsAsync(parentUuid, control) {
500
- var _a;
501
- if (!control.hasOwnProperty('subControls')) {
654
+
655
+ /**
656
+ * Load sub-controls of a control.
657
+ *
658
+ * @param parentUuid The UUID of the parent control.
659
+ * @param control The control containing sub-controls.
660
+ * @returns A promise that resolves when sub-controls are loaded.
661
+ */
662
+ public async loadSubControlsAsync(parentUuid: string, control: Control): Promise<void> {
663
+ if (!('subControls' in control)) {
502
664
  return;
503
665
  }
504
666
  for (let uuid in control.subControls) {
505
667
  const subControl = control.subControls[uuid];
506
- if (!subControl.hasOwnProperty('type')) {
668
+ if (!('type' in subControl)) {
507
669
  continue;
508
670
  }
671
+
509
672
  try {
510
- if (uuid.startsWith(parentUuid + '/')) {
673
+ if (uuid.startsWith(`${parentUuid}/`)) {
511
674
  uuid = uuid.replace('/', '.');
675
+ } else {
676
+ uuid = `${parentUuid}.${uuid.replace('/', '-')}`;
512
677
  }
513
- else {
514
- uuid = parentUuid + '.' + uuid.replace('/', '-');
515
- }
516
- subControl.name = control.name + ': ' + subControl.name;
678
+ subControl.name = `${control.name}: ${subControl.name}`;
679
+
517
680
  await this.loadControlAsync('channel', uuid, subControl);
518
- }
519
- catch (e) {
681
+ } catch (e: any) {
520
682
  this.log.info(`Currently unsupported sub-control type ${subControl.type}: ${e}`);
521
- (_a = this.getSentry()) === null || _a === void 0 ? void 0 : _a.captureException(e, { extra: { uuid, subControl } });
683
+ this.getSentry()?.captureException(e, { extra: { uuid, subControl } });
522
684
  }
523
685
  }
524
686
  }
525
- async loadControlAsync(controlType, uuid, control) {
526
- var _a;
687
+
688
+ private async loadControlAsync(controlType: ControlType, uuid: string, control: Control): Promise<void> {
527
689
  const type = control.type || 'None';
528
690
  if (type.match(/[^a-z0-9]/i)) {
529
691
  throw new Error(`Bad control type: ${type}`);
530
692
  }
531
- let controlObject;
693
+
694
+ let controlObject: ControlBase;
532
695
  try {
533
- const module = await (_a = `./controls/${type}`, Promise.resolve().then(() => require(_a)));
534
- controlObject = new module[type](this);
535
- }
536
- catch (error) {
537
- controlObject = new Unknown_1.Unknown(this);
696
+ controlObject = new AllControls[type as keyof typeof AllControls](this);
697
+ } catch (e: any) {
698
+ this.log.silly(`Couldn't load control type ${type}: ${e}`);
699
+ controlObject = new Unknown(this);
538
700
  }
539
701
  await controlObject.loadAsync(controlType, uuid, control);
540
- if (control.hasOwnProperty('room')) {
541
- if (!this.foundRooms.hasOwnProperty(control.room)) {
702
+
703
+ if ('room' in control) {
704
+ if (!this.foundRooms[control.room]) {
542
705
  this.foundRooms[control.room] = [];
543
706
  }
707
+
544
708
  this.foundRooms[control.room].push(uuid);
545
709
  }
546
- if (control.hasOwnProperty('cat')) {
547
- if (!this.foundCats.hasOwnProperty(control.cat)) {
710
+
711
+ if ('cat' in control) {
712
+ if (!this.foundCats[control.cat]) {
548
713
  this.foundCats[control.cat] = [];
549
714
  }
715
+
550
716
  this.foundCats[control.cat].push(uuid);
551
717
  }
552
718
  }
553
- async loadEnumsAsync(values, enumName, found, enabled) {
719
+
720
+ private async loadEnumsAsync(
721
+ values: Record<string, any>,
722
+ enumName: string,
723
+ found: Record<string, string[]>,
724
+ enabled: boolean,
725
+ ): Promise<void> {
554
726
  if (!enabled) {
555
727
  return;
556
728
  }
729
+
557
730
  for (const uuid in values) {
558
- if (!found.hasOwnProperty(uuid)) {
731
+ if (!found[uuid]) {
559
732
  // don't sync room/cat if we have no control that is using it
560
733
  continue;
561
734
  }
735
+
562
736
  const members = [];
563
- for (const i in found[uuid]) {
564
- members.push(this.namespace + '.' + found[uuid][i]);
737
+ for (const id of found[uuid]) {
738
+ members.push(`${this.namespace}.${id}`);
565
739
  }
740
+
566
741
  const item = values[uuid];
567
742
  const name = item.name.replace(/[\][*.,;'"`<>\\?]+/g, '_');
568
743
  const obj = {
@@ -573,19 +748,20 @@ class Loxone extends utils.Adapter {
573
748
  },
574
749
  native: item,
575
750
  };
576
- await this.updateEnumObjectAsync(enumName + '.' + name, obj);
751
+
752
+ await this.updateEnumObjectAsync(`${enumName}.${name}`, obj);
577
753
  }
578
754
  }
579
- async updateEnumObjectAsync(id, newObj) {
755
+
756
+ private async updateEnumObjectAsync(id: string, newObj: any): Promise<void> {
580
757
  // TODO: the parameter newObj should be an EnumObject, but currently that doesn't exist in the type definition
581
758
  // similar to hm-rega.js:
582
- let obj = await this.getForeignObjectAsync(id);
759
+ let obj: any = await this.getForeignObjectAsync(id);
583
760
  let changed = false;
584
761
  if (!obj) {
585
762
  obj = newObj;
586
763
  changed = true;
587
- }
588
- else if (newObj && newObj.common && newObj.common.members) {
764
+ } else if (newObj && newObj.common && newObj.common.members) {
589
765
  obj.common = obj.common || {};
590
766
  obj.common.members = obj.common.members || [];
591
767
  for (let m = 0; m < newObj.common.members.length; m++) {
@@ -599,27 +775,27 @@ class Loxone extends utils.Adapter {
599
775
  await this.setForeignObjectAsync(id, obj);
600
776
  }
601
777
  }
602
- async loadWeatherServerAsync(data) {
778
+
779
+ private async loadWeatherServerAsync(data: WeatherServer): Promise<void> {
603
780
  if (this.config.weatherServer === 'off') {
604
781
  this.log.debug('WeatherServer is disabled in the adapter configuration');
605
782
  return;
606
783
  }
607
- const handler = new weather_server_handler_1.WeatherServerHandler(this);
784
+ const handler = new WeatherServerHandler(this);
608
785
  await handler.loadAsync(data, this.config.weatherServer || 'all');
609
786
  }
610
- async handleEventQueue() {
787
+
788
+ private async handleEventQueue(): Promise<void> {
611
789
  // TODO: This solution with globals for runQueue & queueRunning
612
790
  // isn't very elegant. It works, but is there a better way?
613
791
  if (!this.runQueue) {
614
792
  this.log.silly('Asked to handle the queue, but is stopped');
615
- }
616
- else if (this.queueRunning) {
793
+ } else if (this.queueRunning) {
617
794
  this.log.silly('Asked to handle the queue, but already in progress');
618
- }
619
- else {
795
+ } else {
620
796
  this.queueRunning = true;
621
- this.log.silly('Processing events from queue length: ' + this.eventsQueue.size());
622
- let evt;
797
+ this.log.silly(`Processing events from queue length: ${this.eventsQueue.size()}`);
798
+ let evt: LoxoneEvent | null;
623
799
  while ((evt = this.eventsQueue.dequeue())) {
624
800
  this.log.silly(`Dequeued event UUID: ${evt.uuid}`);
625
801
  await this.handleEvent(evt);
@@ -628,25 +804,26 @@ class Loxone extends utils.Adapter {
628
804
  this.log.silly('Done with event queue');
629
805
  }
630
806
  }
631
- async handleEvent(evt) {
632
- var _a;
807
+
808
+ private async handleEvent(evt: LoxoneEvent): Promise<void> {
633
809
  const stateEventHandlerList = this.stateEventHandlers[evt.uuid];
634
810
  if (stateEventHandlerList === undefined) {
635
811
  this.log.debug(`Unknown event ${evt.uuid}: ${JSON.stringify(evt.evt)}`);
636
812
  this.incInfoState('info.unknownEvents', evt.uuid, evt.evt);
637
813
  return;
638
814
  }
815
+
639
816
  for (const item of stateEventHandlerList) {
640
817
  try {
641
818
  await item.handler(evt.evt);
642
- }
643
- catch (e) {
819
+ } catch (e: any) {
644
820
  this.log.error(`Error while handling event UUID ${evt.uuid}: ${e}`);
645
- (_a = this.getSentry()) === null || _a === void 0 ? void 0 : _a.captureException(e, { extra: { evt } });
821
+ this.getSentry()?.captureException(e, { extra: { evt } });
646
822
  }
647
823
  }
648
824
  }
649
- async initInfoStates() {
825
+
826
+ private async initInfoStates(): Promise<void> {
650
827
  // Wait for states to load because if we don't, although the chances
651
828
  // of processing starting before this actually completes is small, we
652
829
  // should cater for that.
@@ -657,21 +834,25 @@ class Loxone extends utils.Adapter {
657
834
  await this.initInfoState('info.stateChangesDiscarded');
658
835
  await this.initInfoState('info.unknownEvents', true);
659
836
  }
660
- async initInfoState(id, hasDetails = false) {
837
+
838
+ private async initInfoState(id: string, hasDetails = false): Promise<void> {
661
839
  const state = await this.getStateAsync(id);
662
840
  const initValue = state ? state.val : null;
663
- const entry = {
841
+ const entry: InfoEntry = {
664
842
  value: initValue,
665
843
  lastSet: initValue,
666
- timer: null,
844
+ timer: undefined,
667
845
  };
846
+
668
847
  if (hasDetails) {
669
848
  // TODO: Maybe read these in so they persist across restarts?
670
- entry.detailsMap = new Map();
849
+ entry.detailsMap = new Map<string, InfoDetailsEntry>();
671
850
  }
851
+
672
852
  this.info.set(id, entry);
673
853
  }
674
- flushInfoStates() {
854
+
855
+ private flushInfoStates(): void {
675
856
  // Called on shutdown
676
857
  this.info.forEach((infoEntry, key) => {
677
858
  if (infoEntry.timer) {
@@ -681,15 +862,17 @@ class Loxone extends utils.Adapter {
681
862
  }
682
863
  });
683
864
  }
684
- getInfoEntry(id) {
865
+
866
+ private getInfoEntry(id: string): InfoEntry | undefined {
685
867
  const infoEntry = this.info.get(id);
686
868
  if (!infoEntry) {
687
869
  // This should never happen!
688
- this.log.error('No info entry for ' + id);
870
+ this.log.error(`No info entry for ${id}`);
689
871
  }
690
872
  return infoEntry;
691
873
  }
692
- addInfoDetailsEntry(details, id, value) {
874
+
875
+ private addInfoDetailsEntry(details: InfoDetailsEntryMap, id: string, value?: any): void {
693
876
  /// ... and add details of this event to the map.
694
877
  const eventEntry = details.get(id);
695
878
  if (eventEntry) {
@@ -698,18 +881,17 @@ class Loxone extends utils.Adapter {
698
881
  if (value !== undefined) {
699
882
  eventEntry.lastValue = value;
700
883
  }
701
- }
702
- else {
884
+ } else {
703
885
  // New entry
704
886
  if (value !== undefined) {
705
887
  details.set(id, { count: 1, lastValue: value });
706
- }
707
- else {
888
+ } else {
708
889
  details.set(id, { count: 1 });
709
890
  }
710
891
  }
711
892
  }
712
- incInfoState(id, detailId, detailValue) {
893
+
894
+ private incInfoState(id: string, detailId?: string, detailValue?: any): void {
713
895
  // Increment the given ID
714
896
  const infoEntry = this.getInfoEntry(id);
715
897
  if (infoEntry) {
@@ -724,59 +906,91 @@ class Loxone extends utils.Adapter {
724
906
  }
725
907
  }
726
908
  }
727
- buildInfoDetails(src) {
909
+
910
+ private buildInfoDetails(src: InfoDetailsEntryMap): string {
728
911
  // TODO: shouldn't this use JSON.stringify?
729
- const out = [];
912
+ const out: any[] = [];
730
913
  src.forEach((value, key) => {
731
914
  if (value.lastValue !== undefined) {
732
915
  out.push({ id: key, count: value.count, lastValue: value.lastValue });
733
- }
734
- else {
916
+ } else {
735
917
  out.push({ id: key, count: value.count });
736
918
  }
737
919
  });
738
920
  return JSON.stringify(out);
739
921
  }
740
- setInfoStateIfChanged(id, infoEntry, shutdown = false) {
922
+
923
+ private setInfoStateIfChanged(id: string, infoEntry: InfoEntry, shutdown = false): void {
741
924
  if (infoEntry.value != infoEntry.lastSet) {
742
- this.log.silly('value of ' + id + ' changed to ' + infoEntry.value);
925
+ this.log.silly(`value of ${id} changed to ${infoEntry.value}`);
926
+
743
927
  // Store counter
744
- this.setState(id, infoEntry.value, true);
928
+ this.setState(id, infoEntry.value, true).catch(e => this.log.warn(e));
745
929
  infoEntry.lastSet = infoEntry.value;
930
+
746
931
  // Store any details
747
932
  if (infoEntry.detailsMap) {
748
- this.setState(id + 'Detail', this.buildInfoDetails(infoEntry.detailsMap), true);
933
+ this.setState(`${id}Detail`, this.buildInfoDetails(infoEntry.detailsMap), true).catch(e =>
934
+ this.log.warn(e),
935
+ );
749
936
  }
937
+
750
938
  if (!shutdown) {
751
939
  // Start a timer which will set the current value from the info ID map on completion
752
940
  // Obviously don't do this if called from shutdown
753
- this.log.silly('Starting timer for ' + id);
754
- infoEntry.timer = this.setTimeout((cbId, cbInfoEntry) => {
755
- this.log.silly('Timeout for ' + id);
756
- // Remove from timer from map as we have just finished
757
- cbInfoEntry.timer = null;
758
- // Update the state, but only if the value in the info ID map has changed
759
- this.setInfoStateIfChanged(cbId, cbInfoEntry);
760
- }, 30000, // Update every 30s max TODO: make this a config parameter?
761
- id, infoEntry);
941
+ this.log.silly(`Starting timer for ${id}`);
942
+ infoEntry.timer = this.setTimeout(
943
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
944
+ // @ts-ignore
945
+ (cbId: string, cbInfoEntry: InfoEntry) => {
946
+ this.log.silly(`Timeout for ${id}`);
947
+
948
+ // Remove from timer from map as we have just finished
949
+ cbInfoEntry.timer = undefined;
950
+
951
+ // Update the state, but only if the value in the info ID map has changed
952
+ this.setInfoStateIfChanged(cbId, cbInfoEntry);
953
+ },
954
+ 30000, // Update every 30s max TODO: make this a config parameter?
955
+ id,
956
+ infoEntry, // Pass reference to entry
957
+ );
762
958
  }
763
959
  }
764
960
  }
765
- sendCommand(uuid, action) {
961
+
962
+ /**
963
+ * Sends a command to the Loxone Miniserver.
964
+ *
965
+ * @param uuid The UUID of the command.
966
+ * @param action The name of the action to send.
967
+ */
968
+ public sendCommand(uuid: string, action: string): void {
766
969
  this.log.debug(`Sending command ${uuid} ${action}`);
767
970
  this.incInfoState('info.messagesSent');
768
971
  this.socket.send(`jdev/sps/io/${uuid}/${action}`, 2);
769
972
  }
770
- getExistingObject(id) {
771
- const fullId = this.namespace + '.' + id;
772
- if (this.existingObjects.hasOwnProperty(fullId)) {
773
- return this.existingObjects[fullId];
774
- }
775
- return undefined;
973
+
974
+ /**
975
+ * Get an existing object by its ID.
976
+ *
977
+ * @param id The local ID of the object (without namespace).
978
+ * @returns The existing object or undefined if not found.
979
+ */
980
+ public getExistingObject(id: string): ioBroker.Object | undefined {
981
+ const fullId = `${this.namespace}.${id}`;
982
+ return this.existingObjects[fullId];
776
983
  }
777
- async updateObjectAsync(id, obj) {
778
- const fullId = this.namespace + '.' + id;
779
- if (this.existingObjects.hasOwnProperty(fullId)) {
984
+
985
+ /**
986
+ * Update or create an object asynchronously.
987
+ *
988
+ * @param id The local ID of the object (without namespace).
989
+ * @param obj The object to update or create.
990
+ */
991
+ public async updateObjectAsync(id: string, obj: ioBroker.SettableObject): Promise<void> {
992
+ const fullId = `${this.namespace}.${id}`;
993
+ if (this.existingObjects[fullId]) {
780
994
  const existingObject = this.existingObjects[fullId];
781
995
  if (!this.config.syncNames && obj.common) {
782
996
  obj.common.name = existingObject.common.name;
@@ -787,9 +1001,24 @@ class Loxone extends utils.Adapter {
787
1001
  obj.common.smartName = existingObject.common.smartName;
788
1002
  }*/
789
1003
  }
1004
+
790
1005
  await this.extendObjectAsync(id, obj);
791
1006
  }
792
- async updateStateObjectAsync(id, commonInfo, stateUuid, stateEventHandler) {
1007
+
1008
+ /**
1009
+ * Update a state object asynchronously.
1010
+ *
1011
+ * @param id The local ID of the state (without namespace).
1012
+ * @param commonInfo The common information for the state.
1013
+ * @param stateUuid The UUID of the state.
1014
+ * @param stateEventHandler An optional event handler for state changes.
1015
+ */
1016
+ public async updateStateObjectAsync(
1017
+ id: string,
1018
+ commonInfo: ioBroker.StateCommon,
1019
+ stateUuid: string,
1020
+ stateEventHandler?: NamedStateEventHandler,
1021
+ ): Promise<void> {
793
1022
  /* TODO: re-add:
794
1023
  if (commonInfo.hasOwnProperty('smartIgnore')) {
795
1024
  // interpret smartIgnore (our own extension of common) to generate smartName if needed
@@ -800,7 +1029,7 @@ class Loxone extends utils.Adapter {
800
1029
  }
801
1030
  delete commonInfo.smartIgnore;
802
1031
  }*/
803
- const obj = {
1032
+ const obj: ioBroker.SettableObjectWorker<ioBroker.StateObject> = {
804
1033
  type: 'state',
805
1034
  common: commonInfo,
806
1035
  native: {
@@ -809,24 +1038,43 @@ class Loxone extends utils.Adapter {
809
1038
  };
810
1039
  await this.updateObjectAsync(id, obj);
811
1040
  if (stateEventHandler) {
812
- this.addStateEventHandler(stateUuid, async (value) => {
1041
+ this.addStateEventHandler(stateUuid, async (value: ioBroker.StateValue) => {
813
1042
  await stateEventHandler(id, value);
814
1043
  });
815
1044
  }
816
1045
  }
817
- addStateEventHandler(uuid, eventHandler, name) {
1046
+
1047
+ /**
1048
+ * Add a state event handler for a given UUID.
1049
+ *
1050
+ * @param uuid The UUID of the control.
1051
+ * @param eventHandler The event handler function.
1052
+ * @param name An optional name for the event handler.
1053
+ */
1054
+ public addStateEventHandler(uuid: string, eventHandler: StateEventHandler, name?: string): void {
818
1055
  if (this.stateEventHandlers[uuid] === undefined) {
819
1056
  this.stateEventHandlers[uuid] = [];
820
1057
  }
1058
+
821
1059
  if (name) {
822
1060
  this.removeStateEventHandler(uuid, name);
823
1061
  }
1062
+
824
1063
  this.stateEventHandlers[uuid].push({ name: name, handler: eventHandler });
825
1064
  }
826
- removeStateEventHandler(uuid, name) {
1065
+
1066
+ /**
1067
+ * Remove a state event handler for a given UUID by name.
1068
+ *
1069
+ * @param uuid The UUID of the control.
1070
+ * @param name The name of the event handler to remove.
1071
+ * @returns True if the handler was found and removed, false otherwise.
1072
+ */
1073
+ public removeStateEventHandler(uuid: string, name: string): boolean {
827
1074
  if (this.stateEventHandlers[uuid] === undefined || !name) {
828
1075
  return false;
829
1076
  }
1077
+
830
1078
  let found = false;
831
1079
  for (let i = 0; i < this.stateEventHandlers[uuid].length; i++) {
832
1080
  if (this.stateEventHandlers[uuid][i].name === name) {
@@ -834,17 +1082,27 @@ class Loxone extends utils.Adapter {
834
1082
  found = true;
835
1083
  }
836
1084
  }
1085
+
837
1086
  return found;
838
1087
  }
839
- addStateChangeListener(id, listener, opts) {
840
- this.stateChangeListeners[this.namespace + '.' + id] = {
1088
+
1089
+ /**
1090
+ * Add a state change listener for a given state ID.
1091
+ *
1092
+ * @param id The local ID of the state (without namespace).
1093
+ * @param listener The state change listener function.
1094
+ * @param opts Optional options for the state change listener.
1095
+ */
1096
+ public addStateChangeListener(id: string, listener: StateChangeListener, opts?: StateChangeListenerOpts): void {
1097
+ this.stateChangeListeners[`${this.namespace}.${id}`] = {
841
1098
  listener,
842
1099
  opts,
843
1100
  queuedVal: null,
844
- ackTimer: null,
1101
+ ackTimer: undefined,
845
1102
  };
846
1103
  }
847
- async checkStateForAck(id) {
1104
+
1105
+ private async checkStateForAck(id: string): Promise<void> {
848
1106
  const stateChangeListener = this.stateChangeListeners[id];
849
1107
  if (stateChangeListener) {
850
1108
  // This state change could be a result of a command we sent being ack'd
@@ -852,32 +1110,47 @@ class Loxone extends utils.Adapter {
852
1110
  // Timer is running so clear it
853
1111
  this.log.debug(`Clearing ackTimer for ${id}`);
854
1112
  this.clearTimeout(stateChangeListener.ackTimer);
855
- stateChangeListener.ackTimer = null;
1113
+ stateChangeListener.ackTimer = undefined;
856
1114
  // Send any command that may have been delayed waiting for this ack
857
1115
  await this.handleDelayedStateChange(id, stateChangeListener);
858
- }
859
- else {
1116
+ } else {
860
1117
  this.log.debug(`No ackTimer for ${id}`);
861
1118
  }
862
- }
863
- else {
1119
+ } else {
864
1120
  this.log.silly(`${id} has no stateChangeListener`);
865
1121
  }
866
1122
  }
867
- async setStateAck(id, value) {
868
- const keyId = this.namespace + '.' + id;
1123
+
1124
+ /**
1125
+ * Set state with ack = true and update cached value.
1126
+ *
1127
+ * @param id The local ID of the state (without namespace).
1128
+ * @param value The value to set.
1129
+ */
1130
+ public async setStateAck(id: string, value: CurrentStateValue): Promise<void> {
1131
+ const keyId = `${this.namespace}.${id}`;
869
1132
  this.currentStateValues[keyId] = value;
870
1133
  await this.checkStateForAck(keyId);
871
1134
  await this.setStateAsync(id, { val: value, ack: true });
872
1135
  }
873
- getCachedStateValue(id) {
874
- const keyId = this.namespace + '.' + id;
875
- if (this.currentStateValues.hasOwnProperty(keyId)) {
876
- return this.currentStateValues[keyId];
877
- }
878
- return undefined;
1136
+
1137
+ /**
1138
+ * Get a cached state value.
1139
+ *
1140
+ * @param id The local ID of the state (without namespace).
1141
+ * @returns The cached state value.
1142
+ */
1143
+ public getCachedStateValue(id: string): OldStateValue {
1144
+ const keyId = `${this.namespace}.${id}`;
1145
+ return this.currentStateValues[keyId];
879
1146
  }
880
- getSentry() {
1147
+
1148
+ /**
1149
+ * Get the Sentry instance if available.
1150
+ *
1151
+ * @returns The Sentry instance or undefined if not available.
1152
+ */
1153
+ public getSentry(): Sentry | undefined {
881
1154
  if (this.supportsFeature && this.supportsFeature('PLUGINS')) {
882
1155
  const sentryInstance = this.getPluginInstance('sentry');
883
1156
  if (sentryInstance) {
@@ -885,19 +1158,21 @@ class Loxone extends utils.Adapter {
885
1158
  }
886
1159
  }
887
1160
  }
888
- reportError(message) {
889
- var _a;
1161
+
1162
+ /**
1163
+ * Report an error message to Sentry.
1164
+ *
1165
+ * @param message The error message to report.
1166
+ */
1167
+ public reportError(message: string): void {
890
1168
  this.log.error(message);
891
- (_a = this.getSentry()) === null || _a === void 0 ? void 0 : _a.captureMessage(message, 'error');
1169
+ this.getSentry()?.captureMessage(message, 'error');
892
1170
  }
893
1171
  }
894
- exports.Loxone = Loxone;
895
- if (module.parent) {
1172
+ if (require.main !== module) {
896
1173
  // Export the constructor in compact mode
897
- module.exports = (options) => new Loxone(options);
898
- }
899
- else {
1174
+ module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new Loxone(options);
1175
+ } else {
900
1176
  // otherwise start the instance directly
901
1177
  (() => new Loxone())();
902
1178
  }
903
- //# sourceMappingURL=main.js.map