]> git.basschouten.com Git - openhab-addons.git/blob
c01378f4b0e3fc1b4a9ac3c5f43f2b97cbdc0ab0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.somneo.internal;
14
15 import static org.openhab.binding.somneo.internal.SomneoBindingConstants.*;
16
17 import java.io.UnsupportedEncodingException;
18 import java.nio.charset.StandardCharsets;
19 import java.time.LocalTime;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23
24 import javax.measure.quantity.Time;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.eclipse.jetty.client.api.ContentResponse;
30 import org.eclipse.jetty.client.api.Request;
31 import org.eclipse.jetty.client.util.StringContentProvider;
32 import org.eclipse.jetty.http.HttpMethod;
33 import org.eclipse.jetty.http.HttpStatus;
34 import org.openhab.binding.somneo.internal.model.AlarmSchedulesData;
35 import org.openhab.binding.somneo.internal.model.AlarmSettingsData;
36 import org.openhab.binding.somneo.internal.model.AlarmStateData;
37 import org.openhab.binding.somneo.internal.model.AudioData;
38 import org.openhab.binding.somneo.internal.model.DeviceData;
39 import org.openhab.binding.somneo.internal.model.FirmwareData;
40 import org.openhab.binding.somneo.internal.model.LightData;
41 import org.openhab.binding.somneo.internal.model.PresetData;
42 import org.openhab.binding.somneo.internal.model.RadioData;
43 import org.openhab.binding.somneo.internal.model.RelaxData;
44 import org.openhab.binding.somneo.internal.model.SensorData;
45 import org.openhab.binding.somneo.internal.model.SunsetData;
46 import org.openhab.binding.somneo.internal.model.TimerData;
47 import org.openhab.binding.somneo.internal.model.WifiData;
48 import org.openhab.core.io.net.http.HttpUtil;
49 import org.openhab.core.library.types.DateTimeType;
50 import org.openhab.core.library.types.DecimalType;
51 import org.openhab.core.library.types.OnOffType;
52 import org.openhab.core.library.types.PercentType;
53 import org.openhab.core.library.types.QuantityType;
54 import org.openhab.core.library.types.StringType;
55 import org.openhab.core.library.unit.Units;
56 import org.openhab.core.types.State;
57 import org.openhab.core.types.UnDefType;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 import com.google.gson.Gson;
62 import com.google.gson.JsonElement;
63 import com.google.gson.JsonObject;
64
65 /**
66  * The {@link SomneoHttpConnector} is responsible for sending commands.
67  *
68  * @author Michael Myrcik - Initial contribution
69  */
70 @NonNullByDefault
71 public class SomneoHttpConnector {
72
73     private Logger logger = LoggerFactory.getLogger(SomneoHttpConnector.class);
74
75     private static final int REQUEST_TIMEOUT_MS = 5000;
76
77     private static final String DEFAULT_CONTENT_TYPE = "application/json";
78
79     private final Gson gson = new Gson();
80
81     private final HttpClient httpClient;
82
83     private final String urlBase;
84
85     public SomneoHttpConnector(SomneoConfiguration config, HttpClient httpClient) {
86         this.httpClient = httpClient;
87         this.urlBase = String.format("https://%s:%d/di/v1/products", config.hostname, config.port);
88     }
89
90     public SensorData fetchSensorData() throws TimeoutException, InterruptedException, ExecutionException {
91         return executeUrl("GET", SENSORS_ENDPOINT, SensorData.class);
92     }
93
94     public LightData fetchLightData() throws TimeoutException, InterruptedException, ExecutionException {
95         return executeUrl("GET", LIGHT_ENDPOINT, LightData.class);
96     }
97
98     public SunsetData fetchSunsetData() throws TimeoutException, InterruptedException, ExecutionException {
99         return executeUrl("GET", SUNSET_ENDPOINT, SunsetData.class);
100     }
101
102     public void switchMainLight(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
103         final LightData data = new LightData();
104         data.setMainLight(state);
105         data.setNightLight(false);
106         data.setPreviewLight(false);
107
108         executeUrl("PUT", LIGHT_ENDPOINT, data);
109     }
110
111     public void setMainLightDimmer(int level) throws TimeoutException, InterruptedException, ExecutionException {
112         final LightData data = new LightData();
113         data.setMainLightLevel(level);
114         data.setMainLight(true);
115         data.setNightLight(false);
116         data.setPreviewLight(false);
117
118         executeUrl("PUT", LIGHT_ENDPOINT, data);
119     }
120
121     public void switchNightLight(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
122         final LightData data = new LightData();
123         data.setMainLight(false);
124         data.setNightLight(state);
125         data.setPreviewLight(false);
126
127         executeUrl("PUT", LIGHT_ENDPOINT, data);
128     }
129
130     public void switchSunsetProgram(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
131         final SunsetData data = new SunsetData();
132         data.setState(state);
133
134         executeUrl("PUT", SUNSET_ENDPOINT, data);
135     }
136
137     public void setSunsetLightIntensity(int percent) throws TimeoutException, InterruptedException, ExecutionException {
138         final SunsetData data = new SunsetData();
139         data.setLightIntensity(percent);
140
141         executeUrl("PUT", SUNSET_ENDPOINT, data);
142     }
143
144     public void setSunsetDuration(int duration) throws TimeoutException, InterruptedException, ExecutionException {
145         final SunsetData data = new SunsetData();
146         data.setDurationInMin(duration);
147
148         executeUrl("PUT", SUNSET_ENDPOINT, data);
149     }
150
151     public void setSunsetColorSchema(int value) throws TimeoutException, InterruptedException, ExecutionException {
152         final SunsetData data = new SunsetData();
153         data.setColorSchema(value);
154
155         executeUrl("PUT", SUNSET_ENDPOINT, data);
156     }
157
158     public void setSunsetAmbientNoise(String option) throws TimeoutException, InterruptedException, ExecutionException {
159         final SunsetData data = new SunsetData();
160         data.setAmbientNoise(option);
161
162         executeUrl("PUT", SUNSET_ENDPOINT, data);
163     }
164
165     public void setSunsetVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
166         final SunsetData data = new SunsetData();
167         data.setSoundVolume(percent);
168
169         executeUrl("PUT", SUNSET_ENDPOINT, data);
170     }
171
172     public RelaxData fetchRelaxData() throws TimeoutException, InterruptedException, ExecutionException {
173         return executeUrl("GET", RELAX_ENDPOINT, RelaxData.class);
174     }
175
176     public void setRelaxVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
177         final RelaxData data = new RelaxData();
178         data.setSoundVolume(percent);
179
180         executeUrl("PUT", RELAX_ENDPOINT, data);
181     }
182
183     public void setRelaxLightIntensity(int percent) throws TimeoutException, InterruptedException, ExecutionException {
184         final RelaxData data = new RelaxData();
185         data.setLightIntensity(percent);
186
187         executeUrl("PUT", RELAX_ENDPOINT, data);
188     }
189
190     public void switchRelaxProgram(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
191         final RelaxData data = new RelaxData();
192         data.setState(state);
193
194         executeUrl("PUT", RELAX_ENDPOINT, data);
195     }
196
197     public void setRelaxBreathingRate(int value) throws TimeoutException, InterruptedException, ExecutionException {
198         final RelaxData data = new RelaxData();
199         data.setBreathingRate(value);
200
201         executeUrl("PUT", RELAX_ENDPOINT, data);
202     }
203
204     public void setRelaxDuration(int value) throws TimeoutException, InterruptedException, ExecutionException {
205         final RelaxData data = new RelaxData();
206         data.setDurationInMin(value);
207
208         executeUrl("PUT", RELAX_ENDPOINT, data);
209     }
210
211     public void setRelaxGuidanceType(int value) throws TimeoutException, InterruptedException, ExecutionException {
212         final RelaxData data = new RelaxData();
213         data.setGuidanceType(value);
214
215         executeUrl("PUT", RELAX_ENDPOINT, data);
216     }
217
218     public AudioData fetchAudioData() throws TimeoutException, InterruptedException, ExecutionException {
219         return executeUrl("GET", AUDIO_ENDPOINT, AudioData.class);
220     }
221
222     public void switchRadio(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
223         final AudioData data = new AudioData();
224         if (state) {
225             data.enableRadio();
226         } else {
227             data.disableAudio();
228         }
229
230         executeUrl("PUT", AUDIO_ENDPOINT, data);
231     }
232
233     public void switchAux(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
234         final AudioData data = new AudioData();
235         if (state) {
236             data.enableAux();
237         } else {
238             data.disableAudio();
239         }
240
241         executeUrl("PUT", AUDIO_ENDPOINT, data);
242     }
243
244     public void setAudioVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
245         final AudioData data = new AudioData();
246         data.setVolume(percent);
247
248         executeUrl("PUT", AUDIO_ENDPOINT, data);
249     }
250
251     public void setRadioChannel(String preset) throws TimeoutException, InterruptedException, ExecutionException {
252         final AudioData data = new AudioData();
253         data.enableRadio();
254         data.setRadioPreset(preset);
255
256         executeUrl("PUT", AUDIO_ENDPOINT, data);
257     }
258
259     public RadioData getRadioData() throws TimeoutException, InterruptedException, ExecutionException {
260         RadioData data = new RadioData();
261         int loops = 0;
262         do {
263             if (loops > 20) {
264                 break;
265             }
266             if (loops > 0) {
267                 loops++;
268                 Thread.sleep(250);
269             }
270             data = executeUrl("GET", RADIO_ENDPOINT, RadioData.class);
271         } while (data.isSeeking()); // Wait until seek is finished
272
273         return data;
274     }
275
276     public void radioSeekUp() throws TimeoutException, InterruptedException, ExecutionException {
277         final RadioData data = new RadioData();
278         data.setCmdSeekUp();
279
280         executeUrl("PUT", RADIO_ENDPOINT, data);
281     }
282
283     public void radioSeekDown() throws TimeoutException, InterruptedException, ExecutionException {
284         final RadioData data = new RadioData();
285         data.setCmdSeekDown();
286
287         executeUrl("PUT", RADIO_ENDPOINT, data);
288     }
289
290     public DeviceData fetchDeviceData() throws TimeoutException, InterruptedException, ExecutionException {
291         return executeUrl("GET", DEVICE_ENDPOINT, DeviceData.class);
292     }
293
294     public WifiData fetchWifiData() throws TimeoutException, InterruptedException, ExecutionException {
295         return executeUrl("GET", WIFI_ENDPOINT, WifiData.class);
296     }
297
298     public FirmwareData fetchFirmwareData() throws TimeoutException, InterruptedException, ExecutionException {
299         return executeUrl("GET", FIRMWARE_ENDPOINT, FirmwareData.class);
300     }
301
302     public TimerData fetchTimerData() throws TimeoutException, InterruptedException, ExecutionException {
303         return executeUrl("GET", TIMER_ENDPOINT, TimerData.class);
304     }
305
306     public PresetData fetchPresetData() throws TimeoutException, InterruptedException, ExecutionException {
307         return executeUrl("GET", PRESET_ENDPOINT, PresetData.class);
308     }
309
310     public AlarmStateData fetchAlarmStateData() throws TimeoutException, InterruptedException, ExecutionException {
311         return executeUrl("GET", ALARM_STATES_ENDPOINT, AlarmStateData.class);
312     }
313
314     public AlarmSchedulesData fetchAlarmScheduleData()
315             throws TimeoutException, InterruptedException, ExecutionException {
316         return executeUrl("GET", ALARM_SCHEDULES_ENDPOINT, AlarmSchedulesData.class);
317     }
318
319     public AlarmSettingsData fetchAlarmSettingsData(final int position)
320             throws TimeoutException, InterruptedException, ExecutionException {
321         final String responseBody = executeUrl("PUT", ALARM_SETTINGS_ENDPOINT, "{\"prfnr\":" + (position) + "}");
322         AlarmSettingsData data = (AlarmSettingsData) gson.fromJson(responseBody, AlarmSettingsData.class);
323         if (data == null) {
324             return new AlarmSettingsData();
325         }
326         return data;
327     }
328
329     public State fetchSnoozeDuration() throws TimeoutException, InterruptedException, ExecutionException {
330         final JsonObject response = executeUrl("GET", ALARM_SETTINGS_ENDPOINT, JsonObject.class);
331         final JsonElement snooze = response.get("snztm");
332         if (snooze == null) {
333             return UnDefType.NULL;
334         }
335         return new QuantityType<>(snooze.getAsInt(), Units.MINUTE);
336     }
337
338     public void setAlarmSnooze(int snooze) throws TimeoutException, InterruptedException, ExecutionException {
339         final JsonObject data = new JsonObject();
340         data.addProperty("snztm", snooze);
341         executeUrl("PUT", ALARM_SETTINGS_ENDPOINT, data);
342     }
343
344     public void toggleAlarmConfiguration(int position, OnOffType command)
345             throws TimeoutException, InterruptedException, ExecutionException {
346         final AlarmSettingsData data = AlarmSettingsData.withDefaultValues(position);
347
348         if (OnOffType.ON.equals(command)) {
349             final LocalTime now = LocalTime.now();
350             if (now == null) {
351                 return;
352             }
353
354             data.setConfigured(true);
355             data.setEnabled(true);
356             data.setAlarmTime(now);
357             data.setRepeatDay(0);
358         } else {
359             data.setConfigured(false);
360             data.setEnabled(false);
361         }
362         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
363     }
364
365     public void toggleAlarm(int position, OnOffType enabled)
366             throws TimeoutException, InterruptedException, ExecutionException {
367         final AlarmSettingsData data = new AlarmSettingsData();
368         data.setPosition(position);
369         data.setEnabledState(enabled);
370         if (OnOffType.ON.equals(enabled)) {
371             data.setConfigured(true);
372         }
373         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
374     }
375
376     public void setAlarmTime(int position, DateTimeType time)
377             throws TimeoutException, InterruptedException, ExecutionException {
378         final AlarmSettingsData data = fetchAlarmSettingsData(position);
379         data.setConfigured(true);
380         data.setAlarmTime(time);
381
382         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
383     }
384
385     public void setAlarmRepeatDay(int position, DecimalType days)
386             throws TimeoutException, InterruptedException, ExecutionException {
387         final AlarmSettingsData data = new AlarmSettingsData();
388         data.setPosition(position);
389         data.setConfigured(true);
390         data.setRepeatDayState(days);
391
392         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
393     }
394
395     public void toggleAlarmPowerWake(int position, OnOffType state)
396             throws TimeoutException, InterruptedException, ExecutionException {
397         final AlarmSettingsData data = fetchAlarmSettingsData(position);
398         data.setPosition(position);
399         data.setPowerWakeState(state);
400
401         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
402     }
403
404     public void setAlarmPowerWakeDelay(int position, QuantityType<Time> time)
405             throws TimeoutException, InterruptedException, ExecutionException {
406         final AlarmSettingsData data = fetchAlarmSettingsData(position);
407         data.setConfigured(true);
408         data.setPowerWakeDelayState(time);
409
410         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
411     }
412
413     public void setAlarmSunriseDuration(int position, QuantityType<Time> duration)
414             throws TimeoutException, InterruptedException, ExecutionException {
415         final AlarmSettingsData data = new AlarmSettingsData();
416         data.setPosition(position);
417         data.setConfigured(true);
418         data.setSunriseDurationState(duration);
419
420         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
421     }
422
423     public void setAlarmSunriseBrightness(int position, PercentType percent)
424             throws TimeoutException, InterruptedException, ExecutionException {
425         final AlarmSettingsData data = new AlarmSettingsData();
426         data.setPosition(position);
427         data.setConfigured(true);
428         data.setSunriseBrightnessState(percent);
429
430         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
431     }
432
433     public void setAlarmSunriseSchema(int position, DecimalType schema)
434             throws TimeoutException, InterruptedException, ExecutionException {
435         final AlarmSettingsData data = new AlarmSettingsData();
436         data.setPosition(position);
437         data.setConfigured(true);
438         data.setSunriseSchemaState(schema);
439
440         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
441     }
442
443     public void setAlarmSound(int position, StringType sound)
444             throws TimeoutException, InterruptedException, ExecutionException {
445         final AlarmSettingsData data = new AlarmSettingsData();
446         data.setPosition(position);
447         data.setConfigured(true);
448         data.setAlarmSoundState(sound);
449
450         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
451     }
452
453     public void setAlarmVolume(int position, PercentType volume)
454             throws TimeoutException, InterruptedException, ExecutionException {
455         final AlarmSettingsData data = new AlarmSettingsData();
456         data.setPosition(position);
457         data.setConfigured(true);
458         data.setAlarmVolumeState(volume);
459
460         executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
461     }
462
463     private <T> T executeUrl(String httpMethod, String endpoint, Class<T> classOfT)
464             throws TimeoutException, InterruptedException, ExecutionException {
465         final String responseBody = executeUrl(httpMethod, endpoint, (String) null);
466         return gson.fromJson(responseBody, classOfT);
467     }
468
469     private void executeUrl(String httpMethod, String endpoint, Object data)
470             throws TimeoutException, InterruptedException, ExecutionException {
471         final String content = gson.toJson(data);
472         executeUrl(httpMethod, endpoint, content);
473     }
474
475     /**
476      * Executes the given <code>url</code> with the given <code>httpMethod</code>
477      *
478      * @param httpMethod the HTTP method to use
479      * @param endpoint the url endpoint
480      * @param content the content to be sent to the given <code>url</code> or
481      *            <code>null</code> if no content should be sent.
482      * @return
483      * @throws ExecutionException
484      * @throws InterruptedException
485      * @throws UnsupportedEncodingException
486      * @throws Exception when the request execution failed, timed out or it was interrupted
487      */
488     private String executeUrl(String httpMethod, String endpoint, @Nullable String content)
489             throws TimeoutException, InterruptedException, ExecutionException {
490         final String url = urlBase + endpoint;
491         final HttpMethod method = HttpUtil.createHttpMethod(httpMethod);
492
493         final Request request = httpClient.newRequest(url).method(method).timeout(REQUEST_TIMEOUT_MS,
494                 TimeUnit.MILLISECONDS);
495
496         if (content != null && (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method))) {
497             final StringContentProvider stringContentProvider = new StringContentProvider(content,
498                     StandardCharsets.UTF_8);
499             request.content(stringContentProvider, DEFAULT_CONTENT_TYPE);
500
501             logger.trace("Request for url '{}':\r\n{}", url, content);
502         } else {
503             logger.trace("Request for url '{}'", url);
504         }
505
506         final ContentResponse response = request.send();
507         final int statusCode = response.getStatus();
508         if (logger.isDebugEnabled() && statusCode >= HttpStatus.BAD_REQUEST_400) {
509             String statusLine = statusCode + " " + response.getReason();
510             logger.debug("Method failed: {}", statusLine);
511         }
512
513         final String encoding = response.getEncoding() != null ? response.getEncoding().replaceAll("\"", "").trim()
514                 : StandardCharsets.UTF_8.name();
515
516         try {
517             String responseBody = new String(response.getContent(), encoding);
518             logger.trace("Response for url '{}':\r\n{}", url, responseBody);
519             return responseBody;
520         } catch (UnsupportedEncodingException e) {
521             logger.warn("Get response content failed!", e);
522             return "";
523         }
524     }
525 }