Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[radiobrowser] Initial contribution #16392

Merged
merged 47 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2f04cf1
initial code
Skinah Feb 6, 2024
085bd4e
working
Skinah Feb 7, 2024
4360614
improvements
Skinah Feb 8, 2024
36474ac
Add genres
Skinah Feb 10, 2024
e429766
Add more genres
Skinah Feb 10, 2024
50a1972
improvements
Skinah Feb 10, 2024
4523a37
basic readme
Skinah Feb 10, 2024
e468c51
remove NPE warns and update readme.
Skinah Feb 11, 2024
052e229
cleanup for merge
Skinah Feb 11, 2024
a88cd0d
spotless fix
Skinah Feb 11, 2024
90efcb1
bug fixes
Skinah Feb 11, 2024
132ae9d
Station searches added to the readme
Skinah Feb 11, 2024
d744d87
minor changes
Skinah Feb 12, 2024
ab25817
remove commented out code
Skinah Feb 12, 2024
8bc57ee
Order by votes
Skinah Feb 12, 2024
736b2a9
review changes 1
Skinah Feb 13, 2024
d54f4bc
Update bundles/org.openhab.binding.radiobrowser/README.md
Skinah Feb 13, 2024
debc44b
Update bundles/org.openhab.binding.radiobrowser/README.md
Skinah Feb 13, 2024
fc4087f
Update bundles/org.openhab.binding.radiobrowser/README.md
Skinah Feb 13, 2024
ff45025
Update bundles/org.openhab.binding.radiobrowser/src/main/java/org/ope…
Skinah Feb 13, 2024
877d176
Update bundles/org.openhab.binding.radiobrowser/src/main/java/org/ope…
Skinah Feb 13, 2024
2d3dad7
changes 2
Skinah Feb 13, 2024
e12668b
Merge branch 'radio' of https://github.com/Skinah/openhab-addons into…
Skinah Feb 13, 2024
05d38a9
Update bundles/org.openhab.binding.radiobrowser/src/main/resources/OH…
Skinah Feb 13, 2024
79b2174
fix pom
Skinah Feb 13, 2024
9533c8c
Merge branch 'radio' of https://github.com/Skinah/openhab-addons into…
Skinah Feb 13, 2024
d36bbb3
Update bundles/org.openhab.binding.radiobrowser/src/main/java/org/ope…
Skinah Feb 13, 2024
7e03ac1
Update bundles/org.openhab.binding.radiobrowser/src/main/java/org/ope…
Skinah Feb 13, 2024
e4836f0
review changes 3
Skinah Feb 13, 2024
d05a2f1
Update bundles/org.openhab.binding.radiobrowser/src/main/java/org/ope…
Skinah Feb 13, 2024
7a145d3
more changes
Skinah Feb 13, 2024
b6d86f1
remove unused channels
Skinah Feb 13, 2024
5a6f56e
spotless
Skinah Feb 13, 2024
391e53d
use http
Skinah Feb 13, 2024
dbfc8b9
Merge branch 'radio' of https://github.com/Skinah/openhab-addons into…
Skinah Feb 13, 2024
a95c8ed
bug fix
Skinah Feb 13, 2024
4d10856
Update bundles/org.openhab.binding.radiobrowser/README.md
Skinah Feb 13, 2024
70f6ff6
Add recent and HTTPS
Skinah Feb 14, 2024
66fbacb
Merge branch 'radio' of https://github.com/Skinah/openhab-addons into…
Skinah Feb 14, 2024
5325554
update readme
Skinah Feb 14, 2024
ec74193
Update bundles/org.openhab.binding.radiobrowser/src/main/resources/OH…
Skinah Feb 15, 2024
42966a8
bug fixes for forum reported bugs.
Skinah Feb 17, 2024
7760c3c
Merge branch 'radio' of https://github.com/Skinah/openhab-addons into…
Skinah Feb 17, 2024
d3badcc
Add more genres
Skinah Feb 17, 2024
77935f9
update readme
Skinah Feb 17, 2024
fd738cc
Remove multiple stations to give cleaner list.
Skinah Feb 17, 2024
1d363f3
Update station ch when recent station is selected.
Skinah Feb 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@
/bundles/org.openhab.binding.pushsafer/ @appzer @cweitkamp
/bundles/org.openhab.binding.qbus/ @QbusKoen
/bundles/org.openhab.binding.qolsysiq/ @digitaldan
/bundles/org.openhab.binding.radiobrowser/ @skinah
/bundles/org.openhab.binding.radiothermostat/ @mlobstein
/bundles/org.openhab.binding.regoheatpump/ @crnjan
/bundles/org.openhab.binding.revogi/ @andibraeu
Expand Down
5 changes: 5 additions & 0 deletions bom/openhab-addons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,11 @@
<artifactId>org.openhab.binding.qolsysiq</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.radiobrowser</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.radiothermostat</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.radiobrowser/NOTICE
Original file line number Diff line number Diff line change
@@ -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
84 changes: 84 additions & 0 deletions bundles/org.openhab.binding.radiobrowser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Radio Browser Binding

