2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.somneo.internal;
15 import static org.openhab.binding.somneo.internal.SomneoBindingConstants.*;
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;
24 import javax.measure.quantity.Time;
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;
61 import com.google.gson.Gson;
62 import com.google.gson.JsonElement;
63 import com.google.gson.JsonObject;
66 * The {@link SomneoHttpConnector} is responsible for sending commands.
68 * @author Michael Myrcik - Initial contribution
71 public class SomneoHttpConnector {
73 private Logger logger = LoggerFactory.getLogger(SomneoHttpConnector.class);
75 private static final int REQUEST_TIMEOUT_MS = 5000;
77 private static final String DEFAULT_CONTENT_TYPE = "application/json";
79 private final Gson gson = new Gson();
81 private final HttpClient httpClient;
83 private final String urlBase;
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);
90 public SensorData fetchSensorData() throws TimeoutException, InterruptedException, ExecutionException {
91 return executeUrl("GET", SENSORS_ENDPOINT, SensorData.class);
94 public LightData fetchLightData() throws TimeoutException, InterruptedException, ExecutionException {
95 return executeUrl("GET", LIGHT_ENDPOINT, LightData.class);
98 public SunsetData fetchSunsetData() throws TimeoutException, InterruptedException, ExecutionException {
99 return executeUrl("GET", SUNSET_ENDPOINT, SunsetData.class);
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);
108 executeUrl("PUT", LIGHT_ENDPOINT, data);
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);
118 executeUrl("PUT", LIGHT_ENDPOINT, data);
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);
127 executeUrl("PUT", LIGHT_ENDPOINT, data);
130 public void switchSunsetProgram(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
131 final SunsetData data = new SunsetData();
132 data.setState(state);
134 executeUrl("PUT", SUNSET_ENDPOINT, data);
137 public void setSunsetLightIntensity(int percent) throws TimeoutException, InterruptedException, ExecutionException {
138 final SunsetData data = new SunsetData();
139 data.setLightIntensity(percent);
141 executeUrl("PUT", SUNSET_ENDPOINT, data);
144 public void setSunsetDuration(int duration) throws TimeoutException, InterruptedException, ExecutionException {
145 final SunsetData data = new SunsetData();
146 data.setDurationInMin(duration);
148 executeUrl("PUT", SUNSET_ENDPOINT, data);
151 public void setSunsetColorSchema(int value) throws TimeoutException, InterruptedException, ExecutionException {
152 final SunsetData data = new SunsetData();
153 data.setColorSchema(value);
155 executeUrl("PUT", SUNSET_ENDPOINT, data);
158 public void setSunsetAmbientNoise(String option) throws TimeoutException, InterruptedException, ExecutionException {
159 final SunsetData data = new SunsetData();
160 data.setAmbientNoise(option);
162 executeUrl("PUT", SUNSET_ENDPOINT, data);
165 public void setSunsetVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
166 final SunsetData data = new SunsetData();
167 data.setSoundVolume(percent);
169 executeUrl("PUT", SUNSET_ENDPOINT, data);
172 public RelaxData fetchRelaxData() throws TimeoutException, InterruptedException, ExecutionException {
173 return executeUrl("GET", RELAX_ENDPOINT, RelaxData.class);
176 public void setRelaxVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
177 final RelaxData data = new RelaxData();
178 data.setSoundVolume(percent);
180 executeUrl("PUT", RELAX_ENDPOINT, data);
183 public void setRelaxLightIntensity(int percent) throws TimeoutException, InterruptedException, ExecutionException {
184 final RelaxData data = new RelaxData();
185 data.setLightIntensity(percent);
187 executeUrl("PUT", RELAX_ENDPOINT, data);
190 public void switchRelaxProgram(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
191 final RelaxData data = new RelaxData();
192 data.setState(state);
194 executeUrl("PUT", RELAX_ENDPOINT, data);
197 public void setRelaxBreathingRate(int value) throws TimeoutException, InterruptedException, ExecutionException {
198 final RelaxData data = new RelaxData();
199 data.setBreathingRate(value);
201 executeUrl("PUT", RELAX_ENDPOINT, data);
204 public void setRelaxDuration(int value) throws TimeoutException, InterruptedException, ExecutionException {
205 final RelaxData data = new RelaxData();
206 data.setDurationInMin(value);
208 executeUrl("PUT", RELAX_ENDPOINT, data);
211 public void setRelaxGuidanceType(int value) throws TimeoutException, InterruptedException, ExecutionException {
212 final RelaxData data = new RelaxData();
213 data.setGuidanceType(value);
215 executeUrl("PUT", RELAX_ENDPOINT, data);
218 public AudioData fetchAudioData() throws TimeoutException, InterruptedException, ExecutionException {
219 return executeUrl("GET", AUDIO_ENDPOINT, AudioData.class);
222 public void switchRadio(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
223 final AudioData data = new AudioData();
230 executeUrl("PUT", AUDIO_ENDPOINT, data);
233 public void switchAux(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
234 final AudioData data = new AudioData();
241 executeUrl("PUT", AUDIO_ENDPOINT, data);
244 public void setAudioVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
245 final AudioData data = new AudioData();
246 data.setVolume(percent);
248 executeUrl("PUT", AUDIO_ENDPOINT, data);
251 public void setRadioChannel(String preset) throws TimeoutException, InterruptedException, ExecutionException {
252 final AudioData data = new AudioData();
254 data.setRadioPreset(preset);
256 executeUrl("PUT", AUDIO_ENDPOINT, data);
259 public RadioData getRadioData() throws TimeoutException, InterruptedException, ExecutionException {
260 RadioData data = new RadioData();
270 data = executeUrl("GET", RADIO_ENDPOINT, RadioData.class);
271 } while (data.isSeeking()); // Wait until seek is finished
276 public void radioSeekUp() throws TimeoutException, InterruptedException, ExecutionException {
277 final RadioData data = new RadioData();
280 executeUrl("PUT", RADIO_ENDPOINT, data);
283 public void radioSeekDown() throws TimeoutException, InterruptedException, ExecutionException {
284 final RadioData data = new RadioData();
285 data.setCmdSeekDown();
287 executeUrl("PUT", RADIO_ENDPOINT, data);
290 public DeviceData fetchDeviceData() throws TimeoutException, InterruptedException, ExecutionException {
291 return executeUrl("GET", DEVICE_ENDPOINT, DeviceData.class);
294 public WifiData fetchWifiData() throws TimeoutException, InterruptedException, ExecutionException {
295 return executeUrl("GET", WIFI_ENDPOINT, WifiData.class);
298 public FirmwareData fetchFirmwareData() throws TimeoutException, InterruptedException, ExecutionException {
299 return executeUrl("GET", FIRMWARE_ENDPOINT, FirmwareData.class);
302 public TimerData fetchTimerData() throws TimeoutException, InterruptedException, ExecutionException {
303 return executeUrl("GET", TIMER_ENDPOINT, TimerData.class);
306 public PresetData fetchPresetData() throws TimeoutException, InterruptedException, ExecutionException {
307 return executeUrl("GET", PRESET_ENDPOINT, PresetData.class);
310 public AlarmStateData fetchAlarmStateData() throws TimeoutException, InterruptedException, ExecutionException {
311 return executeUrl("GET", ALARM_STATES_ENDPOINT, AlarmStateData.class);
314 public AlarmSchedulesData fetchAlarmScheduleData()
315 throws TimeoutException, InterruptedException, ExecutionException {
316 return executeUrl("GET", ALARM_SCHEDULES_ENDPOINT, AlarmSchedulesData.class);
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);
324 return new AlarmSettingsData();
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;
335 return new QuantityType<>(snooze.getAsInt(), Units.MINUTE);
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);
344 public void toggleAlarmConfiguration(int position, OnOffType command)
345 throws TimeoutException, InterruptedException, ExecutionException {
346 final AlarmSettingsData data = AlarmSettingsData.withDefaultValues(position);
348 if (OnOffType.ON.equals(command)) {
349 final LocalTime now = LocalTime.now();
354 data.setConfigured(true);
355 data.setEnabled(true);
356 data.setAlarmTime(now);
357 data.setRepeatDay(0);
359 data.setConfigured(false);
360 data.setEnabled(false);
362 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
373 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
382 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
392 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
401 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
410 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
420 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
430 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
440 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
450 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
460 executeUrl("PUT", ALARM_EDIT_ENDPOINT, data);
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);
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);
476 * Executes the given <code>url</code> with the given <code>httpMethod</code>
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.
483 * @throws ExecutionException
484 * @throws InterruptedException
485 * @throws UnsupportedEncodingException
486 * @throws Exception when the request execution failed, timed out or it was interrupted
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);
493 final Request request = httpClient.newRequest(url).method(method).timeout(REQUEST_TIMEOUT_MS,
494 TimeUnit.MILLISECONDS);
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);
501 logger.trace("Request for url '{}':\r\n{}", url, content);
503 logger.trace("Request for url '{}'", url);
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);
513 final String encoding = response.getEncoding() != null ? response.getEncoding().replaceAll("\"", "").trim()
514 : StandardCharsets.UTF_8.name();
517 String responseBody = new String(response.getContent(), encoding);
518 logger.trace("Response for url '{}':\r\n{}", url, responseBody);
520 } catch (UnsupportedEncodingException e) {
521 logger.warn("Get response content failed!", e);