diff --git a/CODEOWNERS b/CODEOWNERS
index 4a68d285a178..21a86eb5fd90 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -294,6 +294,7 @@
/bundles/org.openhab.binding.rme/ @kgoderis
/bundles/org.openhab.binding.robonect/ @reyem
/bundles/org.openhab.binding.roku/ @mlobstein
+/bundles/org.openhab.binding.romyrobot/ @wzbfyb @xeniter
/bundles/org.openhab.binding.rotel/ @lolodomo
/bundles/org.openhab.binding.russound/ @openhab/add-ons-maintainers
/bundles/org.openhab.binding.sagercaster/ @clinique
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index dadea0ba22ef..0206f5dd2b3c 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -1456,6 +1456,11 @@
org.openhab.binding.roku
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.romyrobot
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.rotel
diff --git a/bundles/org.openhab.binding.romyrobot/NOTICE b/bundles/org.openhab.binding.romyrobot/NOTICE
new file mode 100644
index 000000000000..38d625e34923
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/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.romyrobot/README.md b/bundles/org.openhab.binding.romyrobot/README.md
new file mode 100644
index 000000000000..7372ab7f720e
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/README.md
@@ -0,0 +1,95 @@
+# romyRobot Binding
+
+_Give some details about what this binding is meant for - a protocol, system, specific device._
+
+_If possible, provide some resources like pictures (only PNG is supported currently), a video, etc. to give an impression of what can be done with this binding._
+_You can place such resources into a `doc` folder next to this README.md._
+
+_Put each sentence in a separate line to improve readability of diffs._
+
+## Supported Things
+
+_Please describe the different supported things / devices including their ThingTypeUID within this section._
+_Which different types are supported, which models were tested etc.?_
+_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._
+
+- `bridge`: Short description of the Bridge, if any
+- `sample`: Short description of the Thing with the ThingTypeUID `sample`
+
+## Discovery
+
+_Describe the available auto-discovery features here._
+_Mention for what it works and what needs to be kept in mind when using it._
+
+## Binding Configuration
+
+_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it._
+_In this section, you should link to this file and provide some information about the options._
+_The file could e.g. look like:_
+
+```
+# Configuration for the romyRobot Binding
+#
+# Default secret key for the pairing of the romyRobot Thing.
+# It has to be between 10-40 (alphanumeric) characters.
+# This may be changed by the user for security reasons.
+secret=openHABSecret
+```
+
+_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/OH-INF/binding``` of your binding._
+
+_If your binding does not offer any generic configurations, you can remove this section completely._
+
+## Thing Configuration
+
+_Describe what is needed to manually configure a thing, either through the UI or via a thing-file._
+_This should be mainly about its mandatory and optional configuration parameters._
+
+_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._
+
+### `sample` Thing Configuration
+
+| Name | Type | Description | Default | Required | Advanced |
+|-----------------|---------|---------------------------------------|---------|----------|----------|
+| hostname | text | Hostname or IP address of the device | N/A | yes | no |
+| password | text | Password to access the device | N/A | yes | no |
+| refreshInterval | integer | Interval the device is polled in sec. | 600 | no | yes |
+
+## Channels
+
+_Here you should provide information about available channel types, what their meaning is and how they can be used._
+
+_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/OH-INF/thing``` of your binding._
+
+| Channel | Type | Read/Write | Description |
+|---------|--------|------------|-----------------------------|
+| control | Switch | RW | This is the control channel |
+
+## Full Example
+
+_Provide a full usage example based on textual configuration files._
+_*.things, *.items examples are mandatory as textual configuration is well used by many users._
+_*.sitemap examples are optional._
+
+### Thing Configuration
+
+```java
+Example thing configuration goes here.
+```
+
+### Item Configuration
+
+```java
+Example item configuration goes here.
+```
+
+### Sitemap Configuration
+
+```perl
+Optional Sitemap configuration goes here.
+Remove this section, if not needed.
+```
+
+## Any custom content here!
+
+_Feel free to add additional sections for whatever you think should also be mentioned about your binding!_
diff --git a/bundles/org.openhab.binding.romyrobot/pom.xml b/bundles/org.openhab.binding.romyrobot/pom.xml
new file mode 100644
index 000000000000..22da51eed713
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.1.0-SNAPSHOT
+
+
+ org.openhab.binding.romyrobot
+
+ openHAB Add-ons :: Bundles :: RomyRobot Binding
+
+
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/feature/feature.xml b/bundles/org.openhab.binding.romyrobot/src/main/feature/feature.xml
new file mode 100644
index 000000000000..f4eb85b9ce2f
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/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.romyrobot/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotBindingConstants.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotBindingConstants.java
new file mode 100644
index 000000000000..3e9abd315c60
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotBindingConstants.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link romyRobotBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Bernhard Kreuz - Initial contribution
+ */
+@NonNullByDefault
+public class RomyRobotBindingConstants {
+
+ private static final String BINDING_ID = "romyrobot";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID ROMYROBOT_DEVICE = new ThingTypeUID(BINDING_ID, "aicu");
+
+ // List of all Channel ids
+ public static final String CHANNEL_FW_VERSION = "fwversion";
+ public static final String CHANNEL_COMMAND = "command";
+ public static final String CHANNEL_MODE = "mode";
+ public static final String CHANNEL_ACTIVE_PUMP_VOLUME = "activepumpvolume";
+ public static final String CHANNEL_STRATEGY = "strategy";
+ public static final String CHANNEL_SUCTION_MODE = "suctionmode";
+ public static final String CHANNEL_BATTERY = "battery";
+ public static final String CHANNEL_CHARGING = "charging";
+ public static final String CHANNEL_RSSI = "rssi";
+ public static final String CHANNEL_POWER_STATUS = "powerstatus";
+ public static final String CHANNEL_SELECTED_MAP = "selectedmap";
+ public static final String CHANNEL_AVAILABLE_MAPS_JSON = "availablemapsjson";
+}
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotConfiguration.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotConfiguration.java
new file mode 100644
index 000000000000..7ea7f52f9d34
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotConfiguration.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link RomyRobotConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Bernhard Kreuz - Initial contribution
+ */
+@NonNullByDefault
+public class RomyRobotConfiguration {
+
+ /**
+ * Sample configuration parameters. Replace with your own.
+ */
+ public String hostname = "";
+ public String password = "";
+ public int refreshInterval = 600;
+ public int port = 8080;
+ public int timeout = 5;
+}
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotHandler.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotHandler.java
new file mode 100644
index 000000000000..8992e3323c65
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotHandler.java
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal;
+
+import static org.openhab.binding.romyrobot.internal.RomyRobotBindingConstants.*;
+import static org.openhab.core.library.unit.Units.PERCENT;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.validation.constraints.NotNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.romyrobot.internal.api.RomyApi;
+import org.openhab.binding.romyrobot.internal.api.RomyApiFactory;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+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.StateOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RomyRobotHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Bernhard Kreuz - Initial contribution
+ */
+@NonNullByDefault
+public class RomyRobotHandler extends BaseThingHandler {
+ private final Logger logger = LoggerFactory.getLogger(RomyRobotHandler.class);
+
+ private RomyRobotConfiguration config;
+ private @Nullable ScheduledFuture> pollingJob;
+ private RomyApi romyDevice;
+ private RomyApiFactory apiFactory;
+ private RomyRobotStateDescriptionOptionsProvider stateDescriptionProvider;
+
+ public RomyRobotHandler(Thing thing, @NotNull RomyApiFactory apiFactory,
+ RomyRobotStateDescriptionOptionsProvider stateDescriptionProvider) throws Exception {
+ super(thing);
+ this.apiFactory = apiFactory;
+ this.stateDescriptionProvider = stateDescriptionProvider;
+ config = getConfigAs(RomyRobotConfiguration.class);
+ romyDevice = setupAPI(apiFactory);
+ }
+
+ @Override
+ public void handleCommand(@NotNull ChannelUID channelUID, @NotNull Command command) {
+ if (command instanceof RefreshType) {
+ try {
+ getRomyApi().refresh();
+ } catch (Exception e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "RomyRobot refresh threw exception.");
+ }
+ }
+ if (CHANNEL_STRATEGY.equals(channelUID.getId())) {
+ updateState(CHANNEL_STRATEGY, new StringType(command.toString()));
+ try {
+ getRomyApi().setStrategy(command.toString());
+ } catch (Exception e) {
+ logger.error("error updating strategy: {}", e.getMessage());
+ }
+ } else if (CHANNEL_SUCTION_MODE.equals(channelUID.getId())) {
+ updateState(CHANNEL_SUCTION_MODE, new StringType(command.toString()));
+ try {
+ getRomyApi().setSuctionMode(command.toString());
+ } catch (Exception e) {
+ logger.error("error updating suctionmode: {}", e.getMessage());
+ }
+ } else if (CHANNEL_COMMAND.equals(channelUID.getId())) {
+ updateState(CHANNEL_COMMAND, new StringType(command.toString()));
+ try {
+ getRomyApi().executeCommand(command.toString());
+ } catch (Exception e) {
+ logger.error("error executing command against RomyRobot", e);
+ }
+ }
+ }
+
+ @Override
+ public void initialize() {
+ config = getConfigAs(RomyRobotConfiguration.class);
+ updateStatus(ThingStatus.UNKNOWN);
+ pollingJob = scheduler.scheduleWithFixedDelay(this::refreshVacuum, 2, config.refreshInterval, TimeUnit.SECONDS);
+ }
+
+ public void refreshVacuum() {
+ try {
+ getRomyApi().refresh();
+ updateStatus(ThingStatus.ONLINE);
+ this.updateChannels(getRomyApi());
+ } catch (Exception e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Could not sync status with RomyRobot, check your robot is unlocked to unlock it please provide password, you can find it under the dustbin (look for QR Code)"
+ + e.getMessage());
+ }
+ }
+
+ private void updateChannels(RomyApi device) {
+ updateState(CHANNEL_FW_VERSION, StringType.valueOf(device.getFirmwareVersion()));
+ updateState(CHANNEL_MODE, StringType.valueOf(device.getModeString()));
+ updateState(CHANNEL_ACTIVE_PUMP_VOLUME, StringType.valueOf(device.getActivePumpVolume()));
+ updateState(CHANNEL_BATTERY, QuantityType.valueOf(device.getBatteryLevel(), PERCENT));
+ updateState(CHANNEL_CHARGING, StringType.valueOf(device.getChargingStatus()));
+ updateState(CHANNEL_POWER_STATUS, StringType.valueOf(device.getPowerStatus()));
+ updateState(CHANNEL_RSSI, new DecimalType(device.getRssi()));
+ updateState(CHANNEL_AVAILABLE_MAPS_JSON, StringType.valueOf(device.getAvailableMapsJson()));
+ updateMapsList(device.getAvailableMaps());
+ }
+
+ public void updateMapsList(HashMap maps) {
+ logger.trace("RomyRobot updating maps list with {} options", maps.size());
+ List options = new ArrayList<>();
+ for (String key : maps.keySet()) {
+ options.add(new StateOption(key, maps.get(key)));
+ }
+ stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SELECTED_MAP), options);
+ }
+
+ private RomyApi setupAPI(RomyApiFactory apiFactory) throws Exception {
+ logger.debug("Initializing RomyRobot with config (Hostname: {}, Port: {}, Refresh: {}, Timeout {}).",
+ config.hostname, config.port, config.refreshInterval, config.timeout);
+ // hack:
+ logger.error("Initializing RomyRobot with config (Hostname: {}, Port: {}, Refresh: {}, Timeout {}).",
+ config.hostname, config.port, config.refreshInterval, config.timeout);
+
+ return apiFactory.getHttpApi(config);
+ }
+
+ private RomyApi getRomyApi() throws Exception {
+ romyDevice = apiFactory.getHttpApi(config);
+ return romyDevice;
+ }
+
+ @Override
+ public void dispose() {
+ final ScheduledFuture> pollingJob = this.pollingJob;
+ if (pollingJob != null) {
+ pollingJob.cancel(true);
+ this.pollingJob = null;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotHandlerFactory.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotHandlerFactory.java
new file mode 100644
index 000000000000..4d3924311a89
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotHandlerFactory.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal;
+
+import static org.openhab.binding.romyrobot.internal.RomyRobotBindingConstants.ROMYROBOT_DEVICE;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.romyrobot.internal.api.RomyApiFactory;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RomyRobotHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Bernhard Kreuz - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.romyrobot", service = ThingHandlerFactory.class)
+public class RomyRobotHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(ROMYROBOT_DEVICE);
+ private RomyApiFactory apiFactory;
+ private RomyRobotStateDescriptionOptionsProvider stateDescriptionProvider;
+ private final Logger logger = LoggerFactory.getLogger(RomyRobotHandlerFactory.class);
+
+ @Activate
+ public RomyRobotHandlerFactory(@Reference RomyApiFactory apiFactory,
+ @Reference RomyRobotStateDescriptionOptionsProvider stateDescriptionProvider) {
+ this.apiFactory = apiFactory;
+ this.stateDescriptionProvider = stateDescriptionProvider;
+ }
+
+ @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 (ROMYROBOT_DEVICE.equals(thingTypeUID)) {
+ try {
+ return new RomyRobotHandler(thing, apiFactory, stateDescriptionProvider);
+ } catch (Exception e) {
+ logger.error("could not create handler {}", e.getMessage());
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotStateDescriptionOptionsProvider.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotStateDescriptionOptionsProvider.java
new file mode 100644
index 000000000000..8959a14015ce
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/RomyRobotStateDescriptionOptionsProvider.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.events.EventPublisher;
+import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
+import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
+import org.openhab.core.thing.link.ItemChannelLinkRegistry;
+import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Dynamic provider of state options while leaving other state description fields as original.
+ *
+ * @author Gregory Moyer - Initial contribution
+ * @author Mark Hilbush - Adapted to squeezebox binding
+ */
+@Component(service = { DynamicStateDescriptionProvider.class, RomyRobotStateDescriptionOptionsProvider.class })
+@NonNullByDefault
+public class RomyRobotStateDescriptionOptionsProvider extends BaseDynamicStateDescriptionProvider {
+
+ @Activate
+ public RomyRobotStateDescriptionOptionsProvider(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.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApi.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApi.java
new file mode 100644
index 000000000000..bffa08468d18
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApi.java
@@ -0,0 +1,179 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal.api;
+
+import java.util.HashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link RomyApi} interface defines the functions which are
+ * controllable on the Romy API interface.
+ *
+ * @author Bernhard Kreuz - Initial contribution
+ */
+@NonNullByDefault
+public interface RomyApi {
+
+ /**
+ * get robots firmwware version and name
+ * is available when robots http interface is locked
+ *
+ * @throws Exception
+ *
+ * @throws CommunicationApiException
+ * @throws UnauthorizedApiException
+ */
+ void refreshID() throws Exception;
+
+ /**
+ * get robots api protocol version
+ * is available when robots http interface is locked
+ *
+ * @throws Exception
+ *
+ * @throws CommunicationApiException
+ * @throws UnauthorizedApiException
+ */
+ void refreshProtocolVersion() throws Exception;
+
+ /**
+ * Sends all the GET requests and stores/cache the responses for use by the API to prevent the need for multiple
+ * requests.
+ *
+ * @throws Exception
+ *
+ * @throws CommunicationApiException
+ * @throws UnauthorizedApiException
+ */
+ void refresh() throws Exception;
+
+ /**
+ *
+ * @return Firmware Version of robot
+ */
+ @Nullable
+ String getFirmwareVersion();
+
+ /**
+ *
+ * @return Firmware Version of robot
+ */
+ @Nullable
+ String getName();
+
+ /**
+ *
+ * @return Status / Mode robot is currently in
+ */
+ @Nullable
+ String getModeString();
+
+ /**
+ *
+ * @return currently set pump volume
+ */
+ @Nullable
+ String getActivePumpVolume();
+
+ /**
+ *
+ * @param volume the pump volume used on next start
+ */
+ void setActivePumpVolume(String volume);
+
+ /**
+ *
+ * @return cleaning strategy
+ */
+ @Nullable
+ String getStrategy();
+
+ /**
+ *
+ * @param strategy cleaning strategy
+ */
+ void setStrategy(String strategy);
+
+ /**
+ *
+ * @return suction mode, see thing xml for details
+ */
+ @Nullable
+ String getSuctionMode();
+
+ /**
+ *
+ * @param suctionMode suction mode to be used for next start
+ */
+ void setSuctionMode(String suctionMode);
+
+ /**
+ *
+ * @return current battery level
+ */
+ int getBatteryLevel();
+
+ /**
+ *
+ * @return weither the vacuum is charging
+ */
+ @Nullable
+ String getChargingStatus();
+
+ /**
+ *
+ * @return WiFi rssi
+ */
+ int getRssi();
+
+ /**
+ *
+ * @return current power status of the vacuum
+ */
+ @Nullable
+ String getPowerStatus();
+
+ /**
+ *
+ * @return a String listing the available maps
+ */
+ HashMap getAvailableMaps();
+
+ /**
+ *
+ * @return a String listing the available maps
+ */
+ String getAvailableMapsJson();
+
+ /**
+ *
+ * @return Minor Interface Version
+ */
+ int getProtocolVersionMinor();
+
+ /**
+ *
+ * @return Major Interface Version
+ */
+ int getProtocolVersionMajor();
+
+ /**
+ *
+ * @param command command to execute
+ * @throws Exception
+ */
+ void executeCommand(String command) throws Exception;
+}
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApiFactory.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApiFactory.java
new file mode 100644
index 000000000000..9ef0defe1fce
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApiFactory.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.romyrobot.internal.RomyRobotConfiguration;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RomyApiFactory} class is used for creating instances of
+ * the Romy API classes to interact with the RomyRobot HTTP API.
+ *
+ * @author Bernhard Kreuz - Initial contribution
+ */
+@Component(service = RomyApiFactory.class)
+@NonNullByDefault
+public class RomyApiFactory {
+ private HttpClient httpClient;
+
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Activate
+ public RomyApiFactory(@Reference HttpClientFactory httpClientFactory) {
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ }
+
+ public RomyApi getHttpApi(RomyRobotConfiguration config) throws Exception {
+ int version = -1;
+ RomyApi lowestSupportedApi = new RomyApiV6(this.httpClient, config);
+ try {
+ version = lowestSupportedApi.getProtocolVersionMajor();
+ } catch (Exception exp) {
+ logger.error("Problem fetching the firmware version from RomyRobot: {}", exp.getMessage());
+ }
+ // will start to make sense once a breaking API Version > 6 is introduced
+ if (version >= 6) {
+ return lowestSupportedApi;
+ } else {
+ return lowestSupportedApi;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApiV6.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApiV6.java
new file mode 100644
index 000000000000..695e49fabbe0
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/api/RomyApiV6.java
@@ -0,0 +1,374 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal.api;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+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.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.openhab.binding.romyrobot.internal.RomyRobotConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * The {@link RomyApiV6} interface defines the functions which are
+ * controllable on the Romy API interface Version 6.
+ *
+ * @author Bernhard Kreuz - Initial contribution
+ */
+@NonNullByDefault
+public class RomyApiV6 implements RomyApi {
+
+ private String hostname;
+ private RomyRobotConfiguration config;
+ protected HttpRequestSender http;
+ private @Nullable String firmwareVersion;
+ private @Nullable String name;
+ private @Nullable String mode;
+ private @Nullable String activePumpVolume;
+ private @Nullable String charging;
+ private int batteryLevel;
+ private @Nullable String powerStatus;
+ private String mapsJson = "";
+ private int rssi;
+ private @Nullable String strategy;
+ private @Nullable String suctionMode;
+ private @Nullable String selectedMap;
+
+ // that was the newest version when this code was written
+ private int protocolVersionMajor = 6;
+ private int protocolVersionMinor = 49;
+
+ private HashMap availableMaps = new HashMap();
+ private static final String CMD_GET_ROBOT_ID = "get/robot_id";
+ private static final String CMD_GET_STATUS = "get/status";
+ private static final String CMD_GET_MAPS = "get/maps";
+ private static final String CMD_GET_WIFI_STATUS = "get/wifi_status";
+ private static final String CMD_GET_POWER_STATUS = "get/power_status";
+ private static final String CMD_GET_PROTOCOL_VERSION = "get/protocol_version";
+
+ protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ public RomyApiV6(final HttpClient httpClient, final RomyRobotConfiguration config) {
+ this.config = config;
+ if (config.hostname.startsWith("http://") || config.hostname.startsWith("https://")) {
+ this.hostname = config.hostname;
+ } else {
+ this.hostname = "http://" + config.hostname;
+ }
+
+ this.http = new HttpRequestSender(httpClient);
+ }
+
+ /**
+ * Returns the hostname and port formatted URL as a String.
+ *
+ * @return String representation of the OpenSprinkler API URL.
+ */
+ protected String getBaseUrl() {
+ return hostname + ":" + config.port + "/";
+ }
+
+ @Override
+ public void refreshID() throws Exception {
+ String returnContent = http.sendHttpGet(getBaseUrl() + CMD_GET_ROBOT_ID, null);
+ JsonNode jsonNode = new ObjectMapper().readTree(returnContent);
+ firmwareVersion = jsonNode.get("firmware").asText();
+ if (firmwareVersion == null) {
+ logger.error("There was a problem in the HTTP communication: firmware was empty.");
+ }
+ name = jsonNode.get("name").asText();
+ }
+
+ @Override
+ public void refreshProtocolVersion() throws Exception {
+ String returnContent = http.sendHttpGet(getBaseUrl() + CMD_GET_PROTOCOL_VERSION, null);
+ JsonNode jsonNode = new ObjectMapper().readTree(returnContent);
+ protocolVersionMajor = jsonNode.get("version_major").intValue();
+ protocolVersionMinor = jsonNode.get("version_minor").intValue();
+ }
+
+ @Override
+ public void refresh() throws Exception {
+ String returnContent = http.sendHttpGet(getBaseUrl() + CMD_GET_POWER_STATUS, null);
+ powerStatus = new ObjectMapper().readTree(returnContent).get("power_status").asText();
+
+ returnContent = http.sendHttpGet(getBaseUrl() + CMD_GET_STATUS, null);
+ JsonNode jsonNode = new ObjectMapper().readTree(returnContent);
+ mode = jsonNode.get("mode").asText();
+ activePumpVolume = jsonNode.get("active_pump_volume").asText();
+ charging = jsonNode.get("charging").asText();
+ batteryLevel = jsonNode.get("battery_level").asInt();
+
+ returnContent = http.sendHttpGet(getBaseUrl() + CMD_GET_WIFI_STATUS, null);
+ jsonNode = new ObjectMapper().readTree(returnContent);
+ rssi = jsonNode.get("rssi").asInt();
+
+ mapsJson = http.sendHttpGet(getBaseUrl() + CMD_GET_MAPS, null);
+ parseMaps(mapsJson);
+ }
+
+ private void parseMaps(String jsonString) throws JsonMappingException, JsonProcessingException {
+ JsonNode node = new ObjectMapper().readTree(jsonString);
+ JsonNode maps = node.get("maps");
+ if (maps != null && maps.textValue() != null && !maps.textValue().isBlank() && maps.isArray()) {
+ availableMaps.clear();
+ for (final JsonNode field : maps) {
+ String value = field.get("map_meta_data").textValue();
+ String key = field.get("map_id").asInt() + "";
+ String permanentFlag = field.get("permanent_flag").textValue();
+ if ("true".equalsIgnoreCase(permanentFlag)) {
+ availableMaps.put(key, value);
+ }
+ }
+ if (availableMaps.size() == 1 || selectedMap == null) {
+ selectedMap = availableMaps.values().iterator().next();
+ }
+ } else {
+ logger.warn("ROMY has no maps yet, please start a explore to create one!");
+ }
+ }
+
+ @Override
+ public @Nullable String getFirmwareVersion() {
+ return firmwareVersion;
+ }
+
+ @Override
+ public @Nullable String getName() {
+ return name;
+ }
+
+ @Override
+ public @Nullable String getModeString() {
+ return mode;
+ }
+
+ @Override
+ public @Nullable String getActivePumpVolume() {
+ return activePumpVolume;
+ }
+
+ @Override
+ public void setActivePumpVolume(String volume) {
+ this.activePumpVolume = volume;
+ }
+
+ @Override
+ public @Nullable String getStrategy() {
+ return strategy;
+ }
+
+ @Override
+ public void setStrategy(String strategy) {
+ this.strategy = strategy;
+ }
+
+ @Override
+ public @Nullable String getSuctionMode() {
+ return suctionMode;
+ }
+
+ @Override
+ public void setSuctionMode(String suctionMode) {
+ this.suctionMode = suctionMode;
+ }
+
+ @Override
+ public int getBatteryLevel() {
+ return batteryLevel;
+ }
+
+ @Override
+ public @Nullable String getChargingStatus() {
+ return charging;
+ }
+
+ @Override
+ public int getRssi() {
+ return rssi;
+ }
+
+ @Override
+ public @Nullable String getPowerStatus() {
+ return powerStatus;
+ }
+
+ @Override
+ public String getAvailableMapsJson() {
+ return mapsJson;
+ }
+
+ /**
+ * This class contains helper methods for communicating HTTP GET and HTTP POST
+ * requests.
+ *
+ * @author Chris Graham - Initial contribution
+ * @author Florian Schmidt - Reduce visibility of Http communication to Api
+ */
+ protected class HttpRequestSender {
+ private final HttpClient httpClient;
+
+ public HttpRequestSender(HttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ /**
+ * Given a URL and a set parameters, send a HTTP GET request to the URL location
+ * created by the URL and parameters.
+ *
+ * @param url The URL to send a GET request to.
+ * @param urlParameters List of parameters to use in the URL for the GET
+ * request. Null if no parameters.
+ * @return String contents of the response for the GET request.
+ * @throws Exception
+ */
+ public String sendHttpGet(String url, @Nullable String urlParameters) throws Exception {
+ String location = null;
+ if (urlParameters != null) {
+ location = url + "?" + urlParameters;
+ } else {
+ location = url;
+ }
+
+ logger.debug("sendHttpGet location:{}", location);
+ ContentResponse response;
+ try {
+ response = withGeneralProperties(httpClient.newRequest(location))
+ .timeout(config.timeout, TimeUnit.SECONDS).method(HttpMethod.GET).send();
+ } catch (Exception e) {
+ logger.error("Request to RomyRobot device failed: {}", e.getMessage());
+ return "";
+ }
+
+ if (response.getStatus() == HttpStatus.FORBIDDEN_403) {
+ // forbiden, looks like http interface is locked, try to unlock it
+ // ------------------------------------------------------------------
+ URL netUrl = new URL(url);
+ try {
+ logger.info(
+ "looks like http interface is locked, try to unlock it now with password from config...");
+ String unlock = netUrl.getProtocol() + "://" + netUrl.getHost() + ":" + netUrl.getPort()
+ + "/set/unlock_http?pass=" + config.password;
+ response = withGeneralProperties(httpClient.newRequest(unlock))
+ .timeout(config.timeout, TimeUnit.SECONDS).method(HttpMethod.GET).send();
+ } catch (Exception e) {
+ logger.error("Request to unlock RomyRobot device with password {} failed: {}", config.password,
+ e.getMessage());
+ }
+
+ // send request again after unlocking
+ // -------------------------------------
+ try {
+ response = withGeneralProperties(httpClient.newRequest(location))
+ .timeout(config.timeout, TimeUnit.SECONDS).method(HttpMethod.GET).send();
+ } catch (Exception e) {
+ logger.error("Request to RomyRobot device failed: {}", e.getMessage());
+ }
+ }
+ if (response.getStatus() != HttpStatus.OK_200) {
+ logger.error("Error sending HTTP GET request to {}. Got response code: {}", url, response.getStatus());
+ }
+ return response.getContentAsString();
+ }
+
+ private Request withGeneralProperties(Request request) {
+ return request;
+ }
+
+ /**
+ * Given a URL and a set parameters, send a HTTP POST request to the URL
+ * location created by the URL and parameters.
+ *
+ * @param url The URL to send a POST request to.
+ * @param urlParameters List of parameters to use in the URL for the POST
+ * request. Null if no parameters.
+ * @return String contents of the response for the POST request.
+ * @throws Exception
+ */
+ public String sendHttpPost(String url, String urlParameters) throws Exception {
+ ContentResponse response;
+ try {
+ response = withGeneralProperties(httpClient.newRequest(url)).timeout(config.timeout, TimeUnit.SECONDS)
+ .method(HttpMethod.POST).content(new StringContentProvider(urlParameters)).send();
+ } catch (Exception e) {
+ logger.error("Request to RomyRobot device failed: {}", e.getMessage());
+ return "";
+ }
+ if (response.getStatus() != HttpStatus.OK_200) {
+ logger.error("Error sending HTTP POST request to {}. Got response code: {}", url, response.getStatus());
+ }
+ return response.getContentAsString();
+ }
+ }
+
+ @Override
+ public void executeCommand(String command) throws Exception {
+ if ("REFRESH".equalsIgnoreCase(command)) {
+ return;
+ }
+ // String query = "/$" + command;
+ String query = null;
+ List params = new ArrayList();
+ if ("clean_start_or_continue".equalsIgnoreCase(command) || "clean_all".equalsIgnoreCase(command)
+ || "clean_spot".equalsIgnoreCase(command) || "clean_map".equalsIgnoreCase(command)) {
+ if (suctionMode != null && !"REFRESH".equals(suctionMode)) {
+ params.add("cleaning_parameter_set=" + suctionMode);
+ }
+ if (strategy != null && !"REFRESH".equals(strategy)) {
+ params.add("cleaning_strategy_mode=" + strategy);
+ }
+ if (activePumpVolume != null) {
+ params.add("pump_volume=" + activePumpVolume);
+ }
+ if (params.isEmpty()) {
+ query = String.join("&", params);
+ }
+ } else if ("redo_explore".equalsIgnoreCase(command) || "clean_map".equalsIgnoreCase(command)) {
+ params.add("map_id" + selectedMap);
+ }
+ String url = getBaseUrl() + "set/" + command;
+ logger.info("executing RomyRobot command: {} at url {}", query, url);
+ http.sendHttpGet(url, query);
+ }
+
+ @Override
+ public HashMap getAvailableMaps() {
+ return availableMaps;
+ }
+
+ @Override
+ public int getProtocolVersionMajor() {
+ return protocolVersionMajor;
+ }
+
+ @Override
+ public int getProtocolVersionMinor() {
+ return protocolVersionMinor;
+ }
+}
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/discovery/RomyRobotMDNSDiscoveryParticipant.java b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/discovery/RomyRobotMDNSDiscoveryParticipant.java
new file mode 100644
index 000000000000..ce25e41ddf74
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/java/org/openhab/binding/romyrobot/internal/discovery/RomyRobotMDNSDiscoveryParticipant.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2010-2023 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.romyrobot.internal.discovery;
+
+import static org.openhab.binding.romyrobot.internal.RomyRobotBindingConstants.*;
+import static org.openhab.core.thing.Thing.*;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jmdns.ServiceInfo;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.romyrobot.internal.RomyRobotConfiguration;
+import org.openhab.binding.romyrobot.internal.api.RomyApi;
+import org.openhab.binding.romyrobot.internal.api.RomyApiFactory;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a discovery participant which finds RomyRobots on the local network
+ * through their mDNS announcements.
+ *
+ * @author Manuel Dipolt - Initial contribution
+ *
+ */
+@NonNullByDefault
+@Component
+public class RomyRobotMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
+
+ private final Logger logger = LoggerFactory.getLogger(RomyRobotMDNSDiscoveryParticipant.class);
+
+ private RomyApiFactory apiFactory;
+
+ @Activate
+ public RomyRobotMDNSDiscoveryParticipant(
+ @Reference RomyApiFactory apiFactory /* @Reference HttpClientFactory httpClientFactory */) {
+ logger.debug("Activating ROMY Discovery service");
+ this.apiFactory = apiFactory;
+ }
+
+ @Override
+ public String getServiceType() {
+ return "_aicu-http._tcp.local.";
+ }
+
+ @Override
+ public Set getSupportedThingTypeUIDs() {
+ Set supportedThingTypeUIDs = new HashSet<>();
+ supportedThingTypeUIDs.add(ROMYROBOT_DEVICE);
+ return supportedThingTypeUIDs;
+ }
+
+ @Override
+ public @Nullable ThingUID getThingUID(ServiceInfo service) {
+ return new ThingUID(ROMYROBOT_DEVICE, service.getName());
+ }
+
+ @Override
+ public @Nullable DiscoveryResult createResult(ServiceInfo service) {
+ final ThingUID uid = getThingUID(service);
+ if (uid == null) {
+ logger.error("uid is null!");
+ return null;
+ }
+
+ logger.info("Discovered ROMY vacuum cleaner robot: {}", service);
+
+ // get IP address
+ String address = "";
+ String robotName = "";
+ String robotLabel = "";
+ String robotUniqeId = service.getName();
+ String[] hostAddresses = service.getHostAddresses();
+
+ if (hostAddresses.length == 0) {
+ logger.error("hostAddresses is empty!");
+ return null;
+ }
+
+ logger.debug("hostAddresses: {}", Arrays.toString(hostAddresses));
+ address = hostAddresses[0];
+ logger.debug("address: {}", address);
+
+ try {
+ RomyRobotConfiguration config = new RomyRobotConfiguration();
+ config.hostname = address;
+ RomyApi romyDevice = apiFactory.getHttpApi(config);
+ romyDevice.refreshID();
+ romyDevice.refreshProtocolVersion();
+ robotName = romyDevice.getName();
+ logger.debug("New ROMY with the name: {}", robotName);
+ } catch (Exception e) {
+ logger.error("Error setting up ROMY api: {}", e.getMessage());
+ return null;
+ }
+
+ robotLabel = String.format("%s (%s)", robotName, address);
+
+ DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperty(PROPERTY_SERIAL_NUMBER, robotUniqeId)
+ .withProperty("hostname", address).withLabel(robotLabel)
+ .withRepresentationProperty(PROPERTY_SERIAL_NUMBER).build();
+
+ logger.debug("DiscoveryResult: {}", result);
+
+ return result;
+ }
+}
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644
index 000000000000..01701d0dfcee
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/addon/addon.xml
@@ -0,0 +1,10 @@
+
+
+
+ binding
+ romyRobot Binding
+ This is the binding for romyRobot.
+
+
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/i18n/romyrobot.properties b/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/i18n/romyrobot.properties
new file mode 100644
index 000000000000..0c2f44015a92
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/i18n/romyrobot.properties
@@ -0,0 +1,3 @@
+# FIXME: please add all English translations to this file so the texts can be translated using Crowdin
+# FIXME: to generate the content of this file run: mvn i18n:generate-default-translations
+# FIXME: see also: https://www.openhab.org/docs/developer/utils/i18n.html
diff --git a/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 000000000000..b4803c649ca8
--- /dev/null
+++ b/bundles/org.openhab.binding.romyrobot/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,169 @@
+
+
+
+
+
+ A RomyRobot vacuum robot
+ CleaningRobot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ network-address
+
+ The host name or IP address of your ROMY robot
+
+
+ password
+
+ Please take note that the local http interface is locked per default you have to unlock it with the
+ code you find under the dustbin.
+
+
+
+ Port of the RomyRobot Web API interface.
+ 8080
+ true
+
+
+
+ Interval the device is polled in sec.
+ 180
+ true
+
+
+
+ Specifies the connection timeout in seconds.
+ 5
+ true
+
+
+
+ Specifies the connection timeout in seconds.
+ 5
+ true
+
+
+
+
+
+ String
+
+ Firmware version installed on vacuum
+
+
+
+ String
+
+ Command to execute
+
+
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ Intensitiy of water usage
+
+
+
+
+
+
+
+
+
+
+ String
+
+ Choose an available cleaning strategy
+
+
+
+
+
+
+
+
+
+
+ String
+
+ Choose an available suction mode
+
+
+
+
+
+
+
+
+
+
+
+ String
+
+ Robots current status
+
+
+
+ Number
+
+ Battery charge percentage
+
+
+
+ String
+
+ Indicates the vacuums charging status
+
+
+
+
+ String
+
+ Robots current power status
+
+
+
+ String
+
+ selected map if multiple maps are stored, used on commands
+
+
+ String
+
+ Information about availabler maps. Useful for composing commands
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index bc0ef858994b..0c0096df4e0d 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -326,6 +326,7 @@
org.openhab.binding.rme
org.openhab.binding.robonect
org.openhab.binding.roku
+ org.openhab.binding.romyrobot
org.openhab.binding.rotel
org.openhab.binding.russound
org.openhab.binding.sagercaster