diff --git a/CODEOWNERS b/CODEOWNERS
index 6c422679b4042..0e3bb35161238 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -292,6 +292,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
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 168d221f5b879..4e0e91890ebb7 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -1446,6 +1446,11 @@
org.openhab.binding.qolsysiq${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.radiobrowser
+ ${project.version}
+ org.openhab.addons.bundlesorg.openhab.binding.radiothermostat
diff --git a/bundles/org.openhab.binding.radiobrowser/NOTICE b/bundles/org.openhab.binding.radiobrowser/NOTICE
new file mode 100644
index 0000000000000..38d625e349232
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/NOTICE
@@ -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
diff --git a/bundles/org.openhab.binding.radiobrowser/README.md b/bundles/org.openhab.binding.radiobrowser/README.md
new file mode 100644
index 0000000000000..0f6d9a677ffaa
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/README.md
@@ -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
+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.
+
+
+## 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)
+```
diff --git a/bundles/org.openhab.binding.radiobrowser/pom.xml b/bundles/org.openhab.binding.radiobrowser/pom.xml
new file mode 100644
index 0000000000000..d12b2fb3914f3
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.2.0-SNAPSHOT
+
+
+ org.openhab.binding.radiobrowser
+
+ openHAB Add-ons :: Bundles :: RadioBrowser Binding
+
+
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/feature/feature.xml b/bundles/org.openhab.binding.radiobrowser/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..56f0da84588cc
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.radiobrowser/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserBindingConstants.java b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserBindingConstants.java
new file mode 100644
index 0000000000000..ec83e9c9ab371
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserBindingConstants.java
@@ -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";
+}
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserConfiguration.java b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserConfiguration.java
new file mode 100644
index 0000000000000..87256ddc6f5ba
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserConfiguration.java
@@ -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;
+}
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserHandler.java b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserHandler.java
new file mode 100644
index 0000000000000..846f138b51478
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserHandler.java
@@ -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) {
+ 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 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;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserHandlerFactory.java b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserHandlerFactory.java
new file mode 100644
index 0000000000000..62e911f7b9a26
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserHandlerFactory.java
@@ -0,0 +1,72 @@
+/**
+ * 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.THING_TYPE_RADIO;
+
+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.core.i18n.LocaleProvider;
+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 RadioBrowserHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Matthew Skinner - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.radiobrowser", service = ThingHandlerFactory.class)
+public class RadioBrowserHandlerFactory extends BaseThingHandlerFactory {
+ private final HttpClient httpClient;
+ private final LocaleProvider localeProvider;
+ private final RadioBrowserStateDescriptionProvider stateDescriptionProvider;
+
+ @Activate
+ public RadioBrowserHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
+ final @Reference RadioBrowserStateDescriptionProvider stateDescriptionProvider,
+ @Reference LocaleProvider localeProvider) {
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ this.localeProvider = localeProvider;
+ this.stateDescriptionProvider = stateDescriptionProvider;
+ }
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_RADIO);
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (THING_TYPE_RADIO.equals(thingTypeUID)) {
+ return new RadioBrowserHandler(thing, httpClient, stateDescriptionProvider, localeProvider);
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserStateDescriptionProvider.java b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserStateDescriptionProvider.java
new file mode 100644
index 0000000000000..e87e920d8de9e
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/RadioBrowserStateDescriptionProvider.java
@@ -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.events.EventPublisher;
+import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
+import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
+import org.openhab.core.thing.link.ItemChannelLinkRegistry;
+import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link RadioBrowserStateDescriptionProvider} Allows the dynamic updating of Radio Stations and Filters
+ * from the RadioStation Server API.
+ *
+ * @author Matthew Skinner - Initial contribution
+ */
+@Component(service = { DynamicStateDescriptionProvider.class, RadioBrowserStateDescriptionProvider.class })
+@NonNullByDefault
+public class RadioBrowserStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
+
+ @Activate
+ public RadioBrowserStateDescriptionProvider(final @Reference EventPublisher eventPublisher, //
+ final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
+ final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
+ this.eventPublisher = eventPublisher;
+ this.itemChannelLinkRegistry = itemChannelLinkRegistry;
+ this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
+ }
+}
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/ApiException.java b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/ApiException.java
new file mode 100644
index 0000000000000..7aafeeb3f9373
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/ApiException.java
@@ -0,0 +1,41 @@
+/**
+ * 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.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ApiException} gets thrown when something happens wrong with the API
+ *
+ * @author Matthew Skinner - Initial contribution
+ */
+@NonNullByDefault
+public class ApiException extends Exception {
+ /**
+ * Serial ID of this error class.
+ */
+ private static final long serialVersionUID = 1638226795216449L;
+
+ /**
+ * Basic constructor allowing the storing of a single message.
+ *
+ * @param message Descriptive message about the error.
+ */
+ public ApiException(String message) {
+ super(message);
+ }
+
+ public ApiException(String message, Throwable e) {
+ super(message, e);
+ }
+}
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/RadioBrowserApi.java b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/RadioBrowserApi.java
new file mode 100644
index 0000000000000..80470b7ffce00
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/RadioBrowserApi.java
@@ -0,0 +1,378 @@
+/**
+ * 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.api;
+
+import static org.openhab.binding.radiobrowser.internal.RadioBrowserBindingConstants.*;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.URLEncoder;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.radiobrowser.internal.RadioBrowserHandler;
+import org.openhab.binding.radiobrowser.internal.api.RadioBrowserJson.Country;
+import org.openhab.binding.radiobrowser.internal.api.RadioBrowserJson.Language;
+import org.openhab.binding.radiobrowser.internal.api.RadioBrowserJson.State;
+import org.openhab.binding.radiobrowser.internal.api.RadioBrowserJson.Station;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.StateOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link RadioBrowserApi} Handles all http calls to and from the Radio Stations API.
+ *
+ * @author Matthew Skinner - Initial contribution
+ */
+@NonNullByDefault
+public class RadioBrowserApi {
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+ private final RadioBrowserHandler handler;
+ private final HttpClient httpClient;
+ private final Gson gson = new Gson();
+ private String server = "";
+ private String language = "";
+ private String countryCode = "";
+ private String state = "";
+ private String genre = "";
+ private Map stationMap = new HashMap<>();
+ public Map countryMap = new HashMap<>();
+ private List recentOptions = new ArrayList<>();
+
+ public RadioBrowserApi(RadioBrowserHandler handler, HttpClient httpClient) {
+ this.handler = handler;
+ this.httpClient = httpClient;
+ }
+
+ private String sendGetRequest(String url) throws ApiException {
+ Request request;
+ String errorReason = "";
+ request = httpClient.newRequest("https://" + server + url).scheme("https");
+ request.header("Host", server);
+ request.header("User-Agent", "openHAB4/RadioBrowserBinding");// api requirement
+ request.header("Connection", "Keep-Alive");
+ request.timeout(HTTP_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ request.method(HttpMethod.GET);
+ logger.debug("Sending GET:{}", url);
+
+ try {
+ ContentResponse contentResponse = request.send();
+ if (contentResponse.getStatus() == 200) {
+ return contentResponse.getContentAsString();
+ } else {
+ errorReason = String.format("GET request failed with %d: %s", contentResponse.getStatus(),
+ contentResponse.getContentAsString());
+ }
+ } catch (TimeoutException e) {
+ errorReason = "TimeoutException: Server was not reachable on your network";
+ } catch (ExecutionException e) {
+ errorReason = String.format("ExecutionException: %s", e.getMessage());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ errorReason = String.format("InterruptedException: %s", e.getMessage());
+ }
+ throw new ApiException(errorReason);
+ }
+
+ private List getServers() throws ApiException {
+ List listResult = new ArrayList<>();
+ try {
+ // add all round robin servers one by one
+ InetAddress[] list = InetAddress.getAllByName(ALL_SERVERS);
+ for (InetAddress item : list) {
+ listResult.add(item.getCanonicalHostName());
+ }
+ // Requirement of using the API is to spread the load and either let user select or random
+ Random rand = new Random();
+ server = listResult.get(rand.nextInt(listResult.size()));
+ } catch (UnknownHostException e) {
+ throw new ApiException("Unknown host");
+ }
+ return listResult;
+ }
+
+ private List getStates() throws ApiException {
+ try {
+ Country localCountry = countryMap.get(countryCode);
+ if (localCountry == null) {
+ return new ArrayList<>();
+ }
+ String returnContent = sendGetRequest("/json/states/"
+ + URLEncoder.encode(localCountry.name, "UTF-8").replace("+", "%20") + "/?hidebroken=true");
+ State[] states = gson.fromJson(returnContent, State[].class);
+ if (states == null) {
+ throw new ApiException("Server replied with no states");
+ }
+ List stateOptions = new ArrayList<>();
+ for (State state : states) {
+ stateOptions.add(new StateOption(state.name, state.name));
+ }
+ stateOptions.sort(Comparator.comparing(o -> "0".equals(o.getValue()) ? "" : o.getLabel()));
+ stateOptions.add(0, new StateOption("ALL", "All States"));
+ return stateOptions;
+ } catch (JsonSyntaxException | UnsupportedEncodingException e) {
+ throw new ApiException("Server did not reply with a valid json");
+ }
+ }
+
+ private List getCountries() throws ApiException {
+ try {
+ String returnContent = sendGetRequest("/json/countries?hidebroken=true");
+ Country[] countries = gson.fromJson(returnContent, Country[].class);
+ if (countries == null) {
+ throw new ApiException("Server replied with no countries");
+ }
+ List countryOptions = new ArrayList<>();
+ for (Country country : countries) {
+ countryMap.put(country.countryCode, country);
+ if (country.stationcount > 4) {
+ countryOptions.add(
+ new StateOption(country.countryCode, country.name + " (" + country.stationcount + ")"));
+ }
+ }
+ countryOptions.sort(Comparator.comparing(o -> "0".equals(o.getValue()) ? "" : o.getLabel()));
+ countryOptions.add(0, new StateOption("ALL", "All Countries"));
+ return countryOptions;
+ } catch (JsonSyntaxException e) {
+ throw new ApiException("Server did not reply with a valid json");
+ }
+ }
+
+ private List getLanguages() throws ApiException {
+ try {
+ String returnContent = sendGetRequest("/json/languages?hidebroken=true");
+ Language[] languages = gson.fromJson(returnContent, Language[].class);
+ if (languages == null) {
+ throw new ApiException("Server replied with no languages");
+ }
+ List languageOptions = new ArrayList<>();
+ languageOptions.add(new StateOption("ALL", "All Languages"));
+ for (Language language : languages) {
+ if (language.stationcount >= handler.config.languageCount) {
+ languageOptions
+ .add(new StateOption(language.name, language.name + " (" + language.stationcount + ")"));
+ }
+ }
+ languageOptions.sort(Comparator.comparing(o -> "0".equals(o.getValue()) ? "" : o.getLabel()));
+ return languageOptions;
+ } catch (JsonSyntaxException e) {
+ throw new ApiException("Server did not reply with a valid json");
+ }
+ }
+
+ private void searchStations(String arguments) throws ApiException {
+ stationMap.clear();
+ try {
+ String returnContent = sendGetRequest("/json/stations/search" + arguments);
+ Station[] stations = gson.fromJson(returnContent, Station[].class);
+ if (stations == null) {
+ throw new ApiException("Server replied with json that did not contain stations");
+ }
+ List stationOptions = new ArrayList<>();
+ for (Station station : stations) {
+ if (stationMap.putIfAbsent(station.name, station) == null) {// Remove multiples with the exact same name
+ stationOptions.add(new StateOption(station.name, station.name));
+ }
+ }
+ handler.stateDescriptionProvider
+ .setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_STATION), stationOptions);
+ if (stationMap.isEmpty()) {
+ handler.setChannelState(CHANNEL_STATION, new StringType("0 stations"));
+ } else {
+ handler.setChannelState(CHANNEL_STATION, new StringType(stationMap.size() + " stations"));
+ }
+ } catch (JsonSyntaxException e) {
+ throw new ApiException("Server did not reply with a valid json");
+ } catch (IllegalArgumentException e) {
+ logger.warn("IllegalArgumentException:{}", e.getMessage());
+ }
+ }
+
+ public void updateStations() throws ApiException {
+ searchStations(updateFilter());
+ }
+
+ private @Nullable Station searchStationUUID(String uuid) throws ApiException {
+ try {
+ String returnContent = sendGetRequest("/json/stations/byuuid/" + uuid);
+ Station[] stations = gson.fromJson(returnContent, Station[].class);
+ if (stations == null || stations.length == 0) {
+ return null;
+ }
+ return stations[0];
+ } catch (JsonSyntaxException e) {
+ throw new ApiException("Server did not reply with a valid json");
+ }
+ }
+
+ private @Nullable Station searchStationName(String name) throws ApiException {
+ try {
+ String returnContent = sendGetRequest(
+ "/json/stations/byname/" + URLEncoder.encode(name, "UTF-8").replace("+", "%20"));
+ Station[] stations = gson.fromJson(returnContent, Station[].class);
+ if (stations == null || stations.length == 0) {
+ return null;
+ }
+ List stationOptions = new ArrayList<>();
+ stationMap.clear();
+ for (Station station : stations) {
+ if (stationMap.putIfAbsent(station.name, station) == null) {// Remove multiples with the exact same name
+ stationOptions.add(new StateOption(station.name, station.name));
+ }
+ }
+ handler.stateDescriptionProvider
+ .setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_STATION), stationOptions);
+ if (stationMap.isEmpty()) {
+ handler.setChannelState(CHANNEL_STATION, new StringType("0 stations"));
+ } else {
+ handler.setChannelState(CHANNEL_STATION, new StringType(stationMap.size() + " stations"));
+ }
+ return stations[0]; // return first match
+ } catch (JsonSyntaxException e) {
+ throw new ApiException("Server did not reply with a valid json");
+ } catch (IllegalArgumentException e) {
+ logger.warn("IllegalArgumentException:{}", e.getMessage());
+ } catch (UnsupportedEncodingException e) {
+ logger.warn("UnsupportedEncodingException:{}", e.getMessage());
+ }
+ return null;
+ }
+
+ private void addRecent(String uuid, String name) {
+ recentOptions.add(0, new StateOption(uuid, name));
+ if (recentOptions.size() > handler.config.recentLimit) {
+ recentOptions.remove(recentOptions.size() - 1);
+ }
+ handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_RECENT),
+ recentOptions);
+ }
+
+ public void selectStation(String name) throws ApiException {
+ Station station = stationMap.get(name);
+ if (station == null) {
+ // missing from the MAP so its not from state options, try searching.
+ if (name.contains("-") && name.length() == 36) {
+ logger.debug("Looking for station UUID:{}", name);
+ station = searchStationUUID(name);
+ } else {
+ logger.debug("Finding any stations that contain {} in the name.", name);
+ station = searchStationName(name);// first match gets selected but list contains all
+ }
+ if (station == null) {
+ return;
+ }
+ }
+ handler.setChannelState(CHANNEL_STATION, new StringType(station.name));
+ handler.setChannelState(CHANNEL_NAME, new StringType(station.name));
+ handler.setChannelState(CHANNEL_ICON, new StringType(station.favicon));
+ handler.setChannelState(CHANNEL_STREAM, new StringType(station.url));
+ logger.debug("Selected station UUID:{}", station.stationuuid);
+ if (handler.config.recentLimit > 0) {
+ addRecent(station.stationuuid, station.name);
+ }
+ if (handler.config.clicks) {
+ sendGetRequest("/json/url/" + station.stationuuid);
+ }
+ }
+
+ private String updateFilter() {
+ String filter = handler.config.filters;
+ if (!language.isEmpty()) {
+ filter = filter + "&language=" + language;
+ }
+ if (!countryCode.isEmpty()) {
+ filter = filter + "&countrycode=" + countryCode;
+ }
+ if (!genre.isEmpty()) {
+ filter = filter + "&tag=" + genre;
+ }
+ if (!state.isEmpty()) {
+ try {
+ filter = filter + "&state=" + URLEncoder.encode(state, "UTF-8").replace("+", "%20");
+ } catch (UnsupportedEncodingException e) {
+ logger.warn("State contains bad characters?:{}", state);
+ }
+ }
+ return filter;
+ }
+
+ public void setLanguage(String language) throws ApiException {
+ if ("ALL".equals(language)) {
+ this.language = "";
+ } else {
+ this.language = language;
+ }
+ searchStations(updateFilter());
+ }
+
+ public void setCountry(String countryCode) throws ApiException {
+ this.state = "";
+ handler.setChannelState(CHANNEL_STATE, new StringType());
+ if ("ALL".equals(countryCode)) {
+ this.countryCode = "";
+ handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_STATE),
+ new ArrayList<>());
+ } else {
+ this.countryCode = countryCode;
+ handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_STATE),
+ getStates());
+ }
+ searchStations(updateFilter());
+ }
+
+ public void setState(String state) throws ApiException {
+ if ("ALL".equals(state)) {
+ this.state = "";
+ } else {
+ this.state = state;
+ }
+ searchStations(updateFilter());
+ }
+
+ public void setGenre(String genre) throws ApiException {
+ if ("ALL".equals(genre)) {
+ this.genre = "";
+ } else {
+ this.genre = genre;
+ }
+ searchStations(updateFilter());
+ }
+
+ public void initialize() throws ApiException {
+ getServers();
+ logger.debug("Using server:{}", server);
+ handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_COUNTRY),
+ getCountries());
+ handler.stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThing().getUID(), CHANNEL_LANGUAGE),
+ getLanguages());
+ }
+}
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/RadioBrowserJson.java b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/RadioBrowserJson.java
new file mode 100644
index 0000000000000..8f03ad177e274
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/java/org/openhab/binding/radiobrowser/internal/api/RadioBrowserJson.java
@@ -0,0 +1,49 @@
+/**
+ * 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.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link RadioBrowserJson} DTO holds the state and GSON parsed replies from the Radio Stations API.
+ *
+ * @author Matthew Skinner - Initial contribution
+ */
+@NonNullByDefault
+public class RadioBrowserJson {
+ public class Country {
+ public String name = "";
+ @SerializedName(value = "countryCode", alternate = { "iso_3166_1" }) // iso_3166_1 is used in json
+ public String countryCode = "";
+ public int stationcount;
+ }
+
+ public class Language {
+ public String name = "";
+ public int stationcount;
+ }
+
+ public class Station {
+ public String name = "";
+ public String stationuuid = "";
+ public String url = "";
+ public String favicon = "";
+ }
+
+ public class State {
+ public String name = "";
+ public int stationcount;
+ }
+}
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644
index 0000000000000..2ddbc5ed96166
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/addon/addon.xml
@@ -0,0 +1,11 @@
+
+
+
+ binding
+ Radio Browser Binding
+ This binding makes it easy to find a working radio stream using the open API called RadioBrowser.
+ cloud
+
+
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/i18n/radiobrowser.properties b/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/i18n/radiobrowser.properties
new file mode 100644
index 0000000000000..9de813226f069
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/i18n/radiobrowser.properties
@@ -0,0 +1,60 @@
+# add-on
+
+addon.radiobrowser.name = Radio Browser Binding
+addon.radiobrowser.description = This binding makes it easy to find a working radio stream using the open API called RadioBrowser.
+
+# thing types
+
+thing-type.radiobrowser.radio.label = RadioBrowser Thing
+thing-type.radiobrowser.radio.description = RadioBrowser Cloud Connection
+
+# thing types config
+
+thing-type.config.radiobrowser.radio.clicks.label = Clicks
+thing-type.config.radiobrowser.radio.clicks.description = Send a click for every station you select. This helps to mark stations as popular and makes the database more useful to other people.
+thing-type.config.radiobrowser.radio.filters.label = Filters
+thing-type.config.radiobrowser.radio.filters.description = Enter your own custom filter starting with ?, to ensure a stream that works with your device
+thing-type.config.radiobrowser.radio.languageCount.label = Language Count
+thing-type.config.radiobrowser.radio.languageCount.description = The minimum number of stations a language needs to have world wide before the binding lists it as an option.
+
+# channel types
+
+channel-type.radiobrowser.addFavorite.label = Add Favorite
+channel-type.radiobrowser.addFavorite.description = Add the currently selected station to the favorite list
+channel-type.radiobrowser.country.label = Country
+channel-type.radiobrowser.country.description = Show only stations located in the selected country
+channel-type.radiobrowser.favorites.label = Favorites
+channel-type.radiobrowser.favorites.description = The stations you have liked and added to this list
+channel-type.radiobrowser.genre.label = Genre
+channel-type.radiobrowser.genre.description = A list of genres or tags that you can select from
+channel-type.radiobrowser.genre.state.option.ALL = Show All Genres
+channel-type.radiobrowser.genre.state.option.christian = christian
+channel-type.radiobrowser.genre.state.option.classical = classical
+channel-type.radiobrowser.genre.state.option.country = country
+channel-type.radiobrowser.genre.state.option.dance = dance
+channel-type.radiobrowser.genre.state.option.disco = disco
+channel-type.radiobrowser.genre.state.option.gospel = gospel
+channel-type.radiobrowser.genre.state.option.hits = hits
+channel-type.radiobrowser.genre.state.option.jazz = jazz
+channel-type.radiobrowser.genre.state.option.music = music
+channel-type.radiobrowser.genre.state.option.news = news
+channel-type.radiobrowser.genre.state.option.oldies = oldies
+channel-type.radiobrowser.genre.state.option.pop = pop
+channel-type.radiobrowser.genre.state.option.rock = rock
+channel-type.radiobrowser.genre.state.option.sports = sports
+channel-type.radiobrowser.genre.state.option.talk = talk
+channel-type.radiobrowser.genre.state.option.trance = trance
+channel-type.radiobrowser.icon.label = Icon
+channel-type.radiobrowser.icon.description = The currently selected stations icon
+channel-type.radiobrowser.language.label = Language
+channel-type.radiobrowser.language.description = Show only stations spoken in the selected language
+channel-type.radiobrowser.name.label = Name
+channel-type.radiobrowser.name.description = The currently selected stations name
+channel-type.radiobrowser.removeFavorite.label = Remove Favorite
+channel-type.radiobrowser.removeFavorite.description = Add the currently selected station to the favorite list
+channel-type.radiobrowser.state.label = State
+channel-type.radiobrowser.state.description = Show only stations located in the selected state, requires country to be selected first
+channel-type.radiobrowser.station.label = Station
+channel-type.radiobrowser.station.description = A list of stations that you can select from to fetch the URI for
+channel-type.radiobrowser.stream.label = Stream
+channel-type.radiobrowser.stream.description = The stations URI that can be sent to a openHAB audio sink
diff --git a/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 0000000000000..ab778f9a5c0df
--- /dev/null
+++ b/bundles/org.openhab.binding.radiobrowser/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+ Provides a way to interact with the Radio Browser database of stations
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Enter your own custom filter starting with ?, to ensure a stream that works with your device
+ hidebroken=true,limit=1700,reverse=true,order=votes
+ true
+
+
+
+ Send a click for every station you select. This helps to mark stations as popular and makes the
+ database more useful to other people.
+ true
+ true
+
+
+
+ The minimum number of stations a language needs to have, before the binding lists it as an
+ option.
+ 14
+
+
+
+ Limit the number of stations in the recent channel list to this amount. `0` Disables the feature.
+ 5
+
+
+
+
+
+ String
+
+ Show only stations located in the selected country
+
+
+
+ String
+
+ Show only stations spoken in the selected language
+
+
+
+ String
+
+ Show only stations located in the selected state, requires country to be selected first
+
+
+
+ String
+
+ A list of stations that you can select from
+
+
+
+ String
+
+ A list of genres or tags that you can select from
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ The stations URI that can be sent to a openHAB audio sink
+
+
+
+ String
+
+ The currently selected stations name
+
+
+
+ String
+
+ The currently selected stations icon
+
+
+
+ String
+
+ A list of recently played stations
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index c7f2c6383ebd8..11b6c40270cbe 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -324,6 +324,7 @@
org.openhab.binding.pushsaferorg.openhab.binding.qbusorg.openhab.binding.qolsysiq
+ org.openhab.binding.radiobrowserorg.openhab.binding.radiothermostatorg.openhab.binding.regoheatpumporg.openhab.binding.revogi