iobroker.loxone 3.0.1 → 4.0.1

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