[somneo] Initial contribution (#12321)

* [somneo] Initial contribution

Signed-off-by: Michael Myrcik <michael.myrcik@web.de>
This commit is contained in:
0x4d4d 2022-07-27 10:38:06 +02:00 committed by GitHub
parent 1181a104f9
commit 3b8567bd9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2765 additions and 0 deletions

View File

@ -303,6 +303,7 @@
/bundles/org.openhab.binding.solarwatt/ @sven-carstens
/bundles/org.openhab.binding.somfymylink/ @loungeflyz
/bundles/org.openhab.binding.somfytahoma/ @octa22
/bundles/org.openhab.binding.somneo/ @0x4d4d
/bundles/org.openhab.binding.sonnen/ @chingon007
/bundles/org.openhab.binding.sonos/ @kgoderis @lolodomo
/bundles/org.openhab.binding.sonyaudio/ @freke

View File

@ -1511,6 +1511,11 @@
<artifactId>org.openhab.binding.somfytahoma</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.somneo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.sonnen</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,152 @@
# Philips Somneo Binding
This binding integrates Philips Somneo HF367X into openHAB.
## Supported Things
This binding does only support one Thing:
- `Philips Somneo HF367X`: A connected sleep and wake-Up light with the ThingTypeUID `hf367x`
## Thing Configuration
The Philips Somneo thing requires the `hostname` it can connect to.
Its API only allows HTTPS access, but unfortunately the SSL certificate is not trusted and must be ignored by the parameter.
| Parameter | Values | Default |
|---------------------|-------------------------------------------|---------|
| hostname | Hostname or IP address of the device | - |
| port | Port number | 443 |
| refreshInterval | Interval the device is polled in sec | 30 |
| ignoreSSLErrors | Ignore SSL Errors | true |
## Channels
| Channel | Type | Read/Write | Description |
|-----------------------|----------------------|------------|-------------------------------------------------------------|
| _Sensor_ | | | |
| sensor#illuminance | Number:Illuminance | R | The current illuminance in lux |
| sensor#temperature | Number:Temperature | R | The current temperature |
| sensor#humidity | Number:Dimensionless | R | The current humidity in % |
| sensor#noise | Number:Dimensionless | R | The current noise in dB |
| _Light_ | | | |
| light#main | Switch | RW | Turn the light on, off and set the brightness |
| light#night | Switch | RW | Turn the night light on or off |
| _Sunset_ | | | |
| sunset#switch | Switch | RW | Turn the sunset program on or off |
| sunset#remainingTime | Number:Time | R | Remaining time from an activated program |
| sunset#lightIntensity | Dimmer | RW | Set the brightness during the sunset programme |
| sunset#duration | Number:Time | RW | The duration of sunset program in minutes |
| sunset#colorSchema | Number | RW | Choose a personal sunset |
| sunset#ambientNoise | String | RW | Ambient noise played during the sunset |
| sunset#volume | Dimmer | RW | Set the volume during the sunset programme |
| _Relax_ | | | |
| relax#switch | Switch | RW | Turn the relax breathe program on or off |
| relax#remainingTime | Number:Time | R | Remaining time from an activated program |
| relax#breathingRate | Number | RW | Breathing rate per minute during the relax program |
| relax#duration | Number:Time | RW | The duration of breathe program in minutes |
| relax#guidanceType | Number | RW | Select a breath guidance type during the relax program |
| relax#lightIntensity | Dimmer | RW | Set the brightness during the breathe programme |
| relax#volume | Dimmer | RW | Set the volume during the breathe programme |
| _Audio_ | | | |
| audio#radio | Player | RW | Controlling the radio and seeking for a frequency |
| audio#aux | Switch | RW | Turn the AUX input on or off |
| audio#volume | Dimmer | RW | Change the sound volume of the device |
| audio#preset | String | RW | The Device has 5 presets to store radio frequencies |
| audio#frequency | String | R | The currently selected radio frequency |
## Full Example
somneo.things:
```
Thing somneo:hf367x:1 "Philips Somneo" @ "Bedroom" [ hostname="192.168.0.110", ignoreSSLErrors=true ]
```
somneo.items:
```
// Sensors
Number:Illuminance PhilipsSomneo_Illuminance "Illuminance" <Sun> ["Measurement", "Light"] { channel="somneo:hf367x:1:sensor#illuminance" }
Number:Temperature PhilipsSomneo_Temperature "Temperature" <Temperature> ["Measurement", "Temperature"] { channel="somneo:hf367x:1:sensor#temperature" }
Number:Dimensionless PhilipsSomneo_Humidity "Humidity" <Humidity> ["Measurement", "Humidity"] { channel="somneo:hf367x:1:sensor#humidity" }
Number:Dimensionless PhilipsSomneo_Noise "Noise" <Noise> ["Measurement", "Noise"] { channel="somneo:hf367x:1:sensor#noise" }
// Light
Dimmer PhilipsSomneo_MainLight "Light" <Light> ["Control", "Light"] { channel="somneo:hf367x:1:light#main" }
Switch PhilipsSomneo_NightLite "Night Light" <Light> ["Control", "Light"] { channel="somneo:hf367x:1:light#night" }
// Sunset
Switch PhilipsSomneo_SunsetSwitch "Sunset Program" <Light> ["Switch", "Power"] { channel="somneo:hf367x:1:sunset#switch" }
Number:Time PhilipsSomneo_SunsetRemaining "Remaining Time" <Time> ["Status", "Duration"] { channel="somneo:hf367x:1:sunset#remainingTime" }
Dimmer PhilipsSomneo_SunsetIntensity "Light Intensity" <Light> ["Control", "Light"] { channel="somneo:hf367x:1:sunset#lightIntensity" }
Number:Time PhilipsSomneo_SunsetDuration "Duration" <Time> ["Control", "Duration"] { channel="somneo:hf367x:1:sunset#duration" }
Number PhilipsSomneo_SunsetColor "Sunset Color" <Sunset> ["Control", "ColorTemperature"] { channel="somneo:hf367x:1:sunset#colorSchema" }
String PhilipsSomneo_SunsetNoise "Ambient Noise" <Noise> ["Control", "Noise"] { channel="somneo:hf367x:1:sunset#ambientNoise" }
Dimmer PhilipsSomneo_SunsetVolume "Volume" <SoundVolume> ["Control", "SoundVolume"] { channel="somneo:hf367x:1:sunset#volume" }
// Relax
Switch PhilipsSomneo_RelaxSwitch "Relax Program" <Light> ["Switch", "Power"] { channel="somneo:hf367x:1:relax#switch" }
Number:Time PhilipsSomneo_RelaxRemaining "Remaining Time" <Time> ["Status", "Duration"] { channel="somneo:hf367x:1:relax#remainingTime" }
Number PhilipsSomneo_RelaxBreathingRate "Breathing Rate" ["Control"] { channel="somneo:hf367x:1:relax#breathingRate" }
Number:Time PhilipsSomneo_RelaxDuration "Duration" <Time> ["Control", "Duration"] { channel="somneo:hf367x:1:relax#duration" }
Number PhilipsSomneo_RelaxGuidanceType "Guidance Type" ["Control"] { channel="somneo:hf367x:1:relax#guidanceType" }
Dimmer PhilipsSomneo_RelaxIntensity "Light Intensity" <Light> ["Control", "Light"] { channel="somneo:hf367x:1:relax#lightIntensity" }
Dimmer PhilipsSomneo_RelaxVolume "Volume" <SoundVolume> ["Control", "SoundVolume"] { channel="somneo:hf367x:1:relax#volume" }
// Audio
Player PhilipsSomneo_AudioRadio "Radio Control" <MediaControl> ["Control"] { channel="somneo:hf367x:1:audio#radio" }
Switch PhilipsSomneo_AudioAux "AUX-Input" ["Switch", "Power"] { channel="somneo:hf367x:1:audio#aux" }
Dimmer PhilipsSomneo_AudioVolume "Volume" <SoundVolume> ["Control", "SoundVolume"] { channel="somneo:hf367x:1:audio#volume" }
String PhilipsSomneo_AudioPreset "FM Preset" ["Control"] { channel="somneo:hf367x:1:audio#preset" }
String PhilipsSomneo_AudioFrequency "FM Frequency" ["Status"] { channel="somneo:hf367x:1:audio#frequency" }
```
somneo.sitemap:
```
sitemap somneo label="Philips Somneo" {
Frame label="Sensors" {
Default item=PhilipsSomneo_Illuminance
Default item=PhilipsSomneo_Temperature
Default item=PhilipsSomneo_Humidity
Default item=PhilipsSomneo_Noise
}
Frame label="Lights" {
Default item=PhilipsSomneo_MainLight
Default item=PhilipsSomneo_MainLight visibility=[PhilipsSomneo_MainLight>0]
Default item=PhilipsSomneo_NightLite
}
Frame label="Programs" {
Default item=PhilipsSomneo_SunsetSwitch
Default item=PhilipsSomneo_SunsetRemaining visibility=[PhilipsSomneo_SunsetSwitch==ON]
Text label="Sunset Settings" icon="settings" {
Default item=PhilipsSomneo_SunsetIntensity
Default item=PhilipsSomneo_SunsetDuration
Selection item=PhilipsSomneo_SunsetColor
Default item=PhilipsSomneo_SunsetNoise
Default item=PhilipsSomneo_SunsetVolume
}
Default item=PhilipsSomneo_RelaxSwitch
Default item=PhilipsSomneo_RelaxRemaining visibility=[PhilipsSomneo_RelaxSwitch==ON]
Text label="Relax Settings" icon="settings" {
Default item=PhilipsSomneo_RelaxBreathingRate
Selection item=PhilipsSomneo_RelaxDuration
Switch item=PhilipsSomneo_RelaxGuidanceType mappings=[0="Light", 1="Sound"]
Default item=PhilipsSomneo_RelaxIntensity
Default item=PhilipsSomneo_RelaxVolume
}
}
Frame label="Audio" {
Default item=PhilipsSomneo_AudioRadio
Default item=PhilipsSomneo_AudioAux
Default item=PhilipsSomneo_AudioVolume visibility=[PhilipsSomneo_AudioRadio==PLAY, PhilipsSomneo_AudioAux==ON]
Default item=PhilipsSomneo_AudioPreset visibility=[PhilipsSomneo_AudioRadio==PLAY]
Default item=PhilipsSomneo_AudioFrequency visibility=[PhilipsSomneo_AudioRadio==PLAY]
}
}
```
## Acknowledgements
Thanks to:
* [homebridge-somneo](https://github.com/zackwag/homebridge-somneo) - For creating a similar plugin in another platform and exposing endpoints for control.
* [somneo-client](https://github.com/DonkerNet/somneo-client) - For creating a similar plugin in another platform and exposing endpoints for control.
* HTTP Binding and other OpenHAB addons - Which was used as examples.

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.4.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.somneo</artifactId>
<name>openHAB Add-ons :: Bundles :: Somneo Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.somneo-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-somneo" description="Somneo Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.somneo/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
/**
* The {@link HttpClientProvider} defines the interface for providing
* {@link HttpClient} instances to thing handlers.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public interface HttpClientProvider {
/**
* Get the secure http client
*
* @return a HttpClient
*/
HttpClient getSecureClient();
/**
* Get the insecure http client (ignores SSL errors)
*
* @return a HttpClient
*/
HttpClient getInsecureClient();
}

View File

@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link SomneoBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class SomneoBindingConstants {
private static final String BINDING_ID = "somneo";
// List of all Thing properties
public static final String PROPERTY_VENDOR_NAME = "Philips";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_HF367X = new ThingTypeUID(BINDING_ID, "hf367x");
// List of all Channel ids
public static final String CHANNEL_AUDIO_AUX = "audio#aux";
public static final String CHANNEL_AUDIO_FREQUENCY = "audio#frequency";
public static final String CHANNEL_AUDIO_PRESET = "audio#preset";
public static final String CHANNEL_AUDIO_RADIO = "audio#radio";
public static final String CHANNEL_AUDIO_VOLUME = "audio#volume";
public static final String CHANNEL_LIGHT_MAIN = "light#main";
public static final String CHANNEL_LIGHT_NIGHT = "light#night";
public static final String CHANNEL_RELAX_BREATHING_RATE = "relax#breathingRate";
public static final String CHANNEL_RELAX_DURATION = "relax#duration";
public static final String CHANNEL_RELAX_GUIDANCE_TYPE = "relax#guidanceType";
public static final String CHANNEL_RELAX_LIGHT_INTENSITY = "relax#lightIntensity";
public static final String CHANNEL_RELAX_REMAINING_TIME = "relax#remainingTime";
public static final String CHANNEL_RELAX_SWITCH = "relax#switch";
public static final String CHANNEL_RELAX_VOLUME = "relax#volume";
public static final String CHANNEL_SENSOR_ILLUMINANCE = "sensor#illuminance";
public static final String CHANNEL_SENSOR_HUMIDITY = "sensor#humidity";
public static final String CHANNEL_SENSOR_NOISE = "sensor#noise";
public static final String CHANNEL_SENSOR_TEMPERATURE = "sensor#temperature";
public static final String CHANNEL_SUNSET_AMBIENT_NOISE = "sunset#ambientNoise";
public static final String CHANNEL_SUNSET_COLOR_SCHEMA = "sunset#colorSchema";
public static final String CHANNEL_SUNSET_DURATION = "sunset#duration";
public static final String CHANNEL_SUNSET_LIGHT_INTENSITY = "sunset#lightIntensity";
public static final String CHANNEL_SUNSET_REMAINING_TIME = "sunset#remainingTime";
public static final String CHANNEL_SUNSET_SWITCH = "sunset#switch";
public static final String CHANNEL_SUNSET_VOLUME = "sunset#volume";
// List of all Web Service Endpoints
public static final String AUDIO_ENDPOINT = "/1/wuply";
public static final String DEVICE_ENDPOINT = "/1/device";
public static final String FIRMWARE_ENDPOINT = "/0/firmware";
public static final String LIGHT_ENDPOINT = "/1/wulgt";
public static final String PRESET_ENDPOINT = "/1/wufmp/00";
public static final String RADIO_ENDPOINT = "/1/wufmr";
public static final String RELAX_ENDPOINT = "/1/wurlx";
public static final String TIMER_ENDPOINT = "/1/wutmr";
public static final String SENSORS_ENDPOINT = "/1/wusrd";
public static final String SUNSET_ENDPOINT = "/1/wudsk";
public static final String WIFI_ENDPOINT = "/0/wifi";
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SomneoConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class SomneoConfiguration {
public String hostname = "";
public int port = 443;
public int refreshInterval = 30;
public boolean ignoreSSLErrors = false;
}

View File

@ -0,0 +1,544 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal;
import static org.openhab.binding.somneo.internal.SomneoBindingConstants.*;
import java.io.EOFException;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.somneo.internal.model.AudioData;
import org.openhab.binding.somneo.internal.model.DeviceData;
import org.openhab.binding.somneo.internal.model.FirmwareData;
import org.openhab.binding.somneo.internal.model.LightData;
import org.openhab.binding.somneo.internal.model.PresetData;
import org.openhab.binding.somneo.internal.model.RadioData;
import org.openhab.binding.somneo.internal.model.RelaxData;
import org.openhab.binding.somneo.internal.model.SensorData;
import org.openhab.binding.somneo.internal.model.SunsetData;
import org.openhab.binding.somneo.internal.model.TimerData;
import org.openhab.binding.somneo.internal.model.WifiData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.NextPreviousType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SomneoHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class SomneoHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SomneoHandler.class);
private final HttpClientProvider httpClientProvider;
private final SomneoPresetStateDescriptionProvider provider;
/**
* Job to poll data from the device.
*/
private @Nullable ScheduledFuture<?> pollingJob;
/**
* Job to count down the remaining program time.
*/
private @Nullable ScheduledFuture<?> remainingTimerJob;
private @Nullable SomneoHttpConnector connector;
/**
* Cache the last brightness level in order to know the correct level when the
* ON command is given.
*/
private volatile int lastLightBrightness;
private volatile int remainingTimeRelax;
private volatile int remainingTimeSunset;
public SomneoHandler(Thing thing, HttpClientProvider httpClientProvider,
SomneoPresetStateDescriptionProvider provider) {
super(thing);
this.httpClientProvider = httpClientProvider;
this.provider = provider;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String channelId = channelUID.getId();
logger.debug("Handle command '{}' for channel {}", command, channelId);
if (command instanceof RefreshType) {
this.poll();
return;
}
final SomneoHttpConnector connector = this.connector;
if (connector == null) {
return;
}
try {
switch (channelId) {
case CHANNEL_AUDIO_AUX:
if (command instanceof OnOffType) {
boolean isOn = OnOffType.ON.equals(command);
connector.switchAux(isOn);
if (isOn) {
updateState(CHANNEL_AUDIO_RADIO, PlayPauseType.PAUSE);
updateState(CHANNEL_RELAX_SWITCH, OnOffType.OFF);
updateState(CHANNEL_SUNSET_SWITCH, OnOffType.OFF);
}
}
break;
case CHANNEL_AUDIO_PRESET:
if (command instanceof StringType) {
connector.setRadioChannel(command.toFullString());
updateState(CHANNEL_AUDIO_RADIO, PlayPauseType.PLAY);
updateState(CHANNEL_AUDIO_AUX, OnOffType.OFF);
updateState(CHANNEL_RELAX_SWITCH, OnOffType.OFF);
updateState(CHANNEL_SUNSET_SWITCH, OnOffType.OFF);
updateFrequency();
}
break;
case CHANNEL_AUDIO_RADIO:
if (command instanceof PlayPauseType) {
boolean isPlaying = PlayPauseType.PLAY.equals(command);
connector.switchRadio(isPlaying);
if (isPlaying) {
updateState(CHANNEL_AUDIO_AUX, OnOffType.OFF);
updateState(CHANNEL_RELAX_SWITCH, OnOffType.OFF);
updateState(CHANNEL_SUNSET_SWITCH, OnOffType.OFF);
}
} else if (command instanceof NextPreviousType && NextPreviousType.NEXT.equals(command)) {
connector.radioSeekUp();
updateFrequency();
} else if (command instanceof NextPreviousType && NextPreviousType.PREVIOUS.equals(command)) {
connector.radioSeekDown();
updateFrequency();
}
break;
case CHANNEL_AUDIO_VOLUME:
if (command instanceof PercentType) {
connector.setAudioVolume(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_LIGHT_MAIN:
if (command instanceof OnOffType) {
boolean isOn = OnOffType.ON.equals(command);
connector.switchMainLight(isOn);
if (isOn) {
updateState(CHANNEL_LIGHT_MAIN, new PercentType(lastLightBrightness));
updateState(CHANNEL_LIGHT_NIGHT, OnOffType.OFF);
updateState(CHANNEL_RELAX_SWITCH, OnOffType.OFF);
updateState(CHANNEL_SUNSET_SWITCH, OnOffType.OFF);
}
}
if (command instanceof PercentType) {
int level = Integer.parseInt(command.toFullString());
if (level > 0) {
connector.setMainLightDimmer(level);
lastLightBrightness = level;
updateState(CHANNEL_LIGHT_NIGHT, OnOffType.OFF);
updateState(CHANNEL_RELAX_SWITCH, OnOffType.OFF);
updateState(CHANNEL_SUNSET_SWITCH, OnOffType.OFF);
} else {
connector.switchMainLight(false);
}
}
break;
case CHANNEL_LIGHT_NIGHT:
if (command instanceof OnOffType) {
boolean isOn = OnOffType.ON.equals(command);
connector.switchNightLight(isOn);
if (isOn) {
updateState(CHANNEL_LIGHT_MAIN, OnOffType.OFF);
updateState(CHANNEL_RELAX_SWITCH, OnOffType.OFF);
updateState(CHANNEL_SUNSET_SWITCH, OnOffType.OFF);
}
}
break;
case CHANNEL_RELAX_BREATHING_RATE:
if (command instanceof DecimalType) {
connector.setRelaxBreathingRate(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_RELAX_DURATION:
if (command instanceof DecimalType) {
connector.setRelaxDuration(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_RELAX_GUIDANCE_TYPE:
if (command instanceof DecimalType) {
connector.setRelaxGuidanceType(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_RELAX_LIGHT_INTENSITY:
if (command instanceof PercentType) {
connector.setRelaxLightIntensity(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_RELAX_SWITCH:
if (command instanceof OnOffType) {
boolean isOn = OnOffType.ON.equals(command);
connector.switchRelaxProgram(isOn);
updateRemainingTimer();
if (isOn) {
updateState(CHANNEL_AUDIO_AUX, OnOffType.OFF);
updateState(CHANNEL_AUDIO_RADIO, PlayPauseType.PAUSE);
updateState(CHANNEL_LIGHT_MAIN, OnOffType.OFF);
updateState(CHANNEL_LIGHT_NIGHT, OnOffType.OFF);
updateState(CHANNEL_SUNSET_SWITCH, OnOffType.OFF);
}
}
break;
case CHANNEL_RELAX_VOLUME:
if (command instanceof PercentType) {
connector.setRelaxVolume(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_SUNSET_AMBIENT_NOISE:
if (command instanceof StringType) {
connector.setSunsetAmbientNoise(command.toFullString());
}
break;
case CHANNEL_SUNSET_COLOR_SCHEMA:
if (command instanceof DecimalType) {
connector.setSunsetColorSchema(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_SUNSET_DURATION:
if (command instanceof DecimalType) {
connector.setSunsetDuration(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_SUNSET_LIGHT_INTENSITY:
if (command instanceof PercentType) {
connector.setSunsetLightIntensity(Integer.parseInt(command.toFullString()));
}
break;
case CHANNEL_SUNSET_SWITCH:
if (command instanceof OnOffType) {
boolean isOn = OnOffType.ON.equals(command);
connector.switchSunsetProgram(isOn);
updateRemainingTimer();
if (isOn) {
updateState(CHANNEL_AUDIO_AUX, OnOffType.OFF);
updateState(CHANNEL_AUDIO_RADIO, PlayPauseType.PAUSE);
updateState(CHANNEL_LIGHT_MAIN, OnOffType.OFF);
updateState(CHANNEL_LIGHT_NIGHT, OnOffType.OFF);
updateState(CHANNEL_RELAX_SWITCH, OnOffType.OFF);
}
}
break;
case CHANNEL_SUNSET_VOLUME:
if (command instanceof PercentType) {
connector.setSunsetVolume(Integer.parseInt(command.toFullString()));
}
break;
default:
logger.warn("Received unknown channel {}", channelId);
break;
}
} catch (InterruptedException e) {
logger.debug("Handle command interrupted");
Thread.currentThread().interrupt();
} catch (TimeoutException | ExecutionException e) {
if (e.getCause() instanceof EOFException) {
// Occurs on parallel mobile app access
logger.debug("EOF: {}", e.getMessage());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
initConnector();
updateThingProperties();
startPolling();
}
@Override
public void dispose() {
stopPolling();
stopRemainingTimer();
super.dispose();
}
private void initConnector() {
if (connector == null) {
SomneoConfiguration config = getConfigAs(SomneoConfiguration.class);
HttpClient httpClient;
if (config.ignoreSSLErrors) {
logger.info("Using the insecure client for thing '{}'.", thing.getUID());
httpClient = httpClientProvider.getInsecureClient();
} else {
logger.info("Using the secure client for thing '{}'.", thing.getUID());
httpClient = httpClientProvider.getSecureClient();
}
connector = new SomneoHttpConnector(config, httpClient);
}
}
private void updateThingProperties() {
final SomneoHttpConnector connector = this.connector;
if (connector == null) {
return;
}
Map<String, String> properties = editProperties();
properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME);
try {
final DeviceData deviceData = connector.fetchDeviceData();
String value = deviceData.getModelId();
if (value != null) {
properties.put(Thing.PROPERTY_MODEL_ID, value);
}
value = deviceData.getSerial();
if (value != null) {
properties.put(Thing.PROPERTY_SERIAL_NUMBER, value);
}
final WifiData wifiData = connector.fetchWifiData();
value = wifiData.getMacAddress();
if (value != null) {
properties.put(Thing.PROPERTY_MAC_ADDRESS, value);
}
final FirmwareData firmwareData = connector.fetchFirmwareData();
value = firmwareData.getVersion();
if (value != null) {
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, value);
}
updateProperties(properties);
} catch (InterruptedException e) {
logger.debug("Update properties interrupted");
Thread.currentThread().interrupt();
} catch (TimeoutException | ExecutionException e) {
if (e.getCause() instanceof EOFException) {
// Occurs on parallel mobile app access
logger.debug("EOF: {}", e.getMessage());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
/**
* Set up the connection to the receiver by starting to poll the HTTP API.
*/
private void startPolling() {
final ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null && !pollingJob.isCancelled()) {
return;
}
int refreshInterval = getConfigAs(SomneoConfiguration.class).refreshInterval;
logger.debug("Start polling job at interval {}s", refreshInterval);
this.pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshInterval, TimeUnit.SECONDS);
}
private void stopPolling() {
final ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob == null || pollingJob.isCancelled()) {
return;
}
pollingJob.cancel(true);
this.pollingJob = null;
logger.debug("HTTP polling stopped.");
}
private void poll() {
final SomneoHttpConnector connector = this.connector;
if (connector == null) {
return;
}
try {
final SensorData sensorData = connector.fetchSensorData();
updateState(CHANNEL_SENSOR_HUMIDITY, sensorData.getCurrentHumidity());
updateState(CHANNEL_SENSOR_ILLUMINANCE, sensorData.getCurrentIlluminance());
updateState(CHANNEL_SENSOR_NOISE, sensorData.getCurrentNoise());
updateState(CHANNEL_SENSOR_TEMPERATURE, sensorData.getCurrentTemperature());
final LightData lightData = connector.fetchLightData();
updateState(CHANNEL_LIGHT_MAIN, lightData.getMainLightState());
updateState(CHANNEL_LIGHT_NIGHT, lightData.getNightLightState());
lastLightBrightness = lightData.getMainLightLevel();
final SunsetData sunsetData = connector.fetchSunsetData();
updateState(CHANNEL_SUNSET_SWITCH, sunsetData.getSwitchState());
updateState(CHANNEL_SUNSET_LIGHT_INTENSITY, sunsetData.getLightIntensity());
updateState(CHANNEL_SUNSET_DURATION, sunsetData.getDurationInMin());
updateState(CHANNEL_SUNSET_COLOR_SCHEMA, sunsetData.getColorSchema());
updateState(CHANNEL_SUNSET_AMBIENT_NOISE, sunsetData.getAmbientNoise());
updateState(CHANNEL_SUNSET_VOLUME, sunsetData.getSoundVolume());
final RelaxData relaxData = connector.fetchRelaxData();
updateState(CHANNEL_RELAX_SWITCH, relaxData.getSwitchState());
updateState(CHANNEL_RELAX_BREATHING_RATE, relaxData.getBreathingRate());
updateState(CHANNEL_RELAX_DURATION, relaxData.getDurationInMin());
updateState(CHANNEL_RELAX_GUIDANCE_TYPE, relaxData.getGuidanceType());
updateState(CHANNEL_RELAX_LIGHT_INTENSITY, relaxData.getLightIntensity());
updateState(CHANNEL_RELAX_VOLUME, relaxData.getSoundVolume());
final AudioData audioData = connector.fetchAudioData();
updateState(CHANNEL_AUDIO_RADIO, audioData.getRadioState());
updateState(CHANNEL_AUDIO_AUX, audioData.getAuxState());
updateState(CHANNEL_AUDIO_VOLUME, audioData.getVolumeState());
updateState(CHANNEL_AUDIO_PRESET, audioData.getPresetState());
updateFrequency();
updateRemainingTimer();
updateStatus(ThingStatus.ONLINE);
} catch (InterruptedException e) {
logger.debug("Polling data interrupted");
Thread.currentThread().interrupt();
} catch (TimeoutException | ExecutionException e) {
if (e.getCause() instanceof EOFException) {
// Occurs on parallel mobile app access
logger.debug("EOF: {}", e.getMessage());
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
private void updateFrequency() throws TimeoutException, InterruptedException, ExecutionException {
final SomneoHttpConnector connector = this.connector;
if (connector == null) {
return;
}
RadioData radioData = connector.getRadioData();
updateState(CHANNEL_AUDIO_FREQUENCY, radioData.getFrequency());
final PresetData presetData = connector.fetchPresetData();
final Channel presetChannel = getThing().getChannel(CHANNEL_AUDIO_PRESET);
if (presetChannel != null) {
provider.setStateOptions(presetChannel.getUID(), presetData.createPresetOptions());
}
}
private void updateRemainingTimer() throws TimeoutException, InterruptedException, ExecutionException {
final SomneoHttpConnector connector = this.connector;
if (connector == null) {
return;
}
TimerData timerData = connector.fetchTimerData();
remainingTimeRelax = timerData.remainingTimeRelax();
remainingTimeSunset = timerData.remainingTimeSunset();
if (remainingTimeRelax > 0 || remainingTimeSunset > 0) {
startRemainingTimer();
} else {
State state = new QuantityType<>(0, Units.SECOND);
updateState(CHANNEL_RELAX_REMAINING_TIME, state);
updateState(CHANNEL_SUNSET_REMAINING_TIME, state);
}
}
private void startRemainingTimer() {
final ScheduledFuture<?> remainingTimerJob = this.remainingTimerJob;
if (remainingTimerJob != null && !remainingTimerJob.isCancelled()) {
return;
}
logger.debug("Start remaining timer ticker job");
this.remainingTimerJob = scheduler.scheduleWithFixedDelay(this::remainingTimerTick, 0, 1, TimeUnit.SECONDS);
}
private void stopRemainingTimer() {
final ScheduledFuture<?> remainingTimerJob = this.remainingTimerJob;
if (remainingTimerJob == null || remainingTimerJob.isCancelled()) {
return;
}
remainingTimerJob.cancel(true);
this.remainingTimerJob = null;
logger.debug("Remaining timer ticker stopped.");
}
private void remainingTimerTick() {
if (remainingTimeRelax > 0) {
remainingTimeRelax--;
State state = new QuantityType<>(remainingTimeRelax, Units.SECOND);
updateState(CHANNEL_RELAX_REMAINING_TIME, state);
}
if (remainingTimeSunset > 0) {
remainingTimeSunset--;
State state = new QuantityType<>(remainingTimeSunset, Units.SECOND);
updateState(CHANNEL_SUNSET_REMAINING_TIME, state);
}
if (remainingTimeRelax <= 0 && remainingTimeSunset <= 0) {
stopRemainingTimer();
}
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal;
import static org.openhab.binding.somneo.internal.SomneoBindingConstants.THING_TYPE_HF367X;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SomneoHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.somneo", service = ThingHandlerFactory.class)
public class SomneoHandlerFactory extends BaseThingHandlerFactory implements HttpClientProvider {
private final Logger logger = LoggerFactory.getLogger(SomneoHandlerFactory.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_HF367X);
private final HttpClient secureClient;
private final HttpClient insecureClient;
private final SomneoPresetStateDescriptionProvider provider;
@Activate
public SomneoHandlerFactory(@Reference SomneoPresetStateDescriptionProvider provider) {
this.provider = provider;
this.secureClient = new HttpClient(new SslContextFactory.Client(false));
this.insecureClient = new HttpClient(new SslContextFactory.Client(true));
try {
this.secureClient.start();
this.insecureClient.start();
} catch (Exception e) {
logger.warn("Failed to start insecure http client: {}", e.getMessage());
throw new IllegalStateException("Could not create insecure HttpClient");
}
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_HF367X.equals(thingTypeUID)) {
return new SomneoHandler(thing, this, provider);
}
return null;
}
@Override
public HttpClient getSecureClient() {
return secureClient;
}
@Override
public HttpClient getInsecureClient() {
return insecureClient;
}
}

View File

@ -0,0 +1,356 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal;
import static org.openhab.binding.somneo.internal.SomneoBindingConstants.*;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.somneo.internal.model.AudioData;
import org.openhab.binding.somneo.internal.model.DeviceData;
import org.openhab.binding.somneo.internal.model.FirmwareData;
import org.openhab.binding.somneo.internal.model.LightData;
import org.openhab.binding.somneo.internal.model.PresetData;
import org.openhab.binding.somneo.internal.model.RadioData;
import org.openhab.binding.somneo.internal.model.RelaxData;
import org.openhab.binding.somneo.internal.model.SensorData;
import org.openhab.binding.somneo.internal.model.SunsetData;
import org.openhab.binding.somneo.internal.model.TimerData;
import org.openhab.binding.somneo.internal.model.WifiData;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link SomneoHttpConnector} is responsible for sending commands.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class SomneoHttpConnector {
private Logger logger = LoggerFactory.getLogger(SomneoHttpConnector.class);
private static final int REQUEST_TIMEOUT_MS = 5000;
private static final String DEFAULT_CONTENT_TYPE = "application/json";
private final Gson gson = new Gson();
private final HttpClient httpClient;
private final String urlBase;
public SomneoHttpConnector(SomneoConfiguration config, HttpClient httpClient) {
this.httpClient = httpClient;
this.urlBase = String.format("https://%s:%d/di/v1/products", config.hostname, config.port);
}
public SensorData fetchSensorData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", SENSORS_ENDPOINT, SensorData.class);
}
public LightData fetchLightData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", LIGHT_ENDPOINT, LightData.class);
}
public SunsetData fetchSunsetData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", SUNSET_ENDPOINT, SunsetData.class);
}
public void switchMainLight(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
final LightData data = new LightData();
data.setMainLight(state);
data.setNightLight(false);
data.setPreviewLight(false);
executeUrl("PUT", LIGHT_ENDPOINT, data);
}
public void setMainLightDimmer(int level) throws TimeoutException, InterruptedException, ExecutionException {
final LightData data = new LightData();
data.setMainLightLevel(level);
data.setMainLight(true);
data.setNightLight(false);
data.setPreviewLight(false);
executeUrl("PUT", LIGHT_ENDPOINT, data);
}
public void switchNightLight(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
final LightData data = new LightData();
data.setMainLight(false);
data.setNightLight(state);
data.setPreviewLight(false);
executeUrl("PUT", LIGHT_ENDPOINT, data);
}
public void switchSunsetProgram(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
final SunsetData data = new SunsetData();
data.setState(state);
executeUrl("PUT", SUNSET_ENDPOINT, data);
}
public void setSunsetLightIntensity(int percent) throws TimeoutException, InterruptedException, ExecutionException {
final SunsetData data = new SunsetData();
data.setLightIntensity(percent);
executeUrl("PUT", SUNSET_ENDPOINT, data);
}
public void setSunsetDuration(int duration) throws TimeoutException, InterruptedException, ExecutionException {
final SunsetData data = new SunsetData();
data.setDurationInMin(duration);
executeUrl("PUT", SUNSET_ENDPOINT, data);
}
public void setSunsetColorSchema(int value) throws TimeoutException, InterruptedException, ExecutionException {
final SunsetData data = new SunsetData();
data.setColorSchema(value);
executeUrl("PUT", SUNSET_ENDPOINT, data);
}
public void setSunsetAmbientNoise(String option) throws TimeoutException, InterruptedException, ExecutionException {
final SunsetData data = new SunsetData();
data.setAmbientNoise(option);
executeUrl("PUT", SUNSET_ENDPOINT, data);
}
public void setSunsetVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
final SunsetData data = new SunsetData();
data.setSoundVolume(percent);
executeUrl("PUT", SUNSET_ENDPOINT, data);
}
public RelaxData fetchRelaxData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", RELAX_ENDPOINT, RelaxData.class);
}
public void setRelaxVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
final RelaxData data = new RelaxData();
data.setSoundVolume(percent);
executeUrl("PUT", RELAX_ENDPOINT, data);
}
public void setRelaxLightIntensity(int percent) throws TimeoutException, InterruptedException, ExecutionException {
final RelaxData data = new RelaxData();
data.setLightIntensity(percent);
executeUrl("PUT", RELAX_ENDPOINT, data);
}
public void switchRelaxProgram(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
final RelaxData data = new RelaxData();
data.setState(state);
executeUrl("PUT", RELAX_ENDPOINT, data);
}
public void setRelaxBreathingRate(int value) throws TimeoutException, InterruptedException, ExecutionException {
final RelaxData data = new RelaxData();
data.setBreathingRate(value);
executeUrl("PUT", RELAX_ENDPOINT, data);
}
public void setRelaxDuration(int value) throws TimeoutException, InterruptedException, ExecutionException {
final RelaxData data = new RelaxData();
data.setDurationInMin(value);
executeUrl("PUT", RELAX_ENDPOINT, data);
}
public void setRelaxGuidanceType(int value) throws TimeoutException, InterruptedException, ExecutionException {
final RelaxData data = new RelaxData();
data.setGuidanceType(value);
executeUrl("PUT", RELAX_ENDPOINT, data);
}
public AudioData fetchAudioData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", AUDIO_ENDPOINT, AudioData.class);
}
public void switchRadio(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
final AudioData data = new AudioData();
if (state) {
data.enableRadio();
} else {
data.disableAudio();
}
executeUrl("PUT", AUDIO_ENDPOINT, data);
}
public void switchAux(boolean state) throws TimeoutException, InterruptedException, ExecutionException {
final AudioData data = new AudioData();
if (state) {
data.enableAux();
} else {
data.disableAudio();
}
executeUrl("PUT", AUDIO_ENDPOINT, data);
}
public void setAudioVolume(int percent) throws TimeoutException, InterruptedException, ExecutionException {
final AudioData data = new AudioData();
data.setVolume(percent);
executeUrl("PUT", AUDIO_ENDPOINT, data);
}
public void setRadioChannel(String preset) throws TimeoutException, InterruptedException, ExecutionException {
final AudioData data = new AudioData();
data.enableRadio();
data.setRadioPreset(preset);
executeUrl("PUT", AUDIO_ENDPOINT, data);
}
public RadioData getRadioData() throws TimeoutException, InterruptedException, ExecutionException {
RadioData data = new RadioData();
int loops = 0;
do {
if (loops > 20) {
break;
}
if (loops > 0) {
loops++;
Thread.sleep(250);
}
data = executeUrl("GET", RADIO_ENDPOINT, RadioData.class);
} while (data.isSeeking()); // Wait until seek is finished
return data;
}
public void radioSeekUp() throws TimeoutException, InterruptedException, ExecutionException {
final RadioData data = new RadioData();
data.setCmdSeekUp();
executeUrl("PUT", RADIO_ENDPOINT, data);
}
public void radioSeekDown() throws TimeoutException, InterruptedException, ExecutionException {
final RadioData data = new RadioData();
data.setCmdSeekDown();
executeUrl("PUT", RADIO_ENDPOINT, data);
}
public DeviceData fetchDeviceData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", DEVICE_ENDPOINT, DeviceData.class);
}
public WifiData fetchWifiData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", WIFI_ENDPOINT, WifiData.class);
}
public FirmwareData fetchFirmwareData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", FIRMWARE_ENDPOINT, FirmwareData.class);
}
public TimerData fetchTimerData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", TIMER_ENDPOINT, TimerData.class);
}
public PresetData fetchPresetData() throws TimeoutException, InterruptedException, ExecutionException {
return executeUrl("GET", PRESET_ENDPOINT, PresetData.class);
}
private <T> T executeUrl(String httpMethod, String endpoint, Class<T> classOfT)
throws TimeoutException, InterruptedException, ExecutionException {
final String responseBody = executeUrl("GET", endpoint, (String) null);
final T data = gson.fromJson(responseBody, classOfT);
return data;
}
private void executeUrl(String httpMethod, String endpoint, Object data)
throws TimeoutException, InterruptedException, ExecutionException {
final String content = gson.toJson(data);
executeUrl(httpMethod, endpoint, content);
}
/**
* Executes the given <code>url</code> with the given <code>httpMethod</code>
*
* @param httpMethod the HTTP method to use
* @param endpoint the url endpoint
* @param content the content to be sent to the given <code>url</code> or
* <code>null</code> if no content should be sent.
* @return
* @throws ExecutionException
* @throws InterruptedException
* @throws UnsupportedEncodingException
* @throws Exception when the request execution failed, timed out or it was interrupted
*/
private String executeUrl(String httpMethod, String endpoint, @Nullable String content)
throws TimeoutException, InterruptedException, ExecutionException {
final String url = urlBase + endpoint;
final HttpMethod method = HttpUtil.createHttpMethod(httpMethod);
final Request request = httpClient.newRequest(url).method(method).timeout(REQUEST_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
if (content != null && (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method))) {
final StringContentProvider stringContentProvider = new StringContentProvider(content,
StandardCharsets.UTF_8);
request.content(stringContentProvider, DEFAULT_CONTENT_TYPE);
logger.trace("Request for url '{}':\r\n{}", url, content);
} else {
logger.trace("Request for url '{}'", url);
}
final ContentResponse response = request.send();
final int statusCode = response.getStatus();
if (logger.isDebugEnabled() && statusCode >= HttpStatus.BAD_REQUEST_400) {
String statusLine = statusCode + " " + response.getReason();
logger.debug("Method failed: {}", statusLine);
}
final String encoding = response.getEncoding() != null ? response.getEncoding().replaceAll("\"", "").trim()
: StandardCharsets.UTF_8.name();
try {
String responseBody = new String(response.getContent(), encoding);
logger.trace("Response for url '{}':\r\n{}", url, responseBody);
return responseBody;
} catch (UnsupportedEncodingException e) {
logger.warn("Get response content failed!", e);
return "";
}
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic channel state description provider.
*
* @author Michael Myrcik - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, SomneoPresetStateDescriptionProvider.class })
@NonNullByDefault
public class SomneoPresetStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
@Activate
public SomneoPresetStateDescriptionProvider(final @Reference EventPublisher eventPublisher,
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.eventPublisher = eventPublisher;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@ -0,0 +1,125 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the audio state from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class AudioData {
private static final String SOURCE_RADIO = "fmr";
private static final String SOURCE_AUX = "aux";
private static final String SOURCE_OFF = "off";
@SerializedName("onoff")
private @Nullable Boolean power;
/**
* Must be set to false when the audio is turned on, otherwise a light that is
* turned on will be turned off.
*/
@SuppressWarnings("unused")
@SerializedName("tempy")
private @Nullable Boolean previewLight;
/**
* Volume range from 0 to 25.
*/
@SerializedName("sdvol")
private @Nullable Integer volume;
/**
* Current active audio source. Can be radio, aux or off.
*/
@SerializedName("snddv")
private @Nullable String source;
/**
* Current active radio preset.
*/
@SerializedName("sndch")
private @Nullable String preset;
public void disableAudio() {
power = false;
source = SOURCE_OFF;
}
public void enableRadio() {
power = true;
source = SOURCE_RADIO;
previewLight = false;
}
public State getRadioState() {
final Boolean power = this.power;
if (power == null) {
return UnDefType.NULL;
}
return power && SOURCE_RADIO.equals(source) ? PlayPauseType.PLAY : PlayPauseType.PAUSE;
}
public void enableAux() {
power = true;
source = SOURCE_AUX;
previewLight = false;
}
public State getAuxState() {
final Boolean power = this.power;
if (power == null) {
return UnDefType.NULL;
}
return OnOffType.from(power && SOURCE_AUX.equals(source));
}
public void setVolume(int percent) {
this.volume = percent / 4;
}
public State getVolumeState() {
final Integer volume = this.volume;
if (volume == null) {
return UnDefType.NULL;
}
return new PercentType(volume * 4);
}
public void setRadioPreset(String preset) {
this.preset = preset;
}
public State getPresetState() {
final String preset = this.preset;
if (preset == null) {
return UnDefType.NULL;
}
return new StringType(preset);
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the device data from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class DeviceData {
@SerializedName("type")
private @Nullable String modelId;
@SerializedName("serial")
private @Nullable String serial;
public @Nullable String getModelId() {
return modelId;
}
public @Nullable String getSerial() {
return serial;
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the firmware data from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class FirmwareData {
@SerializedName("version")
private @Nullable String version;
public @Nullable String getVersion() {
return version;
}
}

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the light state from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class LightData {
/**
* Brightness range from 0 to 25.
*/
@SerializedName("ltlvl")
private @Nullable Integer mainLightLevel;
@SerializedName("onoff")
private @Nullable Boolean mainLight;
@SuppressWarnings("unused")
@SerializedName("tempy")
private @Nullable Boolean previewLight;
@SerializedName("ngtlt")
private @Nullable Boolean nightLight;
public int getMainLightLevel() {
final Integer mainLightLevel = this.mainLightLevel;
if (mainLightLevel == null) {
return 0;
}
return mainLightLevel * 4;
}
public void setMainLightLevel(int mainLightLevel) {
this.mainLightLevel = mainLightLevel / 4;
}
public State getMainLightState() {
final Boolean mainLight = this.mainLight;
final Integer mainLightLevel = this.mainLightLevel;
if (mainLight == null) {
return UnDefType.NULL;
}
if (mainLightLevel == null) {
return UnDefType.NULL;
}
if (mainLight) {
return new PercentType(mainLightLevel * 4);
}
return OnOffType.OFF;
}
public void setMainLight(boolean mainLight) {
this.mainLight = mainLight;
}
public void setPreviewLight(boolean previewLight) {
this.previewLight = previewLight;
}
public State getNightLightState() {
final Boolean nightLight = this.nightLight;
if (nightLight == null) {
return UnDefType.NULL;
}
return OnOffType.from(nightLight);
}
public void setNightLight(@Nullable Boolean nightLight) {
this.nightLight = nightLight;
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.StateOption;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the preset state from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class PresetData {
private static final String LABEL_TEMPLATE = "%s fm";
private static final String LABEL_EMPTY_TEMPLATE = "FM %s";
@SerializedName("1")
private @Nullable String preset1;
@SerializedName("2")
private @Nullable String preset2;
@SerializedName("3")
private @Nullable String preset3;
@SerializedName("4")
private @Nullable String preset4;
@SerializedName("5")
private @Nullable String preset5;
public List<StateOption> createPresetOptions() {
List<StateOption> stateOptions = new ArrayList<>();
stateOptions.add(createStateOption("1", preset1));
stateOptions.add(createStateOption("2", preset2));
stateOptions.add(createStateOption("3", preset3));
stateOptions.add(createStateOption("4", preset4));
stateOptions.add(createStateOption("5", preset5));
return stateOptions;
}
private static StateOption createStateOption(String index, @Nullable String preset) {
String label;
if (preset == null || "".equals(preset)) {
label = String.format(LABEL_EMPTY_TEMPLATE, index);
} else {
label = String.format(LABEL_TEMPLATE, preset);
}
return new StateOption(index, label);
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the radio state from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class RadioData {
private static final String LABEL_TEMPLATE = "%s fm";
private static final String CMD_SEEK_UP = "seekup";
private static final String CMD_SEEK_DOWN = "seekdown";
@SerializedName("fmfrq")
private @Nullable String frequency;
@SerializedName("fmcmd")
private @Nullable String command;
public State getFrequency() {
final String frequency = this.frequency;
if (frequency == null) {
return UnDefType.NULL;
}
return new StringType(String.format(LABEL_TEMPLATE, frequency));
}
public void setCmdSeekUp() {
this.command = CMD_SEEK_UP;
}
public void setCmdSeekDown() {
this.command = CMD_SEEK_DOWN;
}
public boolean isSeeking() {
return CMD_SEEK_UP.equals(command) || CMD_SEEK_DOWN.equals(command);
}
}

View File

@ -0,0 +1,128 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the relax program state from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class RelaxData {
@SerializedName("onoff")
private @Nullable Boolean state;
@SerializedName("progr")
private @Nullable Integer breathingRate;
@SerializedName("durat")
private @Nullable Integer durationInMin;
@SerializedName("rtype")
private @Nullable Integer guidanceType;
/**
* Brightness range from 0 to 25.
*/
@SerializedName("intny")
private @Nullable Integer lightIntensity;
/**
* Volume range from 0 to 25.
*/
@SerializedName("sndlv")
private @Nullable Integer soundVolume;
public State getSwitchState() {
final Boolean state = this.state;
if (state == null) {
return UnDefType.NULL;
}
return OnOffType.from(state);
}
public void setState(boolean state) {
this.state = state;
}
public State getBreathingRate() {
final Integer breathingRate = this.breathingRate;
if (breathingRate == null) {
return UnDefType.NULL;
}
return new DecimalType(breathingRate);
}
public void setBreathingRate(int breathingRate) {
this.breathingRate = breathingRate;
}
public State getDurationInMin() {
final Integer durationInMin = this.durationInMin;
if (durationInMin == null) {
return UnDefType.NULL;
}
return new DecimalType(durationInMin);
}
public void setDurationInMin(int durationInMin) {
this.durationInMin = durationInMin;
}
public State getGuidanceType() {
final Integer guidanceType = this.guidanceType;
if (guidanceType == null) {
return UnDefType.NULL;
}
return new DecimalType(guidanceType);
}
public void setGuidanceType(int guidanceType) {
this.guidanceType = guidanceType;
}
public State getLightIntensity() {
final Integer lightIntensity = this.lightIntensity;
if (lightIntensity == null) {
return UnDefType.NULL;
}
return new PercentType(lightIntensity * 4);
}
public void setLightIntensity(int percent) {
this.lightIntensity = percent / 4;
}
public State getSoundVolume() {
final Integer soundVolume = this.soundVolume;
if (soundVolume == null) {
return UnDefType.NULL;
}
return new PercentType(soundVolume * 4);
}
public void setSoundVolume(int percent) {
this.soundVolume = percent / 4;
}
}

View File

@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the sensor state from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class SensorData {
@SerializedName("mslux")
private float currentIlluminance;
@SerializedName("mstmp")
private float currentTemperature;
@SerializedName("msrhu")
private float currentHumidity;
@SerializedName("mssnd")
private int currentNoise;
public State getCurrentIlluminance() {
return new QuantityType<>(currentIlluminance, Units.LUX);
}
public State getCurrentTemperature() {
return new QuantityType<>(currentTemperature, SIUnits.CELSIUS);
}
public State getCurrentHumidity() {
return new QuantityType<>(currentHumidity, Units.PERCENT);
}
public State getCurrentNoise() {
return new QuantityType<>(currentNoise, Units.DECIBEL);
}
}

View File

@ -0,0 +1,135 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the sunset program state from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class SunsetData {
@SerializedName("onoff")
private @Nullable Boolean state;
/**
* Brightness range from 0 to 25.
*/
@SerializedName("curve")
private @Nullable Integer lightIntensity;
@SerializedName("durat")
private @Nullable Integer durationInMin;
@SerializedName("ctype")
private @Nullable Integer colorSchema;
@SerializedName("snddv")
private @Nullable String soundSource;
@SerializedName("sndch")
private @Nullable String ambientNoise;
/**
* Volume range from 0 to 25.
*/
@SerializedName("sndlv")
private @Nullable Integer soundVolume;
public State getSwitchState() {
final Boolean state = this.state;
if (state == null) {
return UnDefType.NULL;
}
return OnOffType.from(state);
}
public void setState(boolean state) {
this.state = state;
}
public State getLightIntensity() {
final Integer lightIntensity = this.lightIntensity;
if (lightIntensity == null) {
return UnDefType.NULL;
}
return new PercentType(lightIntensity * 4);
}
public void setLightIntensity(int percent) {
this.lightIntensity = percent / 4;
}
public State getDurationInMin() {
final Integer durationInMin = this.durationInMin;
if (durationInMin == null) {
return UnDefType.NULL;
}
return new DecimalType(durationInMin);
}
public void setDurationInMin(int durationInMin) {
this.durationInMin = durationInMin;
}
public State getColorSchema() {
final Integer colorSchema = this.colorSchema;
if (colorSchema == null) {
return UnDefType.NULL;
}
return new DecimalType(colorSchema);
}
public void setColorSchema(int colorSchema) {
this.colorSchema = colorSchema;
}
public State getAmbientNoise() {
final String soundSource = this.soundSource;
if (soundSource == null) {
return UnDefType.NULL;
}
final String suffix = "off".equals(soundSource) ? "" : "-" + ambientNoise;
return new StringType(soundSource + suffix);
}
public void setAmbientNoise(String option) {
final String[] values = option.split("-");
soundSource = values[0];
ambientNoise = values.length == 1 ? "" : values[1];
}
public State getSoundVolume() {
final Integer soundVolume = this.soundVolume;
if (soundVolume == null) {
return UnDefType.NULL;
}
return new PercentType(soundVolume * 4);
}
public void setSoundVolume(int percent) {
this.soundVolume = percent / 4;
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the program timer state from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class TimerData {
@SerializedName("rlxmn")
private int relaxMinutes;
@SerializedName("rlxsc")
private int relaxSeconds;
@SerializedName("dskmn")
private int sunsetMinutes;
@SerializedName("dsksc")
private int sunsetSeconds;
public int remainingTimeRelax() {
return relaxMinutes * 60 + relaxSeconds;
}
public int remainingTimeSunset() {
return sunsetMinutes * 60 + sunsetSeconds;
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somneo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* This class represents the wifi data from the API.
*
* @author Michael Myrcik - Initial contribution
*/
@NonNullByDefault
public class WifiData {
@SerializedName("macaddress")
private @Nullable String macAddress;
public @Nullable String getMacAddress() {
return macAddress;
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="somneo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Somneo Binding</name>
<description>This is the binding for Philips Somneo devices.</description>
</binding:binding>

View File

@ -0,0 +1,108 @@
# binding
binding.somneo.name = Somneo Binding
binding.somneo.description = This is the binding for Philips Somneo devices.
# thing types
thing-type.somneo.hf367x.label = Philips Somneo Light
thing-type.somneo.hf367x.description = A smart sleep and wake-up light with sensors.
# thing type config description
thing-type.config.somneo.hf367x.hostname.label = Hostname
thing-type.config.somneo.hf367x.hostname.description = Hostname or IP address of the device.
thing-type.config.somneo.hf367x.ignoreSSLErrors.label = Ignore SSL Errors
thing-type.config.somneo.hf367x.ignoreSSLErrors.description = If set to true ignores invalid SSL certificate errors. This is potentially dangerous.
thing-type.config.somneo.hf367x.port.label = HTTP Port
thing-type.config.somneo.hf367x.port.description = HTTP Port used for communication. Normally shouldn't be changed.
thing-type.config.somneo.hf367x.refreshInterval.label = Refresh Interval
thing-type.config.somneo.hf367x.refreshInterval.description = Interval the device is polled in sec.
# channel group types
channel-group-type.somneo.audio.label = Audio Player
channel-group-type.somneo.audio.description = Channels to control the audio player.
channel-group-type.somneo.audio.channel.aux.label = AUX Input
channel-group-type.somneo.audio.channel.aux.description = Turn the AUX input on or off.
channel-group-type.somneo.audio.channel.radio.label = Radio Remote Control
channel-group-type.somneo.audio.channel.radio.description = Remote control for controlling the radio and seeking for a frequency.
channel-group-type.somneo.light.label = Light
channel-group-type.somneo.light.description = Different light channels.
channel-group-type.somneo.sensor.label = Sensor Data
channel-group-type.somneo.sensor.description = Data from the various sensors.
channel-group-type.somneo.relax.label = Relax Breathe
channel-group-type.somneo.relax.description = Light-guided breathing helps you relax for sleep.
channel-group-type.somneo.relax.channel.lightIntensity.label = Light Intensity
channel-group-type.somneo.relax.channel.lightIntensity.description = The channel allows to set the light intensity.
channel-group-type.somneo.relax.channel.switch.label = Relax Breathe Program
channel-group-type.somneo.relax.channel.switch.description = The switch channel allows to turn the relax breathe program on or off.
channel-group-type.somneo.relax.channel.volume.label = Volume
channel-group-type.somneo.relax.channel.volume.description = Set the volume of the relax breath program.
channel-group-type.somneo.sunset.label = Sunset
channel-group-type.somneo.sunset.description = Simulate a sunset with selectable lights and sounds.
channel-group-type.somneo.sunset.channel.lightIntensity.label = Light Intensity
channel-group-type.somneo.sunset.channel.lightIntensity.description = The channel allows to set the light intensity.
channel-group-type.somneo.sunset.channel.switch.label = Sunset Program
channel-group-type.somneo.sunset.channel.switch.description = The switch channel allows to turn the sunset program on or off.
channel-group-type.somneo.sunset.channel.volume.label = Volume
channel-group-type.somneo.sunset.channel.volume.description = Set the volume of the sunset program.
# channel types
channel-type.somneo.ambientNoise.label = Ambient Noise
channel-type.somneo.ambientNoise.description = Ambient noise played during the sunset.
channel-type.somneo.ambientNoise.state.option.off = No sound
channel-type.somneo.ambientNoise.state.option.dus-1 = Soft Rain
channel-type.somneo.ambientNoise.state.option.dus-2 = Ocean Waves
channel-type.somneo.ambientNoise.state.option.dus-3 = Under Water
channel-type.somneo.ambientNoise.state.option.dus-4 = Summer Lake
channel-type.somneo.ambientNoise.state.option.fmr-1 = FM 1
channel-type.somneo.ambientNoise.state.option.fmr-2 = FM 2
channel-type.somneo.ambientNoise.state.option.fmr-3 = FM 3
channel-type.somneo.ambientNoise.state.option.fmr-4 = FM 4
channel-type.somneo.ambientNoise.state.option.fmr-5 = FM 5
channel-type.somneo.breathingRate.label = Breathing Rate
channel-type.somneo.breathingRate.description = Breathing rate per minute.
channel-type.somneo.breathingRate.state.option.1 = 4 BR/min
channel-type.somneo.breathingRate.state.option.2 = 5 BR/min
channel-type.somneo.breathingRate.state.option.3 = 6 BR/min
channel-type.somneo.breathingRate.state.option.4 = 7 BR/min
channel-type.somneo.breathingRate.state.option.5 = 8 BR/min
channel-type.somneo.breathingRate.state.option.6 = 9 BR/min
channel-type.somneo.breathingRate.state.option.7 = 10 BR/min
channel-type.somneo.colorSchema.label = Color Schema
channel-type.somneo.colorSchema.description = Choose a personal sunset.
channel-type.somneo.colorSchema.state.option.0 = Sunny day
channel-type.somneo.colorSchema.state.option.1 = Island red
channel-type.somneo.colorSchema.state.option.2 = Nordic white
channel-type.somneo.colorSchema.state.option.3 = Caribbean red
channel-type.somneo.frequency.label = Frequency
channel-type.somneo.frequency.description = The currently selected radio frequency.
channel-type.somneo.guidanceType.label = Breath Guidance Type
channel-type.somneo.guidanceType.description = Select a breath guidance type.
channel-type.somneo.guidanceType.state.option.0 = Light
channel-type.somneo.guidanceType.state.option.1 = Sound
channel-type.somneo.humidity.label = Humidity
channel-type.somneo.humidity.description = The current humidity in %.
channel-type.somneo.illuminance.label = Illuminance
channel-type.somneo.illuminance.description = The current illuminance in lux.
channel-type.somneo.light.label = Night Light
channel-type.somneo.light.description = The light switch channel allows to turn the night light on or off.
channel-type.somneo.noise.label = Noise
channel-type.somneo.noise.description = The current noise in dB.
channel-type.somneo.preset.label = FM Preset
channel-type.somneo.preset.description = The Device has 5 presets to store radio frequencies.
channel-type.somneo.relaxDuration.label = Duration
channel-type.somneo.relaxDuration.description = The duration of relax breathe program in minutes.
channel-type.somneo.relaxDuration.state.option.5 = 5 Minutes
channel-type.somneo.relaxDuration.state.option.10 = 10 Minutes
channel-type.somneo.relaxDuration.state.option.15 = 15 Minutes
channel-type.somneo.remainingTime.label = Remaining Time
channel-type.somneo.remainingTime.description = Remaining time from an activated program.
channel-type.somneo.sunsetDuration.label = Duration
channel-type.somneo.sunsetDuration.description = The duration of sunset program in minutes.
channel-type.somneo.sunsetDuration.state.option.5 = 5 Minutes
channel-type.somneo.sunsetDuration.state.option.10 = 10 Minutes
channel-type.somneo.sunsetDuration.state.option.15 = 15 Minutes
channel-type.somneo.sunsetDuration.state.option.20 = 20 Minutes
channel-type.somneo.sunsetDuration.state.option.30 = 30 Minutes
channel-type.somneo.sunsetDuration.state.option.45 = 45 Minutes
channel-type.somneo.sunsetDuration.state.option.60 = 60 Minutes
channel-type.somneo.temperature.label = Temperature
channel-type.somneo.temperature.description = The current temperature in °C.

View File

@ -0,0 +1,108 @@
# binding
binding.somneo.name = Somneo Binding
binding.somneo.description = Dieses Binding integriert Philips Somneo Geräte.
# thing types
thing-type.somneo.hf367x.label = Philips Somneo Light
thing-type.somneo.hf367x.description = Ein intelligentes Schlaf- und Wecklicht mit Sensoren.
# thing type config description
thing-type.config.somneo.hf367x.hostname.label = Hostname
thing-type.config.somneo.hf367x.hostname.description = Hostname oder IP-Adresse des Geräts.
thing-type.config.somneo.hf367x.ignoreSSLErrors.label = SSL-Fehler ignorieren
thing-type.config.somneo.hf367x.ignoreSSLErrors.description = Bei der Einstellung true werden Fehler bei ungültigen SSL-Zertifikaten ignoriert. Dies ist potenziell gefährlich.
thing-type.config.somneo.hf367x.port.label = HTTP-Port
thing-type.config.somneo.hf367x.port.description = HTTP-Port für Kommunikation. Sollte normalerweise nicht geändert werden.
thing-type.config.somneo.hf367x.refreshInterval.label = Aktualisierungsintervall
thing-type.config.somneo.hf367x.refreshInterval.description = Intervall in Sekunden, in dem das Gerät abgefragt wird.
# channel group types
channel-group-type.somneo.audio.label = Audioplayer
channel-group-type.somneo.audio.description = Kanäle zur Steuerung des Audioplayers.
channel-group-type.somneo.audio.channel.aux.label = AUX-Eingang
channel-group-type.somneo.audio.channel.aux.description = Schaltet den AUX-Eingang ein oder aus.
channel-group-type.somneo.audio.channel.radio.label = Radio Fernbedienung
channel-group-type.somneo.audio.channel.radio.description = Fernbedienung zur Steuerung des Radios und zur Frequenzsuche.
channel-group-type.somneo.light.label = Licht
channel-group-type.somneo.light.description = Verschiedene Lichtkanäle.
channel-group-type.somneo.sensor.label = Sensor-Daten
channel-group-type.somneo.sensor.description = Daten von den verschiedenen Sensoren.
channel-group-type.somneo.relax.label = Relax Breathe
channel-group-type.somneo.relax.description = Pulsierende Lichteffekte entspannen die Atmung und unterstützen das Einschlafen.
channel-group-type.somneo.relax.channel.lightIntensity.label = Lichtintensität
channel-group-type.somneo.relax.channel.lightIntensity.description = Der Kanal ermöglicht die Einstellung der Lichtintensität.
channel-group-type.somneo.relax.channel.switch.label = Relax Breathe Programm
channel-group-type.somneo.relax.channel.switch.description = Mit dem Schaltkanal kann das Relax Breathe Programm ein- oder ausschalten werden.
channel-group-type.somneo.relax.channel.volume.label = Lautstärke
channel-group-type.somneo.relax.channel.volume.description = Stellen Sie die Lautstärke des Relax Breathe Programm ein.
channel-group-type.somneo.sunset.label = Sonnenuntergang
channel-group-type.somneo.sunset.description = Simulieren einen Sonnenuntergang mit wählbaren Lichtern und Geräuschen.
channel-group-type.somneo.sunset.channel.lightIntensity.label = Lichtintensität
channel-group-type.somneo.sunset.channel.lightIntensity.description = Der Kanal ermöglicht die Einstellung der Lichtintensität.
channel-group-type.somneo.sunset.channel.switch.label = Sonnenuntergang Programm
channel-group-type.somneo.sunset.channel.switch.description = Mit dem Schaltkanal kann das Sonnenuntergangsprogramm ein- oder ausschalten werden.
channel-group-type.somneo.sunset.channel.volume.label = Lautstärke
channel-group-type.somneo.sunset.channel.volume.description = Stellen Sie die Lautstärke des Sonnenuntergangsprogramms ein.
# channel types
channel-type.somneo.ambientNoise.label = Umgebungsgeräusche
channel-type.somneo.ambientNoise.description = Umgebungsgeräusche, die während des Sonnenuntergangs abgespielt werden.
channel-type.somneo.ambientNoise.state.option.off = Kein Geräusch
channel-type.somneo.ambientNoise.state.option.dus-1 = Soft Rain
channel-type.somneo.ambientNoise.state.option.dus-2 = Ocean Waves
channel-type.somneo.ambientNoise.state.option.dus-3 = Under Water
channel-type.somneo.ambientNoise.state.option.dus-4 = Summer Lake
channel-type.somneo.ambientNoise.state.option.fmr-1 = FM 1
channel-type.somneo.ambientNoise.state.option.fmr-2 = FM 2
channel-type.somneo.ambientNoise.state.option.fmr-3 = FM 3
channel-type.somneo.ambientNoise.state.option.fmr-4 = FM 4
channel-type.somneo.ambientNoise.state.option.fmr-5 = FM 5
channel-type.somneo.breathingRate.label = Atemzüge
channel-type.somneo.breathingRate.description = Atemzüge pro Minute.
channel-type.somneo.breathingRate.state.option.1 = 4 AZ/min
channel-type.somneo.breathingRate.state.option.2 = 5 AZ/min
channel-type.somneo.breathingRate.state.option.3 = 6 AZ/min
channel-type.somneo.breathingRate.state.option.4 = 7 AZ/min
channel-type.somneo.breathingRate.state.option.5 = 8 AZ/min
channel-type.somneo.breathingRate.state.option.6 = 9 AZ/min
channel-type.somneo.breathingRate.state.option.7 = 10 AZ/min
channel-type.somneo.colorSchema.label = Farbschema
channel-type.somneo.colorSchema.description = Wählen einen persönlichen Sonnenuntergang.
channel-type.somneo.colorSchema.state.option.0 = Sunny day
channel-type.somneo.colorSchema.state.option.1 = Island red
channel-type.somneo.colorSchema.state.option.2 = Nordic white
channel-type.somneo.colorSchema.state.option.3 = Caribbean red
channel-type.somneo.frequency.label = Frequenz
channel-type.somneo.frequency.description = Die aktuell gewählte Frequenz.
channel-type.somneo.guidanceType.label = Atemzugführung Art
channel-type.somneo.guidanceType.description = Wähle einen Art der Atemzuführung aus.
channel-type.somneo.guidanceType.state.option.0 = Licht
channel-type.somneo.guidanceType.state.option.1 = Sound
channel-type.somneo.humidity.label = Luftfeuchtigkeit
channel-type.somneo.humidity.description = Die aktuelle Luftfeuchtigkeit in %.
channel-type.somneo.illuminance.label = Beleuchtungsstärke
channel-type.somneo.illuminance.description = Die aktuelle Beleuchtungsstärke in Lux.
channel-type.somneo.light.label = Nachtlicht
channel-type.somneo.light.description = Mit dem Lichtschalterkanal kann das Nachtlicht ein- und ausgeschaltet werden.
channel-type.somneo.noise.label = Lärm
channel-type.somneo.noise.description = Der aktuelle Lärm in dB.
channel-type.somneo.preset.label = FM-Voreinstellung
channel-type.somneo.preset.description = Das Gerät verfügt über 5 Voreinstellung zum Speichern von Frequenzen.
channel-type.somneo.relaxDuration.label = Dauer
channel-type.somneo.relaxDuration.description = Die Dauer des Relax Breathe Programm in Minuten.
channel-type.somneo.relaxDuration.state.option.5 = 5 Minuten
channel-type.somneo.relaxDuration.state.option.10 = 10 Minuten
channel-type.somneo.relaxDuration.state.option.15 = 15 Minuten
channel-type.somneo.remainingTime.label = Verbleibende Zeit
channel-type.somneo.remainingTime.description = Verbleibende Zeit eines aktivierten Programms.
channel-type.somneo.sunsetDuration.label = Dauer
channel-type.somneo.sunsetDuration.description = Die Dauer des Sonnenuntergangsprogramms in Minuten.
channel-type.somneo.sunsetDuration.state.option.5 = 5 Minuten
channel-type.somneo.sunsetDuration.state.option.10 = 10 Minuten
channel-type.somneo.sunsetDuration.state.option.15 = 15 Minuten
channel-type.somneo.sunsetDuration.state.option.20 = 20 Minuten
channel-type.somneo.sunsetDuration.state.option.30 = 30 Minuten
channel-type.somneo.sunsetDuration.state.option.45 = 45 Minuten
channel-type.somneo.sunsetDuration.state.option.60 = 60 Minuten
channel-type.somneo.temperature.label = Temperatur
channel-type.somneo.temperature.description = Die aktuelle Temperatur in °C.

View File

@ -0,0 +1,338 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="somneo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="hf367x">
<label>Philips Somneo Light</label>
<description>A smart sleep and wake-up light with sensors.</description>
<channel-groups>
<channel-group id="sensor" typeId="sensor"/>
<channel-group id="light" typeId="light"/>
<channel-group id="sunset" typeId="sunset"/>
<channel-group id="relax" typeId="relax"/>
<channel-group id="audio" typeId="audio"/>
</channel-groups>
<config-description>
<parameter name="hostname" type="text" required="true">
<context>network-address</context>
<label>Hostname</label>
<description>Hostname or IP address of the device.</description>
</parameter>
<parameter name="port" type="integer">
<label>HTTP Port</label>
<description>HTTP Port used for communication. Normally shouldn't be changed.</description>
<default>443</default>
<advanced>true</advanced>
</parameter>
<parameter name="refreshInterval" type="integer" unit="s" min="1">
<label>Refresh Interval</label>
<description>Interval the device is polled in sec.</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="ignoreSSLErrors" type="boolean">
<label>Ignore SSL Errors</label>
<description>If set to true ignores invalid SSL certificate errors. This is potentially dangerous.</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="audio">
<label>Audio Player</label>
<description>Channels to control the audio player.</description>
<category>Player</category>
<channels>
<channel id="radio" typeId="system.media-control">
<label>Radio Remote Control</label>
<description>Remote control for controlling the radio and seeking for a frequency.</description>
</channel>
<channel id="aux" typeId="system.power">
<label>AUX Input</label>
<description>Turn the AUX input on or off.</description>
</channel>
<channel id="volume" typeId="system.volume"/>
<channel id="preset" typeId="preset"/>
<channel id="frequency" typeId="frequency"></channel>
</channels>
</channel-group-type>
<channel-group-type id="light">
<label>Light</label>
<description>Different light channels.</description>
<category>Light</category>
<channels>
<channel id="main" typeId="system.brightness"/>
<channel id="night" typeId="light"/>
</channels>
</channel-group-type>
<channel-group-type id="sensor">
<label>Sensor Data</label>
<description>Data from the various sensors.</description>
<category>Sensor</category>
<channels>
<channel id="illuminance" typeId="illuminance"/>
<channel id="temperature" typeId="temperature"/>
<channel id="humidity" typeId="humidity"/>
<channel id="noise" typeId="noise"/>
</channels>
</channel-group-type>
<channel-group-type id="relax">
<label>Relax Breathe</label>
<description>Light-guided breathing helps you relax for sleep.</description>
<channels>
<channel id="switch" typeId="system.power">
<label>Relax Breathe Program</label>
<description>The switch channel allows to turn the relax breathe program on or off.</description>
</channel>
<channel id="remainingTime" typeId="remainingTime"/>
<channel id="breathingRate" typeId="breathingRate"/>
<channel id="duration" typeId="relaxDuration"/>
<channel id="guidanceType" typeId="guidanceType"/>
<channel id="lightIntensity" typeId="system.brightness">
<label>Light Intensity</label>
<description>The channel allows to set the light intensity.</description>
</channel>
<channel id="volume" typeId="system.volume"/>
</channels>
</channel-group-type>
<channel-group-type id="sunset">
<label>Sunset</label>
<description>Simulate a sunset with selectable lights and sounds.</description>
<category>Sunset</category>
<channels>
<channel id="switch" typeId="system.power">
<label>Sunset Program</label>
<description>The switch channel allows to turn the sunset program on or off.</description>
</channel>
<channel id="remainingTime" typeId="remainingTime"/>
<channel id="lightIntensity" typeId="system.brightness">
<label>Light Intensity</label>
<description>The channel allows to set the light intensity.</description>
</channel>
<channel id="duration" typeId="sunsetDuration"/>
<channel id="colorSchema" typeId="colorSchema"/>
<channel id="ambientNoise" typeId="ambientNoise"/>
<channel id="volume" typeId="system.volume"/>
</channels>
</channel-group-type>
<channel-type id="ambientNoise">
<item-type>String</item-type>
<label>Ambient Noise</label>
<description>Ambient noise played during the sunset.</description>
<category>Noise</category>
<tags>
<tag>Control</tag>
<tag>Noise</tag>
</tags>
<state>
<options>
<option value="off">No sound</option>
<option value="dus-1">Soft Rain</option>
<option value="dus-2">Ocean Waves</option>
<option value="dus-3">Under Water</option>
<option value="dus-4">Summer Lake</option>
<option value="fmr-1">FM 1</option>
<option value="fmr-2">FM 2</option>
<option value="fmr-3">FM 3</option>
<option value="fmr-4">FM 4</option>
<option value="fmr-5">FM 5</option>
</options>
</state>
</channel-type>
<channel-type id="breathingRate">
<item-type>Number</item-type>
<label>Breathing Rate</label>
<description>Breathing rate per minute.</description>
<tags>
<tag>Control</tag>
</tags>
<state>
<options>
<option value="1">4 BR/min</option>
<option value="2">5 BR/min</option>
<option value="3">6 BR/min</option>
<option value="4">7 BR/min</option>
<option value="5">8 BR/min</option>
<option value="6">9 BR/min</option>
<option value="7">10 BR/min</option>
</options>
</state>
</channel-type>
<channel-type id="colorSchema">
<item-type>Number</item-type>
<label>Color Schema</label>
<description>Choose a personal sunset.</description>
<category>Sunset</category>
<tags>
<tag>Control</tag>
<tag>ColorTemperature</tag>
</tags>
<state>
<options>
<option value="0">Sunny day</option>
<option value="1">Island red</option>
<option value="2">Nordic white</option>
<option value="3">Caribbean red</option>
</options>
</state>
</channel-type>
<channel-type id="frequency">
<item-type>String</item-type>
<label>Frequency</label>
<description>The currently selected radio frequency.</description>
<tags>
<tag>Status</tag>
</tags>
<state readOnly="true"/>
</channel-type>
<channel-type id="guidanceType">
<item-type>Number</item-type>
<label>Breath Guidance Type</label>
<description>Select a breath guidance type.</description>
<tags>
<tag>Control</tag>
</tags>
<state>
<options>
<option value="0">Light</option>
<option value="1">Sound</option>
</options>
</state>
</channel-type>
<channel-type id="humidity">
<item-type>Number:Dimensionless</item-type>
<label>Humidity</label>
<description>The current humidity in %.</description>
<category>Humidity</category>
<tags>
<tag>Measurement</tag>
<tag>Humidity</tag>
</tags>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="illuminance">
<item-type>Number:Illuminance</item-type>
<label>Illuminance</label>
<description>The current illuminance in lux.</description>
<category>Sun</category>
<tags>
<tag>Measurement</tag>
<tag>Light</tag>
</tags>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="light">
<item-type>Switch</item-type>
<label>Night Light</label>
<description>The light switch channel allows to turn the night light on or off.</description>
<category>Light</category>
<tags>
<tag>Control</tag>
<tag>Light</tag>
</tags>
</channel-type>
<channel-type id="noise">
<item-type>Number:Dimensionless</item-type>
<label>Noise</label>
<description>The current noise in dB.</description>
<category>Noise</category>
<tags>
<tag>Measurement</tag>
<tag>Noise</tag>
</tags>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="preset">
<item-type>String</item-type>
<label>FM Preset</label>
<description>The Device has 5 presets to store radio frequencies.</description>
<tags>
<tag>Control</tag>
</tags>
<state/>
</channel-type>
<channel-type id="relaxDuration">
<item-type>Number:Time</item-type>
<label>Duration</label>
<description>The duration of relax breathe program in minutes.</description>
<category>Time</category>
<tags>
<tag>Control</tag>
<tag>Duration</tag>
</tags>
<state>
<options>
<option value="5">5 Minutes</option>
<option value="10">10 Minutes</option>
<option value="15">15 Minutes</option>
</options>
</state>
</channel-type>
<channel-type id="remainingTime">
<item-type>Number:Time</item-type>
<label>Remaining Time</label>
<description>Remaining time from an activated program.</description>
<category>Time</category>
<tags>
<tag>Status</tag>
<tag>Duration</tag>
</tags>
<state pattern="%1$tM:%1$tS min" readOnly="true"/>
</channel-type>
<channel-type id="sunsetDuration">
<item-type>Number:Time</item-type>
<label>Duration</label>
<description>The duration of sunset program in minutes.</description>
<category>Time</category>
<tags>
<tag>Control</tag>
<tag>Duration</tag>
</tags>
<state>
<options>
<option value="5">5 Minutes</option>
<option value="10">10 Minutes</option>
<option value="15">15 Minutes</option>
<option value="20">20 Minutes</option>
<option value="30">30 Minutes</option>
<option value="45">45 Minutes</option>
<option value="60">60 Minutes</option>
</options>
</state>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>The current temperature in °C.</description>
<category>Temperature</category>
<tags>
<tag>Measurement</tag>
<tag>Temperature</tag>
</tags>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -336,6 +336,7 @@
<module>org.openhab.binding.solarwatt</module>
<module>org.openhab.binding.somfymylink</module>
<module>org.openhab.binding.somfytahoma</module>
<module>org.openhab.binding.somneo</module>
<module>org.openhab.binding.sonnen</module>
<module>org.openhab.binding.sonos</module>
<module>org.openhab.binding.sonyaudio</module>