Radio Browser is a community driven database of internet radio and TV stations, that has an open API that is free for all to use and has multiple servers to ensure up time is high.
With this binding you can use their database of more than 45,000 stations to apply filters and quickly find internet radio streams that interest you.

## Supported Things

- `radio`: Add one of these manually, and it should come online after fetching language and country data to populate the filter channels.

### `radio` Thing Configuration

| Name | Type | Description | Default | Required | Advanced |
|---------------|---------|--------------------------------------------------------------------------------------------|----------|----------|----------|
| filters | text | Allows you to specify your own filters from the `advanced search` of the API. | See below | yes | no |
| clicks | boolean | Helps to support the server recommend good results. | true | yes | yes |
| languageCount | integer | If you want less languages to be shown as a filter, you can raise this or create your own. | 14 | yes | yes |
| recentLimit | integer | Limit the number of stations in the recent channel list. `0` Disables the feature. | 5 | yes | no |

## Filters Configuration

The `advanced` configuration parameter `filters` can be used to limit the stations based on different fields like codecs, minimum quality, ordering and more.
All possible filter options are listed here <https://de1.api.radio-browser.info/#Advanced_station_search>
The default filter (below) can be changed to suit your needs.

```text
hidebroken=true,limit=1700,reverse=true,order=votes
```

You can also try out the various search features on their main website, and then copy what is added to the address bar of your web browser.
<https://www.radio-browser.info/>

## State Option Metadata

