-
Notifications
You must be signed in to change notification settings - Fork 10
/
Loxone.java
369 lines (328 loc) · 13.4 KB
/
Loxone.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
package cz.smarteon.loxone;
import cz.smarteon.loxone.app.Control;
import cz.smarteon.loxone.app.LoxoneApp;
import cz.smarteon.loxone.message.ControlCommand;
import cz.smarteon.loxone.message.JsonValue;
import cz.smarteon.loxone.message.LoxoneMessage;
import cz.smarteon.loxone.message.LoxoneValue;
import cz.smarteon.loxone.user.UserCommand;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static cz.smarteon.loxone.message.ControlCommand.genericControlCommand;
import static java.util.Objects.requireNonNull;
/**
* Main entry point of the library. Provides complex access to Loxone API. Needs to be started to work properly
* and stopped to release all the resources correctly (see {@link #start()} amd {@link #stop()}).
* <p>
* After {@link #start()} request the instance of {@link LoxoneApp} and then provides it by {@link #app()}, also
* allows to listen for newly fetched {@link LoxoneApp} using {@link LoxoneAppListener}, set by
* {@link #registerLoxoneAppListener(LoxoneAppListener)}.
* <p>
* Allows to configure the connection to receive update events using {@link #setEventsEnabled(boolean)}. Use
* {@link LoxoneWebSocket#registerListener(LoxoneEventListener)} to listen for those events.
* <p>
* Provides set of methods to send loxone commands based on {@link Control}.
* Use {@link LoxoneWebSocket#registerListener(CommandResponseListener)} to listen for command responses.
*/
@SuppressWarnings("checkstyle:ClassFanOutComplexity")
public class Loxone {
private static final Logger LOG = LoggerFactory.getLogger(Loxone.class);
private final LoxoneHttp loxoneHttp;
private final LoxoneWebSocket loxoneWebSocket;
private final LoxoneAuth loxoneAuth;
private final List<LoxoneAppListener> loxoneAppListeners = new LinkedList<>();
private final LoxoneWebSocketListener webSocketListener = this::start;
private final Map<LoxoneEndpoint, LoxoneHttp> clientMiniserversHttp = new HashMap<>();
private CountDownLatch appLatch;
private LoxoneApp loxoneApp;
private boolean eventsEnabled;
private boolean started;
/**
* Creates new instance of given endpoint, user and password.
* @param endpoint endpoint, can't be null
* @param user user name, can't be null
* @param pass password, can't be null
*/
public Loxone(final @NotNull LoxoneEndpoint endpoint, final @NotNull String user, final @NotNull String pass) {
this(endpoint, user, pass, null);
}
/**
* Creates new instance of given endpoint, user, password and visualization password.
* @param endpoint endpoint, can't be null
* @param user user name, can't be null
* @param pass password, can't be null
* @param visuPass visualization password, can be null
*/
public Loxone(final @NotNull LoxoneEndpoint endpoint,
final @NotNull String user, final @NotNull String pass, final @Nullable String visuPass) {
this(new LoxoneProfile(endpoint, user, pass, visuPass));
}
/**
* Creates new instance of given profile.
* @param profile profile, can't be null
*/
public Loxone(final @NotNull LoxoneProfile profile) {
requireNonNull(profile, "profile shouldn't be null");
this.loxoneHttp = new LoxoneHttp(profile.getEndpoint());
this.loxoneAuth = new LoxoneAuth(loxoneHttp, profile);
this.loxoneWebSocket = new LoxoneWebSocket(profile.getEndpoint(), loxoneAuth);
init();
}
@SuppressWarnings("unused")
@TestOnly
Loxone(final @NotNull LoxoneHttp loxoneHttp, final @NotNull LoxoneWebSocket loxoneWebSocket,
final @NotNull LoxoneAuth loxoneAuth) {
this.loxoneHttp = requireNonNull(loxoneHttp);
this.loxoneWebSocket = requireNonNull(loxoneWebSocket);
this.loxoneAuth = requireNonNull(loxoneAuth);
init();
}
private void init() {
loxoneWebSocket.registerListener(new LoxAppResponseListener());
}
/**
* Add given {@link LoxoneAppListener} to the list of listeners.
* @param listener listener to add, can't be null
*/
public void registerLoxoneAppListener(final @NotNull LoxoneAppListener listener) {
loxoneAppListeners.add(requireNonNull(listener, "listener can't be null"));
}
/**
* Start the service - initiates the connection, authenticates, request the {@link LoxoneApp} and ask for event
* updates (in case {@link #setEventsEnabled(boolean)} set to true).
* The method is blocking - waiting for {@link LoxoneApp} first fetch for timeout derived from underlying
* {@link LoxoneWebSocket}. It means that after successful completion of this method {@link #app()} should return
* fetched {@link LoxoneApp}.
* @throws LoxoneException in case something went wrong - the app request couldn't be send or the fetch took
* too long.
*/
public void start() {
appLatch = new CountDownLatch(1);
loxoneWebSocket.sendCommand(Command.LOX_APP);
try {
final int timeout = loxoneWebSocket.getAuthTimeoutSeconds() * loxoneWebSocket.getRetries() + 1;
if (appLatch.await(timeout, TimeUnit.SECONDS)) {
LOG.info("Loxone application fetched");
if (eventsEnabled) {
LOG.info("Signing to receive events");
loxoneWebSocket.sendCommand(Command.ENABLE_STATUS_UPDATE);
}
} else {
LOG.error("Loxone application wasn't fetched within timeout");
throw new LoxoneException("Loxone application wasn't fetched within timeout");
}
} catch (InterruptedException e) {
LOG.error("Interrupted while waiting for loxone application fetch", e);
throw new LoxoneException("Interrupted while waiting for loxone application fetch", e);
}
// let's listen to next websocket open event ie when websocket was restarted
webSocket().registerWebSocketListener(webSocketListener);
started = true;
}
/**
* Configures additional client miniserver based on given endpoint.
*
* @param clientEndpoint endpoint for client miniserver
*/
public void addClientMiniserver(final @NotNull LoxoneEndpoint clientEndpoint) {
clientMiniserverHttp(requireNonNull(clientEndpoint));
}
/**
* Provides enclosed instance of {@link LoxoneAuth}.
* @return loxone auth
*/
@NotNull
public LoxoneAuth auth() {
return loxoneAuth;
}
/**
* Provides enclosed instance of {@link LoxoneHttp}.
* @return loxone http
*/
@NotNull
public LoxoneHttp http() {
return loxoneHttp;
}
/**
* Returns configured {@link LoxoneHttp} for client miniserver of given endpoint. Adds the endpoint to clients,
* in case it's not there yet.
*
* @param clientEndpoint endpoint for client miniserver
* @return configured {@link LoxoneHttp} for client miniserver
*/
@NotNull
public LoxoneHttp clientMiniserverHttp(final @NotNull LoxoneEndpoint clientEndpoint) {
return clientMiniserversHttp.computeIfAbsent(requireNonNull(clientEndpoint), LoxoneHttp::new);
}
/**
* Iterable of configured client miniservers.
* @return client miniservers {@link LoxoneHttp}
*/
@NotNull
public Iterable<LoxoneHttp> clientMiniserversHttp() {
return clientMiniserversHttp.values();
}
/**
* Iterable of all configured miniservers - first is the main miniserver and then the clients.
* @return all miniservers' {@link LoxoneHttp}
*/
@NotNull
public Iterable<LoxoneHttp> allMiniserversHttp() {
final List<LoxoneHttp> all = new ArrayList<>();
all.add(http());
all.addAll(clientMiniserversHttp.values());
return all;
}
/**
* Provides enclosed instance of {@link LoxoneWebSocket}.
* @return loxone web socket
*/
@NotNull
public LoxoneWebSocket webSocket() {
return loxoneWebSocket;
}
/**
* Get the fetched {@link LoxoneApp}, may return null of the app is not fetched yet.
* @return fetched app ro null
*/
@Nullable
public LoxoneApp app() {
return loxoneApp;
}
/**
* Send 'pulse' on given control. Use {@link CommandResponseListener} added to {@link #webSocket()}
* to process the response.
* @param control control to send 'pulse' on, can't be null
*/
public void sendControlPulse(final @NotNull Control control) {
sendControlCommand(control, "Pulse");
}
/**
* Send 'on' on given control. Use {@link CommandResponseListener} added to {@link #webSocket()}
* to process the response.
* @param control control to send 'on' on, can't be null
*/
public void sendControlOn(final @NotNull Control control) {
sendControlCommand(control, "On");
}
/**
* Send 'off' on given control. Use {@link CommandResponseListener} added to {@link #webSocket()}
* to process the response.
* @param control control to send 'off' on, can't be null
*/
public void sendControlOff(final @NotNull Control control) {
sendControlCommand(control, "Off");
}
/**
* Send command built by given commandBuilder applied on given control. Use static factory methods at
* {@link ControlCommand} or extend it with more specific classes in complex scenarios.
* Use {@link CommandResponseListener} added to {@link #webSocket()} to process the response.
* @param control control to build command from
* @param commandBuilder function returning the command of given control
* @param <C> type of the control
*/
public <C extends Control> void sendControlCommand(
final @NotNull C control, final @NotNull Function<C, ControlCommand<?>> commandBuilder) {
sendCommand(commandBuilder.apply(control), control.isSecured());
}
private void sendControlCommand(final Control control, final String command) {
requireNonNull(control, "control can't be null");
final ControlCommand<JsonValue> controlCommand = genericControlCommand(control.getUuid().toString(), command);
sendCommand(controlCommand, control.isSecured());
}
private void sendCommand(final ControlCommand<?> controlCommand, final boolean secured) {
if (secured) {
webSocket().sendSecureCommand(controlCommand);
} else {
webSocket().sendCommand(controlCommand);
}
}
/**
* Sends command built by {@link UserCommand} using http.
* @param userCommand user command
*/
public <V extends LoxoneValue> LoxoneMessage<V> sendUserCommandHttp(final @NotNull UserCommand<V> userCommand) {
return loxoneHttp.get(userCommand, loxoneAuth);
}
/**
* Sends command built by {@link UserCommand} using websocket.
* @param userCommand user command
*/
public void sendUserCommand(final @NotNull UserCommand<?> userCommand) {
loxoneWebSocket.sendCommand(userCommand);
}
/**
* Stops the service closing underlying resources, namely {@link LoxoneWebSocket}.
* Kills the authentication token.
*
* @throws LoxoneException in case the proper close failed
*/
public void stop() {
stop(true);
}
/**
* Stops the service closing underlying resources, namely {@link LoxoneWebSocket}.
* Kills the authentication token, if requested by parameter.
*
* @param killToken whether to kill authentication token
* @throws LoxoneException in case the proper close failed
*/
public void stop(final boolean killToken) {
started = false;
if (killToken) {
loxoneAuth.killToken();
}
loxoneWebSocket.close();
}
/**
* Whether the update events are enabled and requested from miniserver. By default set to false.
* @return true when update events enabled, false otherwise
*/
public boolean isEventsEnabled() {
return eventsEnabled;
}
/**
* Whether the update events are enabled and requested from miniserver. Changing the value only has effect
* before calling {@link #start()}.
* @param eventsEnabled true for events enabled, false by default.
*/
public void setEventsEnabled(final boolean eventsEnabled) {
this.eventsEnabled = eventsEnabled;
}
/**
* Whether the loxone object is started or not.
* @return true is started, otherwise false.
*/
public boolean isStarted() {
return started;
}
private class LoxAppResponseListener implements CommandResponseListener<LoxoneApp> {
@Override
public @NotNull State onCommand(
final @NotNull Command<? extends LoxoneApp> command,
final @NotNull LoxoneApp message
) {
loxoneApp = command.ensureResponse(message);
if (appLatch != null) {
appLatch.countDown();
}
loxoneAppListeners.forEach(listener -> listener.onLoxoneApp(loxoneApp));
return State.READ;
}
@Override
public boolean accepts(final @NotNull Class<?> clazz) {
return LoxoneApp.class.equals(clazz);
}
}
}