added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
38
bundles/org.openhab.binding.fsinternetradio/.classpath
Normal file
38
bundles/org.openhab.binding.fsinternetradio/.classpath
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.fsinternetradio/.project
Normal file
23
bundles/org.openhab.binding.fsinternetradio/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.fsinternetradio</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.binding.fsinternetradio/NOTICE
Normal file
13
bundles/org.openhab.binding.fsinternetradio/NOTICE
Normal 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
|
||||
116
bundles/org.openhab.binding.fsinternetradio/README.md
Normal file
116
bundles/org.openhab.binding.fsinternetradio/README.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# FS Internet Radio Binding
|
||||
|
||||
This binding integrates internet radios based on the [Frontier Silicon chipset](https://www.frontier-silicon.com/).
|
||||
|
||||
## Supported Things
|
||||
|
||||
Successfully tested are internet radios:
|
||||
|
||||
* [Hama IR100](https://de.hama.com/00054823/hama-internetradio-ir110)
|
||||
* [Medion MD87180, MD86988, MD86955, MD87528](http://internetradio.medion.com/)
|
||||
* [Silvercrest SMRS18A1, SMRS30A1, SMRS35A1, SIRD 14 C2, SIRD 14 D1](https://www.silvercrest-multiroom.de/en/products/stereo-internet-radio/)
|
||||
* [Roberts Stream 83i and 93i](https://www.robertsradio.com/uk/products/radio/smart-radio/)
|
||||
* [Auna Connect 150, Auna KR200](https://www.auna.de/Radios/Internetradios/)
|
||||
* [TechniSat DIGITRADIO 350 IR and 850](https://www.technisat.com/en_XX/DAB+-Radios-with-Internetradio/352-10996/)
|
||||
* [TTMicro AS Pinell Supersound](https://www.ttmicro.no/radio)
|
||||
* [Revo SuperConnect](https://revo.co.uk/products/)
|
||||
* [Sangean WFR-28C](http://sg.sangean.com.tw/products/product_category.asp?cid=2)
|
||||
* [Roku SoundBridge M1001](https://soundbridge.roku.com/soundbridge/index.php)
|
||||
* [Dual IR 3a](https://www.dual.de/produkte/digitalradio/radio-station-ir-3a/)
|
||||
* [Teufel 3sixty](https://www.teufel.de/stereo/radio-3sixty-p16568.html)
|
||||
|
||||
But in principle, all internet radios based on the [Frontier Silicon chipset](https://www.frontier-silicon.com/) should be supported because they share the same API.
|
||||
So It is very likely that other internet radio models of the same manufacturers do also work.
|
||||
|
||||
## Community
|
||||
|
||||
For discussions and questions about supported radios, check out [this thread](https://community.openhab.org/t/internet-radio-i-need-your-help/2131).
|
||||
|
||||
## Discovery
|
||||
|
||||
The radios are discovered through UPnP in the local network.
|
||||
|
||||
If your radio is not discovered, please try to access its API via: `http://<radio-ip>/fsapi/CREATE_SESSION?pin=1234` (1234 is default pin, if you get a 403 error, check the radio menu for the correct pin).<br/>
|
||||
If you get a 404 error, maybe a different port than the standard port 80 is used by your radio; try scanning the open ports of your radio.<br/>
|
||||
If you get a result like `FS_OK 1902014387`, your radio is supported.
|
||||
|
||||
If this is the case, please [add your model to this documentation](https://github.com/openhab/openhab-addons/edit/master/bundles/org.openhab.binding.fsinternetradio/README.md) and/or provide discovery information in [this thread](https://community.openhab.org/t/internet-radio-i-need-your-help/2131).
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding itself does not need a configuration.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
Each radio must be configured via its ip address, port, pin, and a refresh rate.
|
||||
|
||||
* If the ip address is not discovered automatically, it must be manually set.
|
||||
* The default port is `80` which should work for most radios.
|
||||
* The default pin is `1234` for most radios, but if it does not work or if it was changed, look it up in the on-screen menu of the radio.
|
||||
* The default refresh rate for the radio items is `60` seconds; `0` disables periodic refresh.
|
||||
|
||||
## Channels
|
||||
|
||||
All devices support some of the following channels:
|
||||
|
||||
| Channel Type ID | Item Type | Description | Access |
|
||||
|-----------------|-----------|-------------|------- |
|
||||
| power | Switch | Switch the radio on or off | R/W |
|
||||
| volume-percent | Dimmer | Radio volume (min=0, max=100) | R/W |
|
||||
| volume-absolute | Number | Radio volume (min=0, max=32) | R/W |
|
||||
| mute | Switch | Mute the radio | R/W |
|
||||
| mode | Number | The radio mode, e.g. FM radio, internet radio, AUX, etc. (model-specific, see list below) | R/W |
|
||||
| preset | Number | Preset radio stations configured in the radio (write-only) | W |
|
||||
| play-info-name | String | The name of the current radio station or track | R |
|
||||
| play-info-text | String | Additional information e.g. of the current radio station | R |
|
||||
|
||||
The radio mode depends on the internet radio model (and its firmware version!).
|
||||
This list is just an example how the mapping looks like for some of the devices, please try it out and adjust your sitemap for your particular radio.
|
||||
|
||||
| Radio Mode | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
|
||||
|--------------------------|----------------|-------------------------|-----------|--------------|-----------|----------|--------------|--------------|-----------|-----------|--------|
|
||||
| Hama IR110 | Internet Radio | Spotify | Player | AUX in | - | - | - | - | - | - |- |
|
||||
| Medion MD87180 | Internet Radio | Music Player (USB, LAN) | DAB Radio | FM Radio | AUX in | - | - | - | - | - |- |
|
||||
| Medion MD 86988 | Internet Radio | Music Player | FM Radio | AUX in | - | - | - | - | - | - |- |
|
||||
| Technisat DigitRadio 580 | Internet Radio | Spotify | - | Music Player | DAB Radio | FM Radio | AUX in | CD | Bluetooth | - |- |
|
||||
| Dual IR 3a | Internet Radio | Spotify | - | Music Player | DAB Radio | FM Radio | Bluetooth | - | - | - |- |
|
||||
| Silvercrest SIRD 14 C1 | - | Napster | Deezer | Qobuz | Spotify | TIDAL | Spotify | Music Player | DAB Radio | FM Radio | AUX in |
|
||||
| Silvercrest SIRD 14 C2 | Internet Radio | TIDAL | Deezer | Qobuz | Spotify | - | Music Player | DAB Radio | FM Radio | AUX in |- |
|
||||
| Auna KR200 Kitchen Radio | Internet Radio | Spotify | - | Music Player | DAB Radio | FM Radio | AUX in | - | - | - |- |
|
||||
|
||||
## Full Example
|
||||
|
||||
demo.things:
|
||||
|
||||
```
|
||||
fsinternetradio:radio:radioInKitchen [ ip="192.168.0.42" ]
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```
|
||||
Switch RadioPower "Radio Power" { channel="fsinternetradio:radio:radioInKitchen:power" }
|
||||
Switch RadioMute "Radio Mute" { channel="fsinternetradio:radio:radioInKitchen:mute" }
|
||||
Dimmer RadioVolume "Radio Volume" { channel="fsinternetradio:radio:radioInKitchen:volume-percent" }
|
||||
Number RadioMode "Radio Mode" { channel="fsinternetradio:radio:radioInKitchen:mode" }
|
||||
Number RadioPreset "Radio Stations" { channel="fsinternetradio:radio:radioInKitchen:preset" }
|
||||
String RadioInfo1 "Radio Info1" { channel="fsinternetradio:radio:radioInKitchen:play-info-name" }
|
||||
String RadioInfo2 "Radio Info2" { channel="fsinternetradio:radio:radioInKitchen:play-info-text" }
|
||||
```
|
||||
|
||||
demo.sitemap:
|
||||
|
||||
```
|
||||
sitemap demo label="Main Menu"
|
||||
{
|
||||
Frame {
|
||||
Switch item=RadioPower
|
||||
Slider visibility=[RadioPower==ON] item=RadioVolume
|
||||
Switch visibility=[RadioPower==ON] item=RadioMute
|
||||
Selection visibility=[RadioPower==ON] item=RadioPreset mappings=[0="Favourit 1", 1="Favourit 2", 2="Favourit 3", 3="Favourit 4"]
|
||||
Selection visibility=[RadioPower==ON] item=RadioMode mappings=[0="Internet Radio", 1="Musik Player", 2="DAB", 3="FM", 4="AUX"]
|
||||
Text visibility=[RadioPower==ON] item=RadioInfo1
|
||||
Text visibility=[RadioPower==ON] item=RadioInfo2
|
||||
}
|
||||
}
|
||||
```
|
||||
17
bundles/org.openhab.binding.fsinternetradio/pom.xml
Normal file
17
bundles/org.openhab.binding.fsinternetradio/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.fsinternetradio</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: FSInternetRadio Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.fsinternetradio-${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-fsinternetradio" description="Frontier Silicon Internet Radio Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-upnp</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.fsinternetradio/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal;
|
||||
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* This {@link FSInternetRadioBindingConstants} interface defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Patrick Koenemann - Initial contribution
|
||||
*/
|
||||
public interface FSInternetRadioBindingConstants {
|
||||
|
||||
String BINDING_ID = "fsinternetradio";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
ThingTypeUID THING_TYPE_RADIO = new ThingTypeUID(BINDING_ID, "radio");
|
||||
|
||||
// List of all Channel ids
|
||||
String CHANNEL_POWER = "power";
|
||||
String CHANNEL_PRESET = "preset";
|
||||
String CHANNEL_VOLUME_PERCENT = "volume-percent";
|
||||
String CHANNEL_VOLUME_ABSOLUTE = "volume-absolute";
|
||||
String CHANNEL_MUTE = "mute";
|
||||
String CHANNEL_PLAY_INFO_NAME = "play-info-name";
|
||||
String CHANNEL_PLAY_INFO_TEXT = "play-info-text";
|
||||
String CHANNEL_MODE = "mode";
|
||||
|
||||
// config properties
|
||||
String CONFIG_PROPERTY_IP = "ip";
|
||||
String CONFIG_PROPERTY_PIN = "pin";
|
||||
String CONFIG_PROPERTY_PORT = "port";
|
||||
String CONFIG_PROPERTY_REFRESH = "refresh";
|
||||
|
||||
// further properties
|
||||
String PROPERTY_MANUFACTURER = "manufacturer";
|
||||
String PROPERTY_MODEL = "model";
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal;
|
||||
|
||||
import static org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jupnp.model.meta.DeviceDetails;
|
||||
import org.jupnp.model.meta.ManufacturerDetails;
|
||||
import org.jupnp.model.meta.ModelDetails;
|
||||
import org.jupnp.model.meta.RemoteDevice;
|
||||
import org.jupnp.model.meta.RemoteDeviceIdentity;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is the discovery service for internet radios based on the fontier silicon chipset. Unfortunately, it is not
|
||||
* easily possible to detect from the upnp information which devices are supported. So currently, discovery only works
|
||||
* for medion internet radios. {@link FSInternetRadioDiscoveryParticipant#getThingUID(RemoteDevice)} must be extended to
|
||||
* add further supported devices!
|
||||
*
|
||||
* @author Patrick Koenemann - Initial contribution
|
||||
* @author Mihaela Memova - removed the getLabel(RemoteDevice device) method due to its unreachable code lines
|
||||
* @author Markus Michels - support for Teufel 3sixty discovery
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
public class FSInternetRadioDiscoveryParticipant implements UpnpDiscoveryParticipant {
|
||||
private final Logger logger = LoggerFactory.getLogger(FSInternetRadioDiscoveryParticipant.class);
|
||||
|
||||
/** Map from UPnP manufacturer to model number for supported radios; filled in static initializer below. */
|
||||
private static final Map<String, Set<String>> SUPPORTED_RADIO_MODELS = new HashMap<>();
|
||||
|
||||
static {
|
||||
// to allow case-insensitive match: add all values UPPER-CASE!
|
||||
|
||||
// format: MANUFACTURER -> MODEL NAME, as shown e.g. by UPnP Tester as explained here:
|
||||
// https://community.openhab.org/t/internet-radio-i-need-your-help/2131
|
||||
|
||||
// list of medion internet radios taken from: http://internetradio.medion.com/
|
||||
final Set<String> medionRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("MEDION AG", medionRadios);
|
||||
medionRadios.add("MD83813");
|
||||
medionRadios.add("MD84017");
|
||||
medionRadios.add("MD85651");
|
||||
medionRadios.add("MD86062");
|
||||
medionRadios.add("MD86250");
|
||||
medionRadios.add("MD86562");
|
||||
medionRadios.add("MD86672");
|
||||
medionRadios.add("MD86698");
|
||||
medionRadios.add("MD86869");
|
||||
medionRadios.add("MD86891");
|
||||
medionRadios.add("MD86955");
|
||||
medionRadios.add("MD86988");
|
||||
medionRadios.add("MD87090");
|
||||
medionRadios.add("MD87180");
|
||||
medionRadios.add("MD87238");
|
||||
medionRadios.add("MD87267");
|
||||
|
||||
// list of hama internet radios taken from:
|
||||
// https://www.hama.com/action/searchCtrl/search?searchMode=1&q=Internet%20Radio
|
||||
final Set<String> hamaRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("HAMA", hamaRadios);
|
||||
hamaRadios.add("IR100");
|
||||
hamaRadios.add("IR110");
|
||||
hamaRadios.add("IR250");
|
||||
hamaRadios.add("IR320");
|
||||
hamaRadios.add("DIR3000");
|
||||
hamaRadios.add("DIR3100");
|
||||
hamaRadios.add("DIR3110");
|
||||
|
||||
// as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/19
|
||||
// and: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/20
|
||||
// and: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/23
|
||||
// these radios do not provide model number, but the model name should also be ok
|
||||
final Set<String> radiosWithoutManufacturer = new HashSet<>();
|
||||
radiosWithoutManufacturer.add(""); // empty manufacturer / model name
|
||||
radiosWithoutManufacturer.add(null); // missing manufacturer / model name
|
||||
SUPPORTED_RADIO_MODELS.put("SMRS18A1", radiosWithoutManufacturer);
|
||||
SUPPORTED_RADIO_MODELS.put("SMRS30A1", radiosWithoutManufacturer);
|
||||
SUPPORTED_RADIO_MODELS.put("SMRS35A1", radiosWithoutManufacturer);
|
||||
|
||||
final Set<String> teufelRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("Teufel", teufelRadios);
|
||||
teufelRadios.add("Radio 3sixty");
|
||||
|
||||
// as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/5
|
||||
final Set<String> ttmicroRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("TTMICRO AS", ttmicroRadios);
|
||||
ttmicroRadios.add("PINELL SUPERSOUND");
|
||||
|
||||
// as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/7
|
||||
final Set<String> revoRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("REVO TECHNOLOGIES LTD", revoRadios);
|
||||
revoRadios.add("S10");
|
||||
|
||||
// as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/10
|
||||
// and: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/21
|
||||
final Set<String> robertsRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("ROBERTS RADIO LIMITED", robertsRadios);
|
||||
robertsRadios.add("ROBERTS STREAM 93I");
|
||||
robertsRadios.add("ROBERTS STREAM 83I");
|
||||
|
||||
// as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/11
|
||||
final Set<String> aunaRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("AUNA", aunaRadios);
|
||||
aunaRadios.add("10028154 & 10028155");
|
||||
aunaRadios.add("10028154");
|
||||
aunaRadios.add("10028155");
|
||||
|
||||
// as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/22
|
||||
final Set<String> sangeanRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("SANGEAN RADIO LIMITED", sangeanRadios);
|
||||
sangeanRadios.add("28");
|
||||
|
||||
// as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/25
|
||||
final Set<String> rokuRadios = new HashSet<>();
|
||||
SUPPORTED_RADIO_MODELS.put("ROKU", rokuRadios);
|
||||
rokuRadios.add("M1001");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Collections.singleton(THING_TYPE_RADIO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscoveryResult createResult(RemoteDevice device) {
|
||||
final ThingUID uid = getThingUID(device);
|
||||
if (uid != null) {
|
||||
final Map<String, Object> properties = new HashMap<>(1);
|
||||
final String ip = getIp(device);
|
||||
if (ip != null) {
|
||||
properties.put(CONFIG_PROPERTY_IP, ip);
|
||||
|
||||
// add manufacturer and model, if provided
|
||||
final String manufacturer = getManufacturer(device);
|
||||
if (manufacturer != null) {
|
||||
properties.put(PROPERTY_MANUFACTURER, manufacturer);
|
||||
}
|
||||
final String dm = getModel(device);
|
||||
final String model = dm != null ? dm : getFriendlyName(device);
|
||||
if (model != null) {
|
||||
properties.put(PROPERTY_MODEL, model);
|
||||
}
|
||||
final String thingName = (manufacturer == null) && (getModel(device) == null) ? getFriendlyName(device)
|
||||
: device.getDisplayString();
|
||||
return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(thingName).build();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getManufacturer(RemoteDevice device) {
|
||||
final DeviceDetails details = device.getDetails();
|
||||
if ((details != null) && (details.getManufacturerDetails() != null)) {
|
||||
String manufacturer = details.getManufacturerDetails().getManufacturer().trim();
|
||||
return manufacturer.isEmpty() ? null : manufacturer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getModel(RemoteDevice device) {
|
||||
final DeviceDetails details = device.getDetails();
|
||||
if ((details != null) && (details.getModelDetails().getModelNumber() != null)) {
|
||||
String model = details.getModelDetails().getModelNumber().trim();
|
||||
return model.isEmpty() ? null : model;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getFriendlyName(RemoteDevice device) {
|
||||
final DeviceDetails details = device.getDetails();
|
||||
if ((details != null) && (details.getFriendlyName() != null)) {
|
||||
String name = details.getFriendlyName().trim();
|
||||
return name.isEmpty() ? null : name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getIp(RemoteDevice device) {
|
||||
final DeviceDetails details = device.getDetails();
|
||||
if (details != null) {
|
||||
if (details.getBaseURL() != null) {
|
||||
return details.getBaseURL().getHost();
|
||||
}
|
||||
}
|
||||
final RemoteDeviceIdentity identity = device.getIdentity();
|
||||
if (identity != null) {
|
||||
if (identity.getDescriptorURL() != null) {
|
||||
return identity.getDescriptorURL().getHost();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>device</code> is a supported device, a unique thing ID (e.g. serial number) must be returned. Further
|
||||
* supported devices should be added here, based on the available UPnP information.
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
public ThingUID getThingUID(RemoteDevice device) {
|
||||
final DeviceDetails details = device.getDetails();
|
||||
final String friendlyName = details.getFriendlyName();
|
||||
logger.debug("Discovered unit: {}", friendlyName);
|
||||
|
||||
if (details != null) {
|
||||
final ManufacturerDetails manufacturerDetails = details.getManufacturerDetails();
|
||||
final ModelDetails modelDetails = details.getModelDetails();
|
||||
if (modelDetails != null) {
|
||||
// check manufacturer and model number
|
||||
final String manufacturer = manufacturerDetails == null ? null : manufacturerDetails.getManufacturer();
|
||||
final String modelNumber = modelDetails.getModelNumber();
|
||||
String serialNumber = details.getSerialNumber();
|
||||
logger.debug("Discovered unit: {} {} - {}", manufacturer, modelNumber, friendlyName);
|
||||
if (modelNumber != null) {
|
||||
if (manufacturer != null) {
|
||||
final Set<String> supportedRadios = SUPPORTED_RADIO_MODELS
|
||||
.get(manufacturer.trim().toUpperCase());
|
||||
if (supportedRadios != null && supportedRadios.contains(modelNumber.toUpperCase())) {
|
||||
return new ThingUID(THING_TYPE_RADIO, serialNumber);
|
||||
}
|
||||
}
|
||||
// check model name and number
|
||||
final String modelName = modelDetails.getModelName();
|
||||
if (modelName != null) {
|
||||
final Set<String> supportedRadios = SUPPORTED_RADIO_MODELS.get(modelName.trim().toUpperCase());
|
||||
if (supportedRadios != null && supportedRadios.contains(modelNumber.toUpperCase())) {
|
||||
return new ThingUID(THING_TYPE_RADIO, serialNumber);
|
||||
}
|
||||
// Teufel reports empty manufacturer and model, but friendly name
|
||||
if (friendlyName.contains("Teufel")) {
|
||||
logger.debug("haha");
|
||||
}
|
||||
if (!friendlyName.isEmpty()) {
|
||||
for (Set<String> models : SUPPORTED_RADIO_MODELS.values()) {
|
||||
for (String model : models) {
|
||||
if ((model != null) && !model.isEmpty() && friendlyName.contains(model)) {
|
||||
return new ThingUID(THING_TYPE_RADIO, serialNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (((manufacturer == null) || manufacturer.trim().isEmpty())
|
||||
&& ((modelNumber == null) || modelNumber.trim().isEmpty())) {
|
||||
// Some devices report crappy UPnP device description so manufacturer and model are ""
|
||||
// In this case we try to find the match in friendlyName
|
||||
final String uname = friendlyName.toUpperCase();
|
||||
for (Map.Entry<String, Set<String>> entry : SUPPORTED_RADIO_MODELS.entrySet()) {
|
||||
for (Set<String> set : SUPPORTED_RADIO_MODELS.values()) {
|
||||
for (String model : set) {
|
||||
if ((model != null) && !model.isEmpty() && uname.contains(model)) {
|
||||
return new ThingUID(THING_TYPE_RADIO, serialNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// maybe we can add further indicators, whether the device is a supported one
|
||||
}
|
||||
// device not supported
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal;
|
||||
|
||||
import static org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants.THING_TYPE_RADIO;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.fsinternetradio.internal.handler.FSInternetRadioHandler;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link FSInternetRadioHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Patrick Koenemann - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.fsinternetradio")
|
||||
@NonNullByDefault
|
||||
public class FSInternetRadioHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_RADIO);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public FSInternetRadioHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@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 (thingTypeUID.equals(THING_TYPE_RADIO)) {
|
||||
return new FSInternetRadioHandler(thing, httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal.handler;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.fsinternetradio.internal.radio.FrontierSiliconRadio;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
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.library.types.UpDownType;
|
||||
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.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link FSInternetRadioHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Patrick Koenemann - Initial contribution
|
||||
* @author Mihaela Memova - removed the unused boolean parameter, changed the check for the PIN
|
||||
* @author Svilen Valkanov - changed handler initialization
|
||||
*/
|
||||
public class FSInternetRadioHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FSInternetRadioHandler.class);
|
||||
|
||||
FrontierSiliconRadio radio;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/** Job that runs {@link #updateRunnable}. */
|
||||
private ScheduledFuture<?> updateJob;
|
||||
|
||||
/** Runnable for job {@link #updateJob} for periodic refresh. */
|
||||
private final Runnable updateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!radio.isLoggedIn()) {
|
||||
// radio is not set, so set all channels to 'undefined'
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
updateState(channel.getUID(), UnDefType.UNDEF);
|
||||
}
|
||||
// now let's silently check if it's back online
|
||||
radioLogin();
|
||||
return; // if login is successful, this method is called again :-)
|
||||
}
|
||||
try {
|
||||
final boolean radioOn = radio.getPower();
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
if (!radioOn && !CHANNEL_POWER.equals(channel.getUID().getId())) {
|
||||
// if radio is off, set all channels (except for 'POWER') to 'undefined'
|
||||
updateState(channel.getUID(), UnDefType.UNDEF);
|
||||
} else if (isLinked(channel.getUID().getId())) {
|
||||
// update all channels that are linked
|
||||
switch (channel.getUID().getId()) {
|
||||
case CHANNEL_POWER:
|
||||
updateState(channel.getUID(), radioOn ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case CHANNEL_VOLUME_ABSOLUTE:
|
||||
updateState(channel.getUID(),
|
||||
DecimalType.valueOf(String.valueOf(radio.getVolumeAbsolute())));
|
||||
break;
|
||||
case CHANNEL_VOLUME_PERCENT:
|
||||
updateState(channel.getUID(),
|
||||
PercentType.valueOf(String.valueOf(radio.getVolumePercent())));
|
||||
break;
|
||||
case CHANNEL_MODE:
|
||||
updateState(channel.getUID(), DecimalType.valueOf(String.valueOf(radio.getMode())));
|
||||
break;
|
||||
case CHANNEL_MUTE:
|
||||
updateState(channel.getUID(), radio.getMuted() ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case CHANNEL_PRESET:
|
||||
// preset is write-only, ignore
|
||||
break;
|
||||
case CHANNEL_PLAY_INFO_NAME:
|
||||
updateState(channel.getUID(), StringType.valueOf(radio.getPlayInfoName()));
|
||||
break;
|
||||
case CHANNEL_PLAY_INFO_TEXT:
|
||||
updateState(channel.getUID(), StringType.valueOf(radio.getPlayInfoText()));
|
||||
break;
|
||||
default:
|
||||
logger.warn("Ignoring unknown channel during update: {}", channel.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE); // set it back online, maybe it was offline before
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public FSInternetRadioHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// read configuration
|
||||
final String ip = (String) getThing().getConfiguration().get(CONFIG_PROPERTY_IP);
|
||||
final BigDecimal port = (BigDecimal) getThing().getConfiguration().get(CONFIG_PROPERTY_PORT);
|
||||
final String pin = (String) getThing().getConfiguration().get(CONFIG_PROPERTY_PIN);
|
||||
|
||||
if (ip == null || StringUtils.isEmpty(pin) || port.intValue() == 0) {
|
||||
// configuration incomplete
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration incomplete");
|
||||
} else {
|
||||
radio = new FrontierSiliconRadio(ip, port.intValue(), pin, httpClient);
|
||||
logger.debug("Initializing connection to {}:{}", ip, port);
|
||||
|
||||
// Long running initialization should be done asynchronously in background
|
||||
radioLogin();
|
||||
|
||||
// also schedule a thread for polling with configured refresh rate
|
||||
final BigDecimal period = (BigDecimal) getThing().getConfiguration().get(CONFIG_PROPERTY_REFRESH);
|
||||
if (period != null && period.intValue() > 0) {
|
||||
updateJob = scheduler.scheduleWithFixedDelay(updateRunnable, period.intValue(), period.intValue(),
|
||||
SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void radioLogin() {
|
||||
scheduler.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (radio.login()) {
|
||||
// Thing initialized. If done set status to ONLINE to indicate proper working.
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
// now update all channels!
|
||||
updateRunnable.run();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (updateJob != null) {
|
||||
updateJob.cancel(true);
|
||||
}
|
||||
updateJob = null;
|
||||
radio = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
if (!radio.isLoggedIn()) {
|
||||
// connection to radio is not initialized, log ignored command and set status, if it is not already offline
|
||||
logger.debug("Ignoring command {} = {} because device is offline.", channelUID.getId(), command);
|
||||
if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_POWER:
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
radio.setPower(true);
|
||||
} else if (OnOffType.OFF.equals(command)) {
|
||||
radio.setPower(false);
|
||||
}
|
||||
// now all items should be updated! (wait some seconds so that text items are up-to-date)
|
||||
scheduler.schedule(updateRunnable, 4, SECONDS);
|
||||
break;
|
||||
case CHANNEL_VOLUME_PERCENT:
|
||||
if (IncreaseDecreaseType.INCREASE.equals(command) || UpDownType.UP.equals(command)) {
|
||||
radio.increaseVolumeAbsolute();
|
||||
} else if (IncreaseDecreaseType.DECREASE.equals(command) || UpDownType.DOWN.equals(command)) {
|
||||
radio.decreaseVolumeAbsolute();
|
||||
} else if (command instanceof PercentType) {
|
||||
radio.setVolumePercent(((PercentType) command).intValue());
|
||||
}
|
||||
// absolute value should also be updated now, so let's update all items
|
||||
scheduler.schedule(updateRunnable, 1, SECONDS);
|
||||
break;
|
||||
case CHANNEL_VOLUME_ABSOLUTE:
|
||||
if (IncreaseDecreaseType.INCREASE.equals(command) || UpDownType.UP.equals(command)) {
|
||||
radio.increaseVolumeAbsolute();
|
||||
} else if (IncreaseDecreaseType.DECREASE.equals(command) || UpDownType.DOWN.equals(command)) {
|
||||
radio.decreaseVolumeAbsolute();
|
||||
} else if (command instanceof DecimalType) {
|
||||
radio.setVolumeAbsolute(((DecimalType) command).intValue());
|
||||
}
|
||||
// percent value should also be updated now, so let's update all items
|
||||
scheduler.schedule(updateRunnable, 1, SECONDS);
|
||||
break;
|
||||
case CHANNEL_MODE:
|
||||
if (command instanceof DecimalType) {
|
||||
radio.setMode(((DecimalType) command).intValue());
|
||||
}
|
||||
break;
|
||||
case CHANNEL_PRESET:
|
||||
if (command instanceof DecimalType) {
|
||||
radio.setPreset(((DecimalType) command).intValue());
|
||||
}
|
||||
break;
|
||||
case CHANNEL_MUTE:
|
||||
if (command instanceof OnOffType) {
|
||||
radio.setMuted(OnOffType.ON.equals(command));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.warn("Ignoring unknown command: {}", command);
|
||||
}
|
||||
// make sure that device state is online
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (Exception e) {
|
||||
// set device state to offline
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal.radio;
|
||||
|
||||
import static org.openhab.binding.fsinternetradio.internal.radio.FrontierSiliconRadioConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
|
||||
/**
|
||||
* Class representing a internet radio based on the frontier silicon chipset. Tested with "hama IR110" and Medion
|
||||
* MD87180" internet radios.
|
||||
*
|
||||
* @author Rainer Ostendorf
|
||||
* @author Patrick Koenemann
|
||||
* @author Mihaela Memova - removed duplicated check for the percent value range
|
||||
*/
|
||||
public class FrontierSiliconRadio {
|
||||
|
||||
/** The http connection/session used for controlling the radio. */
|
||||
private final FrontierSiliconRadioConnection conn;
|
||||
|
||||
/** the volume of the radio. we cache it for fast increase/decrease. */
|
||||
private int currentVolume = 0;
|
||||
|
||||
/**
|
||||
* Constructor for the Radio class
|
||||
*
|
||||
* @param hostname Host name of the Radio addressed, e.g. "192.168.0.100"
|
||||
* @param port Port number, default: 80 (http)
|
||||
* @param pin Access PIN number of the radio. Must be 4 digits, e.g. "1234"
|
||||
* @param httpClient http client instance to use
|
||||
*
|
||||
* @author Rainer Ostendorf
|
||||
*/
|
||||
public FrontierSiliconRadio(String hostname, int port, String pin, HttpClient httpClient) {
|
||||
this.conn = new FrontierSiliconRadioConnection(hostname, port, pin, httpClient);
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return conn.isLoggedIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform login to the radio and establish new session
|
||||
*
|
||||
* @author Rainer Ostendorf
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public boolean login() throws IOException {
|
||||
return conn.doLogin();
|
||||
}
|
||||
|
||||
/**
|
||||
* get the radios power state
|
||||
*
|
||||
* @return true when radio is on, false when radio is off
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public boolean getPower() throws IOException {
|
||||
final FrontierSiliconRadioApiResult result = conn.doRequest(REQUEST_GET_POWER);
|
||||
return result.getValueU8AsBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn radio on/off
|
||||
*
|
||||
* @param powerOn
|
||||
* true turns on the radio, false turns it off
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public void setPower(boolean powerOn) throws IOException {
|
||||
final String params = "value=" + (powerOn ? "1" : "0");
|
||||
conn.doRequest(REQUEST_SET_POWER, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* read the volume (as absolute value, 0-32)
|
||||
*
|
||||
* @return volume: 0=muted, 32=max. volume
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public int getVolumeAbsolute() throws IOException {
|
||||
FrontierSiliconRadioApiResult result = conn.doRequest(REQUEST_GET_VOLUME);
|
||||
currentVolume = result.getValueU8AsInt();
|
||||
return currentVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* read the volume (as percent value, 0-100)
|
||||
*
|
||||
* @return volume: 0=muted, 100=max. volume (100 corresponds 32 absolute value)
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public int getVolumePercent() throws IOException {
|
||||
FrontierSiliconRadioApiResult result = conn.doRequest(REQUEST_GET_VOLUME);
|
||||
currentVolume = result.getValueU8AsInt();
|
||||
return (currentVolume * 100) / 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the radios volume
|
||||
*
|
||||
* @param volume
|
||||
* Radio volume: 0=mute, 32=max. volume
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public void setVolumeAbsolute(int volume) throws IOException {
|
||||
final int newVolume = volume < 0 ? 0 : volume > 32 ? 32 : volume;
|
||||
final String params = "value=" + newVolume;
|
||||
conn.doRequest(REQUEST_SET_VOLUME, params);
|
||||
currentVolume = volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the radios volume in percent
|
||||
*
|
||||
* @param volume
|
||||
* Radio volume: 0=muted, 100=max. volume (100 corresponds 32 absolute value)
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public void setVolumePercent(int volume) throws IOException {
|
||||
final int newVolumeAbsolute = (volume * 32) / 100;
|
||||
final String params = "value=" + newVolumeAbsolute;
|
||||
conn.doRequest(REQUEST_SET_VOLUME, params);
|
||||
currentVolume = volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase radio volume by 1 step, max is 32.
|
||||
*
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public void increaseVolumeAbsolute() throws IOException {
|
||||
if (currentVolume < 32) {
|
||||
setVolumeAbsolute(currentVolume + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease radio volume by 1 step.
|
||||
*
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public void decreaseVolumeAbsolute() throws IOException {
|
||||
if (currentVolume > 0) {
|
||||
setVolumeAbsolute(currentVolume - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the radios operating mode
|
||||
*
|
||||
* @return operating mode. On hama radio: 0="Internet Radio", 1=Spotify, 2=Player, 3="AUX IN"
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public int getMode() throws IOException {
|
||||
FrontierSiliconRadioApiResult result = conn.doRequest(REQUEST_GET_MODE);
|
||||
return result.getValueU32AsInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the radio operating mode
|
||||
*
|
||||
* @param mode
|
||||
* On hama radio: 0="Internet Radio", 1=Spotify, 2=Player, 3="AUX IN"
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public void setMode(int mode) throws IOException {
|
||||
final String params = "value=" + mode;
|
||||
conn.doRequest(REQUEST_SET_MODE, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the Station info name, e.g. "WDR2"
|
||||
*
|
||||
* @return the station name, e.g. "WDR2"
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public String getPlayInfoName() throws IOException {
|
||||
FrontierSiliconRadioApiResult result = conn.doRequest(REQUEST_GET_PLAY_INFO_NAME);
|
||||
return result.getValueC8ArrayAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* read the stations radio text like the song name currently playing
|
||||
*
|
||||
* @return the radio info text, e.g. music title
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public String getPlayInfoText() throws IOException {
|
||||
FrontierSiliconRadioApiResult result = conn.doRequest(REQUEST_GET_PLAY_INFO_TEXT);
|
||||
return result.getValueC8ArrayAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* set a station preset. Tunes the radio to a preselected station.
|
||||
*
|
||||
* @param presetId
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public void setPreset(Integer presetId) throws IOException {
|
||||
conn.doRequest(REQUEST_SET_PRESET, "value=1");
|
||||
conn.doRequest(REQUEST_SET_PRESET_ACTION, "value=" + presetId.toString());
|
||||
conn.doRequest(REQUEST_SET_PRESET, "value=0");
|
||||
}
|
||||
|
||||
/**
|
||||
* read the muted state
|
||||
*
|
||||
* @return true: radio is muted, false: radio is not muted
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public boolean getMuted() throws IOException {
|
||||
FrontierSiliconRadioApiResult result = conn.doRequest(REQUEST_GET_MUTE);
|
||||
return result.getValueU8AsBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* mute the radio volume
|
||||
*
|
||||
* @param muted
|
||||
* true: mute the radio, false: unmute the radio
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public void setMuted(boolean muted) throws IOException {
|
||||
final String params = "value=" + (muted ? "1" : "0");
|
||||
conn.doRequest(REQUEST_SET_MUTE, params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal.radio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.CharacterData;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* This class hold the result of a request read from the radio. Upon a request the radio returns a XML document like
|
||||
* this:
|
||||
*
|
||||
* <pre>
|
||||
* <xmp>
|
||||
* <fsapiResponse> <status>FS_OK</status> <value><u8>1</u8></value> </fsapiResponse>
|
||||
* </xmp>
|
||||
* </pre>
|
||||
*
|
||||
* This class parses this XML data and provides functions for reading and casting typical fields.
|
||||
*
|
||||
* @author Rainer Ostendorf
|
||||
* @author Patrick Koenemann
|
||||
*
|
||||
*/
|
||||
public class FrontierSiliconRadioApiResult {
|
||||
|
||||
/**
|
||||
* XML structure holding the parsed response
|
||||
*/
|
||||
final Document xmlDoc;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FrontierSiliconRadioApiResult.class);
|
||||
|
||||
/**
|
||||
* Create result object from XML that was received from the radio.
|
||||
*
|
||||
* @param requestResultString
|
||||
* The XML string received from the radio.
|
||||
* @throws IOException in case the XML returned by the radio is invalid.
|
||||
*/
|
||||
public FrontierSiliconRadioApiResult(String requestResultString) throws IOException {
|
||||
Document xml = null;
|
||||
try {
|
||||
xml = getXmlDocFromString(requestResultString);
|
||||
} catch (Exception e) {
|
||||
logger.trace("converting to XML failed: '{}' with {}: {}", requestResultString, e.getClass().getName(),
|
||||
e.getMessage());
|
||||
logger.debug("converting to XML failed with {}: {}", e.getClass().getName(), e.getMessage());
|
||||
if (e instanceof IOException) {
|
||||
throw (IOException) e;
|
||||
}
|
||||
throw new IOException(e);
|
||||
}
|
||||
xmlDoc = xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the field "status" from the result and return it
|
||||
*
|
||||
* @return result field as string.
|
||||
*/
|
||||
private String getStatus() {
|
||||
final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
|
||||
final Element statusNode = (Element) fsApiResult.getElementsByTagName("status").item(0);
|
||||
|
||||
final String status = getCharacterDataFromElement(statusNode);
|
||||
logger.trace("status is: {}", status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the responses status code was "FS_OK"
|
||||
*
|
||||
* @return true if status is "FS_OK", false else
|
||||
*/
|
||||
public boolean isStatusOk() {
|
||||
return ("FS_OK").equals(getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* read the <value><u8> field as boolean
|
||||
*
|
||||
* @return value.u8 field as bool
|
||||
*/
|
||||
public boolean getValueU8AsBoolean() {
|
||||
try {
|
||||
final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
|
||||
final Element valueNode = (Element) fsApiResult.getElementsByTagName("value").item(0);
|
||||
final Element u8Node = (Element) valueNode.getElementsByTagName("u8").item(0);
|
||||
|
||||
final String value = getCharacterDataFromElement(u8Node);
|
||||
logger.trace("value is: {}", value);
|
||||
|
||||
return "1".equals(value);
|
||||
} catch (Exception e) {
|
||||
logger.error("getting Value.U8 failed with {}: {}", e.getClass().getName(), e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the <value><u8> field as int
|
||||
*
|
||||
* @return value.u8 field as int
|
||||
*/
|
||||
public int getValueU8AsInt() {
|
||||
try {
|
||||
final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
|
||||
final Element valueNode = (Element) fsApiResult.getElementsByTagName("value").item(0);
|
||||
final Element u8Node = (Element) valueNode.getElementsByTagName("u8").item(0);
|
||||
|
||||
final String value = getCharacterDataFromElement(u8Node);
|
||||
logger.trace("value is: {}", value);
|
||||
|
||||
return Integer.parseInt(value);
|
||||
} catch (Exception e) {
|
||||
logger.error("getting Value.U8 failed with {}: {}", e.getClass().getName(), e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the <value><u32> field as int
|
||||
*
|
||||
* @return value.u32 field as int
|
||||
*/
|
||||
public int getValueU32AsInt() {
|
||||
try {
|
||||
final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
|
||||
final Element valueNode = (Element) fsApiResult.getElementsByTagName("value").item(0);
|
||||
final Element u32Node = (Element) valueNode.getElementsByTagName("u32").item(0);
|
||||
|
||||
final String value = getCharacterDataFromElement(u32Node);
|
||||
logger.trace("value is: {}", value);
|
||||
|
||||
return Integer.parseInt(value);
|
||||
} catch (Exception e) {
|
||||
logger.error("getting Value.U32 failed with {}: {}", e.getClass().getName(), e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the <value><c8_array> field as String
|
||||
*
|
||||
* @return value.c8_array field as String
|
||||
*/
|
||||
public String getValueC8ArrayAsString() {
|
||||
try {
|
||||
final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
|
||||
final Element valueNode = (Element) fsApiResult.getElementsByTagName("value").item(0);
|
||||
final Element c8Array = (Element) valueNode.getElementsByTagName("c8_array").item(0);
|
||||
|
||||
final String value = getCharacterDataFromElement(c8Array);
|
||||
logger.trace("value is: {}", value);
|
||||
|
||||
return value;
|
||||
} catch (Exception e) {
|
||||
logger.error("getting Value.c8array failed with {}: {}", e.getClass().getName(), e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the <sessionId> field as String
|
||||
*
|
||||
* @return value of sessionId field
|
||||
*/
|
||||
public String getSessionId() {
|
||||
final NodeList sessionIdTagList = xmlDoc.getElementsByTagName("sessionId");
|
||||
final String givenSessId = getCharacterDataFromElement((Element) sessionIdTagList.item(0));
|
||||
return givenSessId;
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the string we got from the radio to a parsable XML document
|
||||
*
|
||||
* @param xmlString
|
||||
* the XML string read from the radio
|
||||
* @return the parsed XML document
|
||||
* @throws ParserConfigurationException
|
||||
* @throws SAXException
|
||||
* @throws IOException
|
||||
*/
|
||||
private Document getXmlDocFromString(String xmlString)
|
||||
throws ParserConfigurationException, SAXException, IOException {
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
final DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
final Document xmlDocument = builder.parse(new InputSource(new StringReader(xmlString)));
|
||||
return xmlDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the value of a given XML element to a string for further processing
|
||||
*
|
||||
* @param e
|
||||
* XML Element
|
||||
* @return the elements value converted to string
|
||||
*/
|
||||
private static String getCharacterDataFromElement(Element e) {
|
||||
final Node child = e.getFirstChild();
|
||||
if (child instanceof CharacterData) {
|
||||
final CharacterData cd = (CharacterData) child;
|
||||
return cd.getData();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal.radio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class holds the http-connection and session information for controlling the radio.
|
||||
*
|
||||
* @author Rainer Ostendorf
|
||||
* @author Patrick Koenemann
|
||||
* @author Svilen Valkanov - replaced Apache HttpClient with Jetty
|
||||
* @author Mihaela Memova - changed the calling of the stopHttpClient() method, fixed the hardcoded URL path, fixed the
|
||||
* for loop condition part
|
||||
*/
|
||||
public class FrontierSiliconRadioConnection {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FrontierSiliconRadioConnection.class);
|
||||
|
||||
/** Timeout for HTTP requests in ms */
|
||||
private static final int SOCKET_TIMEOUT = 5000;
|
||||
|
||||
/** Hostname of the radio. */
|
||||
private final String hostname;
|
||||
|
||||
/** Port number, usually 80. */
|
||||
private final int port;
|
||||
|
||||
/** Access pin, passed upon login as GET parameter. */
|
||||
private final String pin;
|
||||
|
||||
/** The session ID we get from the radio after logging in. */
|
||||
private String sessionId;
|
||||
|
||||
/** http clients, store cookies, so it is kept in connection class. */
|
||||
private HttpClient httpClient = null;
|
||||
|
||||
/** Flag indicating if we are successfully logged in. */
|
||||
private boolean isLoggedIn = false;
|
||||
|
||||
public FrontierSiliconRadioConnection(String hostname, int port, String pin, HttpClient httpClient) {
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
this.pin = pin;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return isLoggedIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform login/establish a new session. Uses the PIN number and when successful saves the assigned sessionID for
|
||||
* future requests.
|
||||
*
|
||||
* @return <code>true</code> if login was successful; <code>false</code> otherwise.
|
||||
* @throws IOException if communication with the radio failed, e.g. because the device is not reachable.
|
||||
*/
|
||||
public boolean doLogin() throws IOException {
|
||||
isLoggedIn = false; // reset login flag
|
||||
|
||||
final String url = "http://" + hostname + ":" + port + FrontierSiliconRadioConstants.CONNECTION_PATH
|
||||
+ "/CREATE_SESSION?pin=" + pin;
|
||||
|
||||
logger.trace("opening URL: {}", url);
|
||||
|
||||
Request request = httpClient.newRequest(url).method(HttpMethod.GET).timeout(SOCKET_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS);
|
||||
|
||||
try {
|
||||
ContentResponse response = request.send();
|
||||
int statusCode = response.getStatus();
|
||||
if (statusCode != HttpStatus.OK_200) {
|
||||
String reason = response.getReason();
|
||||
logger.debug("Communication with radio failed: {} {}", statusCode, reason);
|
||||
if (statusCode == HttpStatus.FORBIDDEN_403) {
|
||||
throw new IllegalStateException("Radio does not allow connection, maybe wrong pin?");
|
||||
}
|
||||
throw new IOException("Communication with radio failed, return code: " + statusCode);
|
||||
}
|
||||
|
||||
final String responseBody = response.getContentAsString();
|
||||
if (!responseBody.isEmpty()) {
|
||||
logger.trace("login response: {}", responseBody);
|
||||
}
|
||||
|
||||
final FrontierSiliconRadioApiResult result = new FrontierSiliconRadioApiResult(responseBody);
|
||||
if (result.isStatusOk()) {
|
||||
logger.trace("login successful");
|
||||
sessionId = result.getSessionId();
|
||||
isLoggedIn = true;
|
||||
return true; // login successful :-)
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.debug("Fatal transport error: {}", e.toString());
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
return false; // login not successful
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request to the radio with no further parameters.
|
||||
*
|
||||
* Typically used for polling state info.
|
||||
*
|
||||
* @param REST
|
||||
* API requestString, e.g. "GET/netRemote.sys.power"
|
||||
* @return request result
|
||||
* @throws IOException if the request failed.
|
||||
*/
|
||||
public FrontierSiliconRadioApiResult doRequest(String requestString) throws IOException {
|
||||
return doRequest(requestString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request to the radio with addition parameters.
|
||||
*
|
||||
* Typically used for changing parameters.
|
||||
*
|
||||
* @param REST
|
||||
* API requestString, e.g. "SET/netRemote.sys.power"
|
||||
* @param params
|
||||
* , e.g. "value=1"
|
||||
* @return request result
|
||||
* @throws IOException if the request failed.
|
||||
*/
|
||||
public FrontierSiliconRadioApiResult doRequest(String requestString, String params) throws IOException {
|
||||
// 3 retries upon failure
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (!isLoggedIn && !doLogin()) {
|
||||
continue; // not logged in and login was not successful - try again!
|
||||
}
|
||||
|
||||
final String url = "http://" + hostname + ":" + port + FrontierSiliconRadioConstants.CONNECTION_PATH + "/"
|
||||
+ requestString + "?pin=" + pin + "&sid=" + sessionId
|
||||
+ (params == null || params.trim().length() == 0 ? "" : "&" + params);
|
||||
|
||||
logger.trace("calling url: '{}'", url);
|
||||
|
||||
Request request = httpClient.newRequest(url).method(HttpMethod.GET).timeout(SOCKET_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS);
|
||||
|
||||
try {
|
||||
ContentResponse response = request.send();
|
||||
final int statusCode = response.getStatus();
|
||||
if (statusCode != HttpStatus.OK_200) {
|
||||
/*-
|
||||
* Issue: https://github.com/eclipse/smarthome/issues/2548
|
||||
* If the session expired, we might get a 404 here. That's ok, remember that we are not logged-in
|
||||
* and try again. Print warning only if this happens in the last iteration.
|
||||
*/
|
||||
if (i >= 2) {
|
||||
String reason = response.getReason();
|
||||
logger.warn("Method failed: {} {}", statusCode, reason);
|
||||
}
|
||||
isLoggedIn = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
final String responseBody = response.getContentAsString();
|
||||
if (!responseBody.isEmpty()) {
|
||||
logger.trace("got result: {}", responseBody);
|
||||
} else {
|
||||
logger.debug("got empty result");
|
||||
isLoggedIn = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
final FrontierSiliconRadioApiResult result = new FrontierSiliconRadioApiResult(responseBody);
|
||||
if (result.isStatusOk()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
isLoggedIn = false;
|
||||
continue; // try again
|
||||
} catch (Exception e) {
|
||||
logger.error("Fatal transport error: {}", e.toString());
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
isLoggedIn = false; // 3 tries failed. log in again next time, maybe our session went invalid (radio restarted?)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal.radio;
|
||||
|
||||
/**
|
||||
* Internal constants for the frontier silicon radio.
|
||||
*
|
||||
* @author Markus Rathgeb - Moved the constants to separate class
|
||||
*/
|
||||
public class FrontierSiliconRadioConstants {
|
||||
|
||||
public static final String REQUEST_SET_POWER = "SET/netRemote.sys.power";
|
||||
public static final String REQUEST_GET_POWER = "GET/netRemote.sys.power";
|
||||
public static final String REQUEST_GET_MODE = "GET/netRemote.sys.mode";
|
||||
public static final String REQUEST_SET_MODE = "SET/netRemote.sys.mode";
|
||||
public static final String REQUEST_SET_VOLUME = "SET/netRemote.sys.audio.volume";
|
||||
public static final String REQUEST_GET_VOLUME = "GET/netRemote.sys.audio.volume";
|
||||
public static final String REQUEST_SET_MUTE = "SET/netRemote.sys.audio.mute";
|
||||
public static final String REQUEST_GET_MUTE = "GET/netRemote.sys.audio.mute";
|
||||
public static final String REQUEST_SET_PRESET = "SET/netRemote.nav.state";
|
||||
public static final String REQUEST_SET_PRESET_ACTION = "SET/netRemote.nav.action.selectPreset";
|
||||
public static final String REQUEST_GET_PLAY_INFO_TEXT = "GET/netRemote.play.info.text";
|
||||
public static final String REQUEST_GET_PLAY_INFO_NAME = "GET/netRemote.play.info.name";
|
||||
|
||||
/** URL path, must begin with a slash (/) */
|
||||
public static final String CONNECTION_PATH = "/fsapi";
|
||||
|
||||
private FrontierSiliconRadioConstants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="fsinternetradio" 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>FSInternetRadio Binding</name>
|
||||
<description>This is the binding for internet radios based on the Frontier Silicon chipset.</description>
|
||||
<author>Patrick Koenemann</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="fsinternetradio"
|
||||
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="radio">
|
||||
<label>Internet Radio</label>
|
||||
<description>An internet radio device based on the Frontier Silicon chipset.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="mode" typeId="mode"/>
|
||||
<channel id="volume-absolute" typeId="volume-absolute"/>
|
||||
<channel id="volume-percent" typeId="volume-percent"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="play-info-name" typeId="play-info-name"/>
|
||||
<channel id="play-info-text" typeId="play-info-text"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Frontiersilicon</property>
|
||||
<property name="modelId"></property>
|
||||
</properties>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ip" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Network Address</label>
|
||||
<description>The IP address (name or numeric) of the internet radio.</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="true">
|
||||
<label>Port</label>
|
||||
<description>The port of the internet radio (default: 80).</description>
|
||||
<default>80</default>
|
||||
</parameter>
|
||||
<parameter name="pin" type="text" required="true">
|
||||
<label>Pin</label>
|
||||
<description>The PIN configured in the internet radio (default: 1234).</description>
|
||||
<default>1234</default>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in seconds.</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="power">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Power</label>
|
||||
<description>Switch the radio on or off.</description>
|
||||
<category>Switch</category>
|
||||
</channel-type>
|
||||
<channel-type id="preset">
|
||||
<item-type>Number</item-type>
|
||||
<label>Preset</label>
|
||||
<description>Preset radio stations configured in the radio.</description>
|
||||
</channel-type>
|
||||
<channel-type id="volume-absolute" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Volume</label>
|
||||
<description>Volume (min=0, max=32).</description>
|
||||
<category>SoundVolume</category>
|
||||
<state min="0" max="32" step="1"/>
|
||||
</channel-type>
|
||||
<channel-type id="volume-percent">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Volume</label>
|
||||
<description>Volume (in percent).</description>
|
||||
<category>SoundVolume</category>
|
||||
<state min="0" max="100" step="3"/> <!-- 3% correspond to 1 absolute step -->
|
||||
</channel-type>
|
||||
<channel-type id="mute">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Mute</label>
|
||||
<description>Mute the radio.</description>
|
||||
</channel-type>
|
||||
<channel-type id="play-info-name">
|
||||
<item-type>String</item-type>
|
||||
<label>Current Title</label>
|
||||
<description>The name of the current radio station or track.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="play-info-text">
|
||||
<item-type>String</item-type>
|
||||
<label>Info Text</label>
|
||||
<description>Additional information e.g. of the current radio station.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="mode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Mode</label>
|
||||
<description>The radio mode, e.g. FM radio, internet radio, AUX, etc.</description>
|
||||
<state min="0" step="1"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.internal.handler;
|
||||
|
||||
import org.openhab.binding.fsinternetradio.internal.radio.FrontierSiliconRadio;
|
||||
|
||||
/**
|
||||
* Utils for the handler.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
|
||||
public class HandlerUtils {
|
||||
|
||||
/**
|
||||
* Get the radio of a radio handler.
|
||||
*
|
||||
* @param handler the handler
|
||||
* @return the managed radio object
|
||||
*/
|
||||
public static FrontierSiliconRadio getRadio(final FSInternetRadioHandler handler) {
|
||||
return handler.radio;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.jupnp.model.ValidationException;
|
||||
import org.jupnp.model.meta.DeviceDetails;
|
||||
import org.jupnp.model.meta.ManufacturerDetails;
|
||||
import org.jupnp.model.meta.ModelDetails;
|
||||
import org.jupnp.model.meta.RemoteDevice;
|
||||
import org.jupnp.model.meta.RemoteDeviceIdentity;
|
||||
import org.jupnp.model.meta.RemoteService;
|
||||
import org.jupnp.model.types.DeviceType;
|
||||
import org.jupnp.model.types.UDN;
|
||||
import org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants;
|
||||
import org.openhab.binding.fsinternetradio.internal.FSInternetRadioDiscoveryParticipant;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* OSGi tests for the {@link FSInternetRadioDiscoveryParticipant}.
|
||||
*
|
||||
* @author Mihaela Memova - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated from Groovy to pure Java test, made more robust
|
||||
* @author Velin Yordanov - Migrated to mockito
|
||||
*
|
||||
*/
|
||||
public class FSInternetRadioDiscoveryParticipantJavaTest {
|
||||
UpnpDiscoveryParticipant discoveryParticipant;
|
||||
|
||||
// default device variables used in the tests
|
||||
DeviceType DEFAULT_TYPE = new DeviceType("namespace", "type");
|
||||
String DEFAULT_UPC = "upc";
|
||||
URI DEFAULT_URI = null;
|
||||
|
||||
// default radio variables used in most of the tests
|
||||
private static final RemoteDeviceIdentity DEFAULT_RADIO_IDENTITY;
|
||||
private static final URL DEFAULT_RADIO_BASE_URL;
|
||||
String DEFAULT_RADIO_NAME = "HamaRadio";
|
||||
static {
|
||||
try {
|
||||
DEFAULT_RADIO_IDENTITY = new RemoteDeviceIdentity(new UDN("radioUDN"), 60,
|
||||
new URL("http://radioDescriptiveURL"), null, null);
|
||||
DEFAULT_RADIO_BASE_URL = new URL("http://radioBaseURL");
|
||||
} catch (final MalformedURLException ex) {
|
||||
throw new Error("Initialization error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The default radio is chosen from the {@link FrontierSiliconRadioDiscoveryParticipant}'s
|
||||
* set of supported radios
|
||||
*/
|
||||
String DEFAULT_RADIO_MANIFACTURER = "HAMA";
|
||||
String DEFAULT_RADIO_MODEL_NAME = "IR";
|
||||
String DEFAULT_RADIO_MODEL_DESCRIPTION = "IR Radio";
|
||||
String DEFAULT_RADIO_MODEL_NUMBER = "IR100";
|
||||
String DEFAULT_RADIO_SERIAL_NUMBER = "serialNumber123";
|
||||
|
||||
String RADIO_BINDING_ID = "fsinternetradio"; // taken from the binding.xml file
|
||||
String RADIO_THING_TYPE_ID = "radio"; // taken from the thing-types.xml file
|
||||
String DEFAULT_RADIO_THING_UID = String.format("%s:%s:%s", RADIO_BINDING_ID, RADIO_THING_TYPE_ID,
|
||||
DEFAULT_RADIO_SERIAL_NUMBER);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
discoveryParticipant = new FSInternetRadioDiscoveryParticipant();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify correct supported types.
|
||||
*/
|
||||
@Test
|
||||
public void correctSupportedTypes() {
|
||||
assertEquals(1, discoveryParticipant.getSupportedThingTypeUIDs().size());
|
||||
assertEquals(FSInternetRadioBindingConstants.THING_TYPE_RADIO,
|
||||
discoveryParticipant.getSupportedThingTypeUIDs().iterator().next());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify valid DiscoveryResult with completeFSInterntRadioDevice.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@Test
|
||||
public void validDiscoveryResultWithComplete() throws ValidationException {
|
||||
RemoteDevice completeFSInternetRadioDevice = createDefaultFSInternetRadioDevice(DEFAULT_RADIO_BASE_URL);
|
||||
final DiscoveryResult result = discoveryParticipant.createResult(completeFSInternetRadioDevice);
|
||||
assertEquals(new ThingUID(DEFAULT_RADIO_THING_UID), result.getThingUID());
|
||||
assertEquals(FSInternetRadioBindingConstants.THING_TYPE_RADIO, result.getThingTypeUID());
|
||||
assertEquals(DEFAULT_RADIO_MANIFACTURER,
|
||||
result.getProperties().get(FSInternetRadioBindingConstants.PROPERTY_MANUFACTURER));
|
||||
assertEquals(DEFAULT_RADIO_MODEL_NUMBER,
|
||||
result.getProperties().get(FSInternetRadioBindingConstants.PROPERTY_MODEL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify no discovery result for unknown device.
|
||||
*
|
||||
* @throws ValidationException
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
@Test
|
||||
public void noDiscoveryResultIfUnknown() throws MalformedURLException, ValidationException {
|
||||
RemoteDevice unknownRemoteDevice = createUnknownRemoteDevice();
|
||||
assertNull(discoveryParticipant.createResult(unknownRemoteDevice));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify valid DiscoveryResult with FSInterntRadio device without base URL.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@Test
|
||||
public void validDiscoveryResultIfWithoutBaseUrl() throws ValidationException {
|
||||
RemoteDevice fsInternetRadioDeviceWithoutUrl = createDefaultFSInternetRadioDevice(null);
|
||||
final DiscoveryResult result = discoveryParticipant.createResult(fsInternetRadioDeviceWithoutUrl);
|
||||
assertEquals(new ThingUID(DEFAULT_RADIO_THING_UID), result.getThingUID());
|
||||
assertEquals(FSInternetRadioBindingConstants.THING_TYPE_RADIO, result.getThingTypeUID());
|
||||
assertEquals(DEFAULT_RADIO_MANIFACTURER,
|
||||
result.getProperties().get(FSInternetRadioBindingConstants.PROPERTY_MANUFACTURER));
|
||||
assertEquals(DEFAULT_RADIO_MODEL_NUMBER,
|
||||
result.getProperties().get(FSInternetRadioBindingConstants.PROPERTY_MODEL));
|
||||
}
|
||||
|
||||
private RemoteDevice createDefaultFSInternetRadioDevice(URL baseURL) throws ValidationException {
|
||||
ManufacturerDetails manifacturerDetails = new ManufacturerDetails(DEFAULT_RADIO_MANIFACTURER);
|
||||
ModelDetails modelDetails = new ModelDetails(DEFAULT_RADIO_MODEL_NAME, DEFAULT_RADIO_MODEL_DESCRIPTION,
|
||||
DEFAULT_RADIO_MODEL_NUMBER);
|
||||
DeviceDetails deviceDetails = new DeviceDetails(baseURL, DEFAULT_RADIO_NAME, manifacturerDetails, modelDetails,
|
||||
DEFAULT_RADIO_SERIAL_NUMBER, DEFAULT_UPC, DEFAULT_URI);
|
||||
|
||||
final RemoteService remoteService = null;
|
||||
return new RemoteDevice(DEFAULT_RADIO_IDENTITY, DEFAULT_TYPE, deviceDetails, remoteService);
|
||||
}
|
||||
|
||||
private RemoteDevice createUnknownRemoteDevice() throws ValidationException, MalformedURLException {
|
||||
int deviceIdentityMaxAgeSeconds = 60;
|
||||
RemoteDeviceIdentity identity = new RemoteDeviceIdentity(new UDN("unknownUDN"), deviceIdentityMaxAgeSeconds,
|
||||
new URL("http://unknownDescriptorURL"), null, null);
|
||||
URL anotherBaseURL = new URL("http://unknownBaseUrl");
|
||||
String friendlyName = "Unknown remote device";
|
||||
ManufacturerDetails manifacturerDetails = new ManufacturerDetails("UnknownManifacturer");
|
||||
ModelDetails modelDetails = new ModelDetails("unknownModel");
|
||||
String serialNumber = "unknownSerialNumber";
|
||||
|
||||
DeviceDetails deviceDetails = new DeviceDetails(anotherBaseURL, friendlyName, manifacturerDetails, modelDetails,
|
||||
serialNumber, DEFAULT_UPC, DEFAULT_URI);
|
||||
|
||||
final RemoteService remoteService = null;
|
||||
return new RemoteDevice(identity, DEFAULT_TYPE, deviceDetails, remoteService);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants;
|
||||
import org.openhab.binding.fsinternetradio.internal.handler.FSInternetRadioHandler;
|
||||
import org.openhab.binding.fsinternetradio.internal.handler.HandlerUtils;
|
||||
import org.openhab.binding.fsinternetradio.internal.radio.FrontierSiliconRadio;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.items.DimmerItem;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.test.TestPortUtil;
|
||||
import org.openhab.core.test.TestServer;
|
||||
import org.openhab.core.test.java.JavaTest;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* OSGi tests for the {@link FSInternetRadioHandler}.
|
||||
*
|
||||
* @author Mihaela Memova - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated from Groovy to pure Java test, made more robust
|
||||
* @author Velin Yordanov - Migrated to mockito
|
||||
*
|
||||
*/
|
||||
public class FSInternetRadioHandlerJavaTest extends JavaTest {
|
||||
private static final String DEFAULT_TEST_THING_NAME = "testRadioThing";
|
||||
private static final String DEFAULT_TEST_ITEM_NAME = "testItem";
|
||||
private final String VOLUME = "volume";
|
||||
|
||||
// The request send for preset is "SET/netRemote.nav.action.selectPreset";
|
||||
private static final String PRESET = "Preset";
|
||||
private static final int TIMEOUT = 10 * 1000;
|
||||
private static final ThingTypeUID DEFAULT_THING_TYPE_UID = FSInternetRadioBindingConstants.THING_TYPE_RADIO;
|
||||
private static final ThingUID DEFAULT_THING_UID = new ThingUID(DEFAULT_THING_TYPE_UID, DEFAULT_TEST_THING_NAME);
|
||||
private static final RadioServiceDummy radioServiceDummy = new RadioServiceDummy();
|
||||
|
||||
/**
|
||||
* In order to test a specific channel, it is necessary to create a Thing with two channels - CHANNEL_POWER
|
||||
* and the tested channel. So before each test, the power channel is created and added
|
||||
* to an ArrayList of channels. Then in the tests an additional channel is created and added to the ArrayList
|
||||
* when it's needed.
|
||||
*/
|
||||
private Channel powerChannel;
|
||||
|
||||
private ThingHandlerCallback callback;
|
||||
|
||||
private static TestServer server;
|
||||
|
||||
/**
|
||||
* A HashMap which saves all the 'channel-acceppted_item_type' pairs.
|
||||
* It is set before all the tests.
|
||||
*/
|
||||
private static Map<String, String> acceptedItemTypes;
|
||||
|
||||
/**
|
||||
* ArrayList of channels which is used to initialize a radioThing in the test cases.
|
||||
*/
|
||||
private final List<Channel> channels = new ArrayList<>();
|
||||
|
||||
private FSInternetRadioHandler radioHandler;
|
||||
private Thing radioThing;
|
||||
|
||||
private static HttpClient httpClient;
|
||||
|
||||
// default configuration properties
|
||||
private static final String DEFAULT_CONFIG_PROPERTY_IP = "127.0.0.1";
|
||||
private static final String DEFAULT_CONFIG_PROPERTY_PIN = "1234";
|
||||
private static final int DEFAULT_CONFIG_PROPERTY_PORT = TestPortUtil.findFreePort();
|
||||
|
||||
/** The default refresh interval is 60 seconds. For the purposes of the tests it is set to 1 second */
|
||||
private static final String DEFAULT_CONFIG_PROPERTY_REFRESH = "1";
|
||||
private static final Configuration DEFAULT_COMPLETE_CONFIGURATION = createDefaultConfiguration();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpClass() throws Exception {
|
||||
ServletHolder holder = new ServletHolder(radioServiceDummy);
|
||||
server = new TestServer(DEFAULT_CONFIG_PROPERTY_IP, DEFAULT_CONFIG_PROPERTY_PORT, TIMEOUT, holder);
|
||||
setTheChannelsMap();
|
||||
server.startServer();
|
||||
httpClient = new HttpClient();
|
||||
httpClient.start();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
createThePowerChannel();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() throws Exception {
|
||||
server.stopServer();
|
||||
httpClient.stop();
|
||||
}
|
||||
|
||||
private static @NonNull Channel getChannel(final @NonNull Thing thing, final @NonNull String channelId) {
|
||||
final Channel channel = thing.getChannel(channelId);
|
||||
Assert.assertNotNull(channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
private static @NonNull ChannelUID getChannelUID(final @NonNull Thing thing, final @NonNull String channelId) {
|
||||
final ChannelUID channelUID = getChannel(thing, channelId).getUID();
|
||||
Assert.assertNotNull(channelUID);
|
||||
return channelUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify OFFLINE Thing status when the IP is NULL.
|
||||
*/
|
||||
@Test
|
||||
public void offlineIfNullIp() {
|
||||
Configuration config = createConfiguration(null, DEFAULT_CONFIG_PROPERTY_PIN,
|
||||
String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
|
||||
Thing radioThingWithNullIP = initializeRadioThing(config);
|
||||
testRadioThingConsideringConfiguration(radioThingWithNullIP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify OFFLINE Thing status when the PIN is empty String.
|
||||
*/
|
||||
@Test
|
||||
public void offlineIfEmptyPIN() {
|
||||
Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, "",
|
||||
String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
|
||||
Thing radioThingWithEmptyPIN = initializeRadioThing(config);
|
||||
testRadioThingConsideringConfiguration(radioThingWithEmptyPIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify OFFLINE Thing status when the PORT is zero.
|
||||
*/
|
||||
@Test
|
||||
public void offlineIfZeroPort() {
|
||||
Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, DEFAULT_CONFIG_PROPERTY_PIN, "0",
|
||||
DEFAULT_CONFIG_PROPERTY_REFRESH);
|
||||
Thing radioThingWithZeroPort = initializeRadioThing(config);
|
||||
testRadioThingConsideringConfiguration(radioThingWithZeroPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify OFFLINE Thing status when the PIN is wrong.
|
||||
*/
|
||||
@Test
|
||||
public void offlineIfWrongPIN() {
|
||||
final String wrongPin = "5678";
|
||||
Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, wrongPin,
|
||||
String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
|
||||
initializeRadioThing(config);
|
||||
waitForAssert(() -> {
|
||||
String exceptionMessage = "Radio does not allow connection, maybe wrong pin?";
|
||||
verifyCommunicationError(exceptionMessage);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify OFFLINE Thing status when the HTTP response cannot be parsed correctly.
|
||||
*/
|
||||
@Test
|
||||
public void offlineIfParseError() {
|
||||
// create a thing with two channels - the power channel and any of the others
|
||||
String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
|
||||
String acceptedItemType = acceptedItemTypes.get(modeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
|
||||
|
||||
/*
|
||||
* Setting the isInvalidResponseExpected variable to true
|
||||
* in order to get the incorrect XML response from the servlet
|
||||
*/
|
||||
radioServiceDummy.setInvalidResponse(true);
|
||||
|
||||
// try to handle a command
|
||||
radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
|
||||
|
||||
waitForAssert(() -> {
|
||||
String exceptionMessage = "java.io.IOException: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 2;";
|
||||
verifyCommunicationError(exceptionMessage);
|
||||
});
|
||||
radioServiceDummy.setInvalidResponse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the HTTP status is handled correctly when it is not OK_200.
|
||||
*/
|
||||
@Test
|
||||
public void httpStatusNokHandling() {
|
||||
// create a thing with two channels - the power channel and any of the others
|
||||
String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
|
||||
String acceptedItemType = acceptedItemTypes.get(modeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
// turn-on the radio
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
/*
|
||||
* Setting the needed boolean variable to false, so we can be sure
|
||||
* that the XML response won't have a OK_200 status
|
||||
*/
|
||||
ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
|
||||
Item modeTestItem = initializeItem(modeChannelUID, CHANNEL_MODE, acceptedItemType);
|
||||
|
||||
// try to handle a command
|
||||
radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertSame(UnDefType.NULL, modeTestItem.getState());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify ONLINE status of a Thing with complete configuration.
|
||||
*/
|
||||
@Test
|
||||
public void verifyOnlineStatus() {
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the power channel is updated.
|
||||
*/
|
||||
@Test
|
||||
public void powerChannelUpdated() {
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
ChannelUID powerChannelUID = powerChannel.getUID();
|
||||
initializeItem(powerChannelUID, DEFAULT_TEST_ITEM_NAME,
|
||||
acceptedItemTypes.get(FSInternetRadioBindingConstants.CHANNEL_POWER));
|
||||
|
||||
radioHandler.handleCommand(powerChannelUID, OnOffType.ON);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to turn on the radio",
|
||||
radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
radioHandler.handleCommand(powerChannelUID, OnOffType.OFF);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to turn off the radio",
|
||||
radioServiceDummy.containsRequestParameter(0, CHANNEL_POWER));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
/*
|
||||
* Setting the needed boolean variable to true, so we can be sure
|
||||
* that an invalid value will be returned in the XML response
|
||||
*/
|
||||
radioHandler.handleCommand(powerChannelUID, OnOffType.ON);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to turn on the radio",
|
||||
radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the mute channel is updated.
|
||||
*/
|
||||
@Test
|
||||
public void muteChhannelUpdated() {
|
||||
String muteChannelID = FSInternetRadioBindingConstants.CHANNEL_MUTE;
|
||||
String acceptedItemType = acceptedItemTypes.get(muteChannelID);
|
||||
createChannel(DEFAULT_THING_UID, muteChannelID, acceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID muteChannelUID = getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_MUTE);
|
||||
initializeItem(muteChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
|
||||
|
||||
radioHandler.handleCommand(muteChannelUID, OnOffType.ON);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to mute the radio",
|
||||
radioServiceDummy.containsRequestParameter(1, CHANNEL_MUTE));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
radioHandler.handleCommand(muteChannelUID, OnOffType.OFF);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to unmute the radio",
|
||||
radioServiceDummy.containsRequestParameter(0, CHANNEL_MUTE));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
/*
|
||||
* Setting the needed boolean variable to true, so we can be sure
|
||||
* that an invalid value will be returned in the XML response
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the mode channel is updated.
|
||||
*/
|
||||
@Test
|
||||
public void modeChannelUdpated() {
|
||||
String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
|
||||
String acceptedItemType = acceptedItemTypes.get(modeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
|
||||
initializeItem(modeChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
|
||||
|
||||
radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to update the mode channel correctly",
|
||||
radioServiceDummy.containsRequestParameter(1, CHANNEL_MODE));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
/*
|
||||
* Setting the needed boolean variable to true, so we can be sure
|
||||
* that an invalid value will be returned in the XML response
|
||||
*/
|
||||
radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("3"));
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to update the mode channel correctly",
|
||||
radioServiceDummy.containsRequestParameter(3, CHANNEL_MODE));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the volume is updated through the CHANNEL_VOLUME_ABSOLUTE using INCREASE and DECREASE commands.
|
||||
*/
|
||||
@Test
|
||||
public void volumechannelUpdatedAbsIncDec() {
|
||||
String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
|
||||
String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
|
||||
Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
|
||||
absoluteAcceptedItemType);
|
||||
|
||||
testChannelWithINCREASEAndDECREASECommands(absoluteVolumeChannelUID, volumeTestItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the volume is updated through the CHANNEL_VOLUME_ABSOLUTE using UP and DOWN commands.
|
||||
*/
|
||||
@Test
|
||||
public void volumeChannelUpdatedAbsUpDown() {
|
||||
String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
|
||||
String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
|
||||
Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
|
||||
absoluteAcceptedItemType);
|
||||
|
||||
testChannelWithUPAndDOWNCommands(absoluteVolumeChannelUID, volumeTestItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the invalid values when updating CHANNEL_VOLUME_ABSOLUTE are handled correctly.
|
||||
*/
|
||||
@Test
|
||||
public void invalidAbsVolumeValues() {
|
||||
String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
|
||||
String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
|
||||
initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME, absoluteAcceptedItemType);
|
||||
|
||||
// Trying to set a value that is greater than the maximum volume
|
||||
radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("36"));
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertTrue("The volume should not exceed the maximum value",
|
||||
radioServiceDummy.containsRequestParameter(32, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
// Trying to increase the volume more than its maximum value using the INCREASE command
|
||||
radioHandler.handleCommand(absoluteVolumeChannelUID, IncreaseDecreaseType.INCREASE);
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertTrue("The volume should not be increased above the maximum value",
|
||||
radioServiceDummy.areRequestParametersEmpty());
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
// Trying to increase the volume more than its maximum value using the UP command
|
||||
radioHandler.handleCommand(absoluteVolumeChannelUID, UpDownType.UP);
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertTrue("The volume should not be increased above the maximum value",
|
||||
radioServiceDummy.areRequestParametersEmpty());
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
// Trying to set a value that is lower than the minimum volume value
|
||||
radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("-10"));
|
||||
waitForAssert(() -> {
|
||||
assertTrue("The volume should not be decreased below 0",
|
||||
radioServiceDummy.containsRequestParameter(0, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
/*
|
||||
* Setting the needed boolean variable to true, so we can be sure
|
||||
* that an invalid value will be returned in the XML response
|
||||
*/
|
||||
|
||||
// trying to set the volume
|
||||
radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("15"));
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to set the volume correctly",
|
||||
radioServiceDummy.containsRequestParameter(15, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the volume is updated through the CHANNEL_VOLUME_PERCENT using INCREASE and DECREASE commands.
|
||||
*/
|
||||
@Test
|
||||
public void volumeChannelUpdatedPercIncDec() {
|
||||
/*
|
||||
* The volume is set through the CHANNEL_VOLUME_PERCENT in order to check if
|
||||
* the absolute volume will be updated properly.
|
||||
*/
|
||||
String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
|
||||
String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
|
||||
|
||||
String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
|
||||
String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
|
||||
Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
|
||||
absoluteAcceptedItemType);
|
||||
|
||||
ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
|
||||
|
||||
testChannelWithINCREASEAndDECREASECommands(percentVolumeChannelUID, volumeTestItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the volume is updated through the CHANNEL_VOLUME_PERCENT using UP and DOWN commands.
|
||||
*/
|
||||
@Test
|
||||
public void volumeChannelUpdatedPercUpDown() {
|
||||
/*
|
||||
* The volume is set through the CHANNEL_VOLUME_PERCENT in order to check if
|
||||
* the absolute volume will be updated properly.
|
||||
*/
|
||||
String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
|
||||
String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
|
||||
|
||||
String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
|
||||
String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
|
||||
Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
|
||||
absoluteAcceptedItemType);
|
||||
|
||||
ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
|
||||
|
||||
testChannelWithUPAndDOWNCommands(percentVolumeChannelUID, volumeTestItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the valid and invalid values when updating CHANNEL_VOLUME_PERCENT are handled correctly.
|
||||
*/
|
||||
@Test
|
||||
public void validInvalidPercVolume() {
|
||||
String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
|
||||
String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
|
||||
|
||||
String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
|
||||
String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
|
||||
createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
|
||||
initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME, absoluteAcceptedItemType);
|
||||
|
||||
ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
|
||||
|
||||
/*
|
||||
* Giving the handler a valid percent value. According to the FrontierSiliconRadio's
|
||||
* documentation 100 percents correspond to 32 absolute value
|
||||
*/
|
||||
radioHandler.handleCommand(percentVolumeChannelUID, PercentType.valueOf("50"));
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to set the volume correctly using percentages.",
|
||||
radioServiceDummy.containsRequestParameter(16, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
radioHandler.handleCommand(percentVolumeChannelUID, PercentType.valueOf("15"));
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to set the volume correctly using percentages.",
|
||||
radioServiceDummy.containsRequestParameter(4, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
}
|
||||
|
||||
private void testChannelWithINCREASEAndDECREASECommands(ChannelUID channelUID, Item item) {
|
||||
synchronized (channelUID) {
|
||||
// First we have to make sure that the item state is 0
|
||||
radioHandler.handleCommand(channelUID, DecimalType.valueOf("0"));
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to turn on the radio",
|
||||
radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
radioHandler.handleCommand(channelUID, IncreaseDecreaseType.INCREASE);
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to increase the volume correctly",
|
||||
radioServiceDummy.containsRequestParameter(1, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
radioHandler.handleCommand(channelUID, IncreaseDecreaseType.DECREASE);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to increase the volume correctly",
|
||||
radioServiceDummy.containsRequestParameter(0, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
// Trying to decrease one more time
|
||||
radioHandler.handleCommand(channelUID, IncreaseDecreaseType.DECREASE);
|
||||
waitForAssert(() -> {
|
||||
assertFalse("We should be able to decrease the volume correctly",
|
||||
radioServiceDummy.containsRequestParameter(0, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void testChannelWithUPAndDOWNCommands(ChannelUID channelUID, Item item) {
|
||||
synchronized (channelUID) {
|
||||
// First we have to make sure that the item state is 0
|
||||
radioHandler.handleCommand(channelUID, DecimalType.valueOf("0"));
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to turn on the radio",
|
||||
radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
radioHandler.handleCommand(channelUID, UpDownType.UP);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to increase the volume correctly",
|
||||
radioServiceDummy.containsRequestParameter(1, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
radioHandler.handleCommand(channelUID, UpDownType.DOWN);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to decrease the volume correctly",
|
||||
radioServiceDummy.containsRequestParameter(0, VOLUME));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
|
||||
// Trying to decrease one more time
|
||||
radioHandler.handleCommand(channelUID, UpDownType.DOWN);
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We shouldn't be able to decrease the volume below 0",
|
||||
radioServiceDummy.areRequestParametersEmpty());
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the preset channel is updated.
|
||||
*/
|
||||
@Test
|
||||
public void presetChannelUpdated() {
|
||||
String presetChannelID = FSInternetRadioBindingConstants.CHANNEL_PRESET;
|
||||
String acceptedItemType = acceptedItemTypes.get(presetChannelID);
|
||||
createChannel(DEFAULT_THING_UID, presetChannelID, acceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID presetChannelUID = getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_PRESET);
|
||||
initializeItem(presetChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
|
||||
|
||||
radioHandler.handleCommand(presetChannelUID, DecimalType.valueOf("100"));
|
||||
waitForAssert(() -> {
|
||||
assertTrue("We should be able to set value to the preset",
|
||||
radioServiceDummy.containsRequestParameter(100, PRESET));
|
||||
radioServiceDummy.clearRequestParameters();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the playInfoName channel is updated.
|
||||
*/
|
||||
@Test
|
||||
public void playInfoNameChannelUpdated() {
|
||||
String playInfoNameChannelID = FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME;
|
||||
String acceptedItemType = acceptedItemTypes.get(playInfoNameChannelID);
|
||||
createChannel(DEFAULT_THING_UID, playInfoNameChannelID, acceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThingWithMockedHandler(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
|
||||
ChannelUID playInfoNameChannelUID = getChannelUID(radioThing,
|
||||
FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME);
|
||||
initializeItem(playInfoNameChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
|
||||
|
||||
waitForAssert(() -> {
|
||||
verifyOnlineStatusIsSet();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the playInfoText channel is updated.
|
||||
*/
|
||||
@Test
|
||||
public void playInfoTextChannelUpdated() {
|
||||
String playInfoTextChannelID = FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT;
|
||||
String acceptedItemType = acceptedItemTypes.get(playInfoTextChannelID);
|
||||
createChannel(DEFAULT_THING_UID, playInfoTextChannelID, acceptedItemType);
|
||||
|
||||
Thing radioThing = initializeRadioThingWithMockedHandler(DEFAULT_COMPLETE_CONFIGURATION);
|
||||
testRadioThingConsideringConfiguration(radioThing);
|
||||
|
||||
turnTheRadioOn(radioThing);
|
||||
ChannelUID playInfoTextChannelUID = getChannelUID(radioThing,
|
||||
FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT);
|
||||
initializeItem(playInfoTextChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
|
||||
|
||||
waitForAssert(() -> {
|
||||
verifyOnlineStatusIsSet();
|
||||
});
|
||||
}
|
||||
|
||||
private static Configuration createDefaultConfiguration() {
|
||||
return createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, DEFAULT_CONFIG_PROPERTY_PIN,
|
||||
String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
|
||||
}
|
||||
|
||||
private static Configuration createConfiguration(String ip, String pin, String port, String refresh) {
|
||||
Configuration config = new Configuration();
|
||||
config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_IP, ip);
|
||||
config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PIN, pin);
|
||||
config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PORT, new BigDecimal(port));
|
||||
config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_REFRESH, new BigDecimal(refresh));
|
||||
return config;
|
||||
}
|
||||
|
||||
private static void setTheChannelsMap() {
|
||||
acceptedItemTypes = new HashMap<>();
|
||||
acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_POWER, "Switch");
|
||||
acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_MODE, "Number");
|
||||
acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_MUTE, "Switch");
|
||||
acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME, "String");
|
||||
acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT, "String");
|
||||
acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PRESET, "Number");
|
||||
acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE, "Number");
|
||||
acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT, "Dimmer");
|
||||
}
|
||||
|
||||
private void createThePowerChannel() {
|
||||
String powerChannelID = FSInternetRadioBindingConstants.CHANNEL_POWER;
|
||||
String acceptedItemType = acceptedItemTypes.get(powerChannelID);
|
||||
powerChannel = createChannel(DEFAULT_THING_UID, powerChannelID, acceptedItemType);
|
||||
}
|
||||
|
||||
private Item initializeItem(ChannelUID channelUID, String itemName, String acceptedItemType) {
|
||||
Item item = null;
|
||||
|
||||
switch (acceptedItemType) {
|
||||
case "Number":
|
||||
item = new NumberItem(itemName);
|
||||
break;
|
||||
|
||||
case "String":
|
||||
item = new StringItem(itemName);
|
||||
break;
|
||||
|
||||
case "Switch":
|
||||
item = new SwitchItem(itemName);
|
||||
break;
|
||||
|
||||
case "Dimmer":
|
||||
item = new DimmerItem(itemName);
|
||||
break;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private Channel createChannel(ThingUID thingUID, String channelID, String acceptedItemType) {
|
||||
ChannelUID channelUID = new ChannelUID(thingUID, channelID);
|
||||
|
||||
Channel radioChannel = ChannelBuilder.create(channelUID, acceptedItemType).build();
|
||||
channels.add(radioChannel);
|
||||
return radioChannel;
|
||||
}
|
||||
|
||||
private void testRadioThingConsideringConfiguration(Thing thing) {
|
||||
Configuration config = thing.getConfiguration();
|
||||
if (isConfigurationComplete(config)) {
|
||||
waitForAssert(() -> {
|
||||
verifyOnlineStatusIsSet();
|
||||
});
|
||||
} else {
|
||||
waitForAssert(() -> {
|
||||
verifyConfigurationError();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isConfigurationComplete(Configuration config) {
|
||||
String ip = (String) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_IP);
|
||||
BigDecimal port = (BigDecimal) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PORT.toString());
|
||||
String pin = (String) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PIN.toString());
|
||||
|
||||
if (ip == null || port.compareTo(BigDecimal.ZERO) == 0 || StringUtils.isEmpty(pin)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private Thing initializeRadioThing(Configuration config) {
|
||||
radioThing = ThingBuilder.create(DEFAULT_THING_TYPE_UID, DEFAULT_THING_UID).withConfiguration(config)
|
||||
.withChannels(channels).build();
|
||||
|
||||
callback = mock(ThingHandlerCallback.class);
|
||||
|
||||
radioHandler = new FSInternetRadioHandler(radioThing, httpClient);
|
||||
radioHandler.setCallback(callback);
|
||||
radioThing.setHandler(radioHandler);
|
||||
radioThing.getHandler().initialize();
|
||||
|
||||
return radioThing;
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private Thing initializeRadioThingWithMockedHandler(Configuration config) {
|
||||
radioThing = ThingBuilder.create(DEFAULT_THING_TYPE_UID, DEFAULT_THING_UID).withConfiguration(config)
|
||||
.withChannels(channels).build();
|
||||
|
||||
callback = mock(ThingHandlerCallback.class);
|
||||
|
||||
radioHandler = new MockedRadioHandler(radioThing, httpClient);
|
||||
radioHandler.setCallback(callback);
|
||||
radioThing.setHandler(radioHandler);
|
||||
radioThing.getHandler().initialize();
|
||||
|
||||
return radioThing;
|
||||
}
|
||||
|
||||
private void turnTheRadioOn(Thing radioThing) {
|
||||
radioHandler.handleCommand(getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_POWER),
|
||||
OnOffType.ON);
|
||||
|
||||
final FrontierSiliconRadio radio = HandlerUtils.getRadio(radioHandler);
|
||||
|
||||
waitForAssert(() -> {
|
||||
try {
|
||||
assertTrue(radio.getPower());
|
||||
} catch (IOException ex) {
|
||||
throw new AssertionError("I/O error", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void verifyOnlineStatusIsSet() {
|
||||
ThingStatusInfoBuilder statusBuilder = ThingStatusInfoBuilder.create(ThingStatus.ONLINE,
|
||||
ThingStatusDetail.NONE);
|
||||
ThingStatusInfo statusInfo = statusBuilder.withDescription(null).build();
|
||||
verify(callback, atLeast(1)).statusUpdated(radioThing, statusInfo);
|
||||
}
|
||||
|
||||
private void verifyConfigurationError() {
|
||||
ThingStatusInfoBuilder statusBuilder = ThingStatusInfoBuilder.create(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
ThingStatusInfo statusInfo = statusBuilder.withDescription("Configuration incomplete").build();
|
||||
verify(callback, atLeast(1)).statusUpdated(radioThing, statusInfo);
|
||||
}
|
||||
|
||||
private void verifyCommunicationError(String exceptionMessage) {
|
||||
ArgumentCaptor<ThingStatusInfo> captor = ArgumentCaptor.forClass(ThingStatusInfo.class);
|
||||
verify(callback, atLeast(1)).statusUpdated(isA(Thing.class), captor.capture());
|
||||
ThingStatusInfo status = captor.getValue();
|
||||
assertThat(status.getStatus(), is(ThingStatus.OFFLINE));
|
||||
assertThat(status.getStatusDetail(), is(ThingStatusDetail.COMMUNICATION_ERROR));
|
||||
assertThat(status.getDescription().contains(exceptionMessage), is(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.test;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.fsinternetradio.internal.handler.FSInternetRadioHandler;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* A mock of FSInternetRadioHandler to enable testing.
|
||||
*
|
||||
* @author Velin Yordanov - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MockedRadioHandler extends FSInternetRadioHandler {
|
||||
|
||||
public MockedRadioHandler(Thing thing, HttpClient client) {
|
||||
super(thing, client);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLinked(String channelUID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.fsinternetradio.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.fsinternetradio.internal.radio.FrontierSiliconRadioConstants;
|
||||
|
||||
/**
|
||||
* Radio service mock.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
* @author Velin Yordanov - Small adjustments
|
||||
*/
|
||||
public class RadioServiceDummy extends HttpServlet {
|
||||
private static Map<Integer, String> requestParameters = new ConcurrentHashMap<>();
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String MOCK_RADIO_PIN = "1234";
|
||||
|
||||
private static final String REQUEST_SET_POWER = "/" + FrontierSiliconRadioConstants.REQUEST_SET_POWER;
|
||||
private static final String REQUEST_GET_POWER = "/" + FrontierSiliconRadioConstants.REQUEST_GET_POWER;
|
||||
private static final String REQUEST_GET_MODE = "/" + FrontierSiliconRadioConstants.REQUEST_GET_MODE;
|
||||
private static final String REQUEST_SET_MODE = "/" + FrontierSiliconRadioConstants.REQUEST_SET_MODE;
|
||||
private static final String REQUEST_SET_VOLUME = "/" + FrontierSiliconRadioConstants.REQUEST_SET_VOLUME;
|
||||
private static final String REQUEST_GET_VOLUME = "/" + FrontierSiliconRadioConstants.REQUEST_GET_VOLUME;
|
||||
private static final String REQUEST_SET_MUTE = "/" + FrontierSiliconRadioConstants.REQUEST_SET_MUTE;
|
||||
private static final String REQUEST_GET_MUTE = "/" + FrontierSiliconRadioConstants.REQUEST_GET_MUTE;
|
||||
private static final String REQUEST_SET_PRESET_ACTION = "/"
|
||||
+ FrontierSiliconRadioConstants.REQUEST_SET_PRESET_ACTION;
|
||||
private static final String REQUEST_GET_PLAY_INFO_TEXT = "/"
|
||||
+ FrontierSiliconRadioConstants.REQUEST_GET_PLAY_INFO_TEXT;
|
||||
private static final String REQUEST_GET_PLAY_INFO_NAME = "/"
|
||||
+ FrontierSiliconRadioConstants.REQUEST_GET_PLAY_INFO_NAME;
|
||||
private static final String VALUE = "value";
|
||||
|
||||
/*
|
||||
* For the purposes of the tests it is assumed that the current station and the additional information
|
||||
* are always the same (random_station and additional_info)
|
||||
*/
|
||||
private final String playInfoNameValue = "random_station";
|
||||
private final String playInfoNameTag = makeC8_arrayTag(playInfoNameValue);
|
||||
|
||||
private final String playInfoTextValue = "additional_info";
|
||||
private final String playInfoTextTag = makeC8_arrayTag(playInfoTextValue);
|
||||
|
||||
private final int httpStatus;
|
||||
|
||||
private String tagToReturn = "";
|
||||
private String responseToReturn = "";
|
||||
|
||||
private boolean isInvalidResponseExpected;
|
||||
private boolean isInvalidValueExpected;
|
||||
private boolean isOKAnswerExpected = true;
|
||||
|
||||
private String powerValue;
|
||||
private String powerTag = "";
|
||||
|
||||
private String muteValue;
|
||||
private String muteTag = "";
|
||||
|
||||
private String absoluteVolumeValue;
|
||||
private String absoluteVolumeTag = "";
|
||||
|
||||
private String modeValue;
|
||||
private String modeTag = "";
|
||||
|
||||
private String radioStation = "";
|
||||
|
||||
public RadioServiceDummy() {
|
||||
this.httpStatus = HttpStatus.OK_200;
|
||||
}
|
||||
|
||||
public String getRadioStation() {
|
||||
return radioStation;
|
||||
}
|
||||
|
||||
public void setRadioStation(final String radioStation) {
|
||||
this.radioStation = radioStation;
|
||||
}
|
||||
|
||||
public void setInvalidResponseExpected(boolean isInvalidResponseExpected) {
|
||||
this.isInvalidResponseExpected = isInvalidResponseExpected;
|
||||
}
|
||||
|
||||
public void setOKAnswerExpected(boolean isOKAnswerExpected) {
|
||||
this.isOKAnswerExpected = isOKAnswerExpected;
|
||||
}
|
||||
|
||||
public boolean containsRequestParameter(int value, String parameter) {
|
||||
String url = requestParameters.get(value);
|
||||
if (url == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.contains(parameter);
|
||||
}
|
||||
|
||||
public void clearRequestParameters() {
|
||||
requestParameters.clear();
|
||||
}
|
||||
|
||||
public boolean areRequestParametersEmpty() {
|
||||
return requestParameters.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String queryString = request.getQueryString();
|
||||
Collection<String> requestParameterNames = Collections.list(request.getParameterNames());
|
||||
if (queryString != null && requestParameterNames.contains(VALUE)) {
|
||||
StringBuffer fullUrl = request.getRequestURL().append("?").append(queryString);
|
||||
int value = Integer.parseInt(request.getParameter(VALUE));
|
||||
requestParameters.put(value, fullUrl.toString());
|
||||
}
|
||||
|
||||
String pin = request.getParameter("pin");
|
||||
if (!MOCK_RADIO_PIN.equals(pin)) {
|
||||
response.setStatus(HttpStatus.FORBIDDEN_403);
|
||||
} else if (!isOKAnswerExpected) {
|
||||
response.setStatus(HttpStatus.NOT_FOUND_404);
|
||||
} else {
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
response.setContentType("text/xml");
|
||||
String commandString = request.getPathInfo();
|
||||
|
||||
switch (commandString) {
|
||||
case (REQUEST_SET_POWER):
|
||||
if (isInvalidValueExpected) {
|
||||
powerValue = null;
|
||||
} else {
|
||||
powerValue = request.getParameter(VALUE);
|
||||
}
|
||||
|
||||
case (REQUEST_GET_POWER):
|
||||
powerTag = makeU8Tag(powerValue);
|
||||
tagToReturn = powerTag;
|
||||
break;
|
||||
|
||||
case (REQUEST_SET_MUTE):
|
||||
if (isInvalidValueExpected) {
|
||||
muteValue = null;
|
||||
} else {
|
||||
muteValue = request.getParameter(VALUE);
|
||||
}
|
||||
|
||||
case (REQUEST_GET_MUTE):
|
||||
muteTag = makeU8Tag(muteValue);
|
||||
tagToReturn = muteTag;
|
||||
break;
|
||||
|
||||
case (REQUEST_SET_MODE):
|
||||
if (isInvalidValueExpected) {
|
||||
modeValue = null;
|
||||
} else {
|
||||
modeValue = request.getParameter(VALUE);
|
||||
}
|
||||
|
||||
case (REQUEST_GET_MODE):
|
||||
modeTag = makeU32Tag(modeValue);
|
||||
tagToReturn = modeTag;
|
||||
break;
|
||||
|
||||
case (REQUEST_SET_VOLUME):
|
||||
if (isInvalidValueExpected) {
|
||||
absoluteVolumeValue = null;
|
||||
} else {
|
||||
absoluteVolumeValue = request.getParameter(VALUE);
|
||||
}
|
||||
|
||||
case (REQUEST_GET_VOLUME):
|
||||
absoluteVolumeTag = makeU8Tag(absoluteVolumeValue);
|
||||
tagToReturn = absoluteVolumeTag;
|
||||
break;
|
||||
|
||||
case (REQUEST_SET_PRESET_ACTION):
|
||||
final String station = request.getParameter(VALUE);
|
||||
setRadioStation(station);
|
||||
break;
|
||||
|
||||
case (REQUEST_GET_PLAY_INFO_NAME):
|
||||
tagToReturn = playInfoNameTag;
|
||||
break;
|
||||
|
||||
case (REQUEST_GET_PLAY_INFO_TEXT):
|
||||
tagToReturn = playInfoTextTag;
|
||||
break;
|
||||
|
||||
default:
|
||||
tagToReturn = "";
|
||||
break;
|
||||
}
|
||||
|
||||
if (isInvalidResponseExpected) {
|
||||
responseToReturn = makeInvalidXMLResponse();
|
||||
} else {
|
||||
responseToReturn = makeValidXMLResponse();
|
||||
}
|
||||
PrintWriter out = response.getWriter();
|
||||
out.print(responseToReturn);
|
||||
}
|
||||
}
|
||||
|
||||
protected String makeU8Tag(final String value) {
|
||||
return String.format("<value><u8>%s</u8></value>", value);
|
||||
}
|
||||
|
||||
protected String makeU32Tag(final String value) {
|
||||
return String.format("<value><u32>%s</u32></value>", value);
|
||||
}
|
||||
|
||||
protected String makeC8_arrayTag(final String value) {
|
||||
return String.format("<value><c8_array>%s</c8_array></value>", value);
|
||||
}
|
||||
|
||||
private String makeValidXMLResponse() throws IOException {
|
||||
return IOUtils.toString(getClass().getResourceAsStream("/validXml.xml"));
|
||||
}
|
||||
|
||||
private String makeInvalidXMLResponse() throws IOException {
|
||||
return IOUtils.toString(getClass().getResourceAsStream("/invalidXml.xml"));
|
||||
}
|
||||
|
||||
public void setInvalidResponse(boolean value) {
|
||||
isInvalidResponseExpected = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<--xmmmmt version="1.0" encoding="UTF-8"?>
|
||||
<pre>
|
||||
<sessionId>111</sessionId>
|
||||
<xmp>
|
||||
<fsapiResponse>
|
||||
<status>FS_OK</status>
|
||||
</fsapiResponse>
|
||||
</xmp>
|
||||
</pre>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<pre>
|
||||
<sessionId>111</sessionId>
|
||||
<xmp>
|
||||
<fsapiResponse>
|
||||
<value>
|
||||
<u8>1</u8>
|
||||
</value>
|
||||
<status>FS_OK</status>
|
||||
</fsapiResponse>
|
||||
</xmp>
|
||||
</pre>
|
||||
Reference in New Issue
Block a user