If you wish to display only a couple of languages or custom choices to any of the filters, you can create your own using metadata>state options.
The countries need to be the country code not the full name, for example `US` and not `The United States of America`.
The binding will auto select your country based on openHAB's settings that you made when setting up openHAB.
It makes sense to do this for languages if the built in way of `languageCount` does not work for your use case.
Genres are a good example for using the metadata, only show the styles of music and other tags that you like.
If in doubt you can use the [Event Monitor in the Developer Sidebar](https://www.openhab.org/docs/tutorial/tips-and-tricks.html#event-monitor) to watch what commands are sent to the bindings channels.

## Channels

| Channel | Type | Read/Write | Description |
|-----------|--------|------------|-------------------------------------------------------------------------------------------------------------|
| country | String | RW | This allows you to only find stations in ALL or a country of your choice. |
| state | String | RW | When a country is selected, this will auto populate with states that are in your country. |
| language | String | RW | You can limit the stations to only be in your language, or you can also use Metadata to set your own list. |
| genre | String | RW | A list of common genres to help you find a station you like. State Options Metadata allows you to change this. |
| station | String | RW | These are the search results back from the database that match your filter settings. |
| stream | String | RW | This is the URL for the selected station. |
| name | String | RW | This is the name of the selected station. |
| icon | String | RW | This is the icon for the selected station if available in their database. |
| recent | String | RW | Records the last stations you selected to make them easier to find. A config allows this list to be longer. |

## Using the Stream URL

You can send the `stream` channel that holds a URL for a stream to the `playuri` channel of the [Chromecast Binding](https://www.openhab.org/addons/bindings/chromecast/#channels) or the `stream` channel of the [Squeezebox Binding](https://www.openhab.org/addons/bindings/squeezebox/#player-channels).
To make this easier without needing to setup a rule to forward the stream, you can use this [widget found in the marketplace](https://community.openhab.org/t/radio-browser-basic-widget-for-finding-internet-radio-streams-with-the-ui/153783) without needing to create any rules.
The widget is probably the easiest way to get started and have a play with what is possible.

## Station Searches

Searches can be done in a few different ways and since the binding will auto select the first result, you can change what is sent to the top of the list by changing the filters config from including `order=clickcount` (default) to `votes`, `clicktrend` or even `random`.

Examples on how to do searches from rules, or you can also change an item to take input by using `oh-input-item` using metadata called `Default list item widget`.


Search for all stations that contain `hit` in their name, and auto select the first result.

```
Radio_Station.sendCommand("hit")
```

Search and auto select the station if you know the UUID from the website.

```
Radio_Station.sendCommand("b6a490e8-f498-4a7c-b024-607b3d997614")
```

Clear any manual search results using the above two methods, and `REFRESH` back to using the normal filter channels.

```
Radio_Station.sendCommand(REFRESH)
```
17 changes: 17 additions & 0 deletions bundles/org.openhab.binding.radiobrowser/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>4.2.0-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.binding.radiobrowser</artifactId>

<name>openHAB Add-ons :: Bundles :: RadioBrowser Binding</name>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.radiobrowser-${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-radiobrowser" description="RadioBrowser Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.radiobrowser/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2024 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.radiobrowser.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;

/**
* The {@link RadioBrowserBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class RadioBrowserBindingConstants {
private static final String BINDING_ID = "radiobrowser";
public static final int HTTP_TIMEOUT_SECONDS = 10;
public static final String ALL_SERVERS = "all.api.radio-browser.info";

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_RADIO = new ThingTypeUID(BINDING_ID, "radio");

// List of all Channel ids
public static final String CHANNEL_COUNTRY = "country";
public static final String CHANNEL_LANGUAGE = "language";
public static final String CHANNEL_STATE = "state";
public static final String CHANNEL_GENRE = "genre";
public static final String CHANNEL_STATION = "station";
public static final String CHANNEL_NAME = "name";
public static final String CHANNEL_ICON = "icon";
public static final String CHANNEL_STREAM = "stream";
public static final String CHANNEL_RECENT = "recent";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2024 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.radiobrowser.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* The {@link RadioBrowserConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class RadioBrowserConfiguration {
public boolean clicks = true;
public int languageCount;
public String filters = "";
public int recentLimit;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/**
* Copyright (c) 2010-2024 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.radiobrowser.internal;

import static org.openhab.binding.radiobrowser.internal.RadioBrowserBindingConstants.*;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.radiobrowser.internal.api.ApiException;
import org.openhab.binding.radiobrowser.internal.api.RadioBrowserApi;
import org.openhab.binding.radiobrowser.internal.api.RadioBrowserJson.Country;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link RadioBrowserHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class RadioBrowserHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final LocaleProvider localeProvider;
public final RadioBrowserStateDescriptionProvider stateDescriptionProvider;
public RadioBrowserConfiguration config = new RadioBrowserConfiguration();
private RadioBrowserApi radioBrowserApi;
private @Nullable ScheduledFuture<?> reconnectFuture = null;

public RadioBrowserHandler(Thing thing, HttpClient httpClient,
RadioBrowserStateDescriptionProvider stateDescriptionProvider, LocaleProvider localeProvider) {
super(thing);
this.localeProvider = localeProvider;
this.stateDescriptionProvider = stateDescriptionProvider;
radioBrowserApi = new RadioBrowserApi(this, httpClient);
}

public void setChannelState(String channelToUpdate, State valueOf) {
updateState(channelToUpdate, valueOf);
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
try {
if (command instanceof RefreshType) {
switch (channelUID.getId()) {
case CHANNEL_STATION:
radioBrowserApi.updateStations();
return;
}
} else if (command instanceof StringType) {
lsiepel marked this conversation as resolved.
Show resolved Hide resolved
switch (channelUID.getId()) {
case CHANNEL_LANGUAGE:
radioBrowserApi.setLanguage(command.toString());
return;
case CHANNEL_COUNTRY:
radioBrowserApi.setCountry(command.toString());
return;
case CHANNEL_STATE:
radioBrowserApi.setState(command.toString());
return;
case CHANNEL_GENRE:
radioBrowserApi.setGenre(command.toString());
return;
case CHANNEL_RECENT:
case CHANNEL_STATION:
radioBrowserApi.selectStation(command.toString());
return;
}
}
} catch (ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
Future<?> future = reconnectFuture;
if (future == null) {
// reconnect every 3 mins, but try in 30 seconds time in case its only 1 of 5 servers down.
reconnectFuture = scheduler.scheduleWithFixedDelay(this::reconnect, 30, 180, TimeUnit.SECONDS);
}
}
}

private void reconnect() {
try {
// Will look up and randomly connect to one of the servers
radioBrowserApi.initialize();
updateStatus(ThingStatus.ONLINE);
updateState(CHANNEL_STATION, new StringType());
updateState(CHANNEL_STATE, new StringType());
updateState(CHANNEL_LANGUAGE, new StringType());
updateState(CHANNEL_GENRE, new StringType());
String countryCode = localeProvider.getLocale().getCountry();
Country localCountry = radioBrowserApi.countryMap.get(countryCode);
if (localCountry != null) {
updateState(CHANNEL_COUNTRY, new StringType(localCountry.name));
radioBrowserApi.setCountry(countryCode);
} else {
logger.debug(
"The binding could not auto discover your country, check openHAB has a country setup in the settings");
}

Future<?> future = reconnectFuture;
if (future != null) {
future.cancel(false);// don't interrupt as we are running it right now
reconnectFuture = null;
}
} catch (ApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}

private boolean buildFilters() {
if (!config.filters.contains("=") || config.filters.startsWith("?") || config.filters.contains(" ")
|| config.filters.startsWith(" ")) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Please update your filters config to the correct key=value,key2=value2 format");
return false;
}
String builtFilters = "";
List<String> filterList = Arrays.asList(config.filters.split(","));
for (String filter : filterList) {
if (builtFilters.isEmpty()) {
builtFilters = "?" + filter;
} else {
builtFilters = builtFilters + "&" + filter;
}
}
// over write the config with fixed copy, existing code keep working
config.filters = builtFilters;
return true;
}

@Override
public void initialize() {
config = getConfigAs(RadioBrowserConfiguration.class);
updateStatus(ThingStatus.UNKNOWN);
if (buildFilters()) {
// First time connecting, try again in 60 seconds to try another random server out of 5? possible ones
reconnectFuture = scheduler.scheduleWithFixedDelay(this::reconnect, 0, 60, TimeUnit.SECONDS);
}
}

@Override
public void dispose() {
Future<?> future = reconnectFuture;
if (future != null) {
future.cancel(true);
reconnectFuture = null;
}
}
}
Loading
Loading