Romy Robot integration via http binding (Austrian Vacuum robot, highly recommended)

Good morning everybody,

for the last few days, I built an integration for the new Romy robot vacuums, using the HTTP binding and some JS.

What is the Romy Robot about?
The great thing about the robot is that it is:

  • created by an Austrian company (https://romyrobot.eu/)
  • can be locally controlled (local UI and HTTP API)
  • the cloud servers are EU-hosted, European law applies
  • the robot is actually very decent and powerful with a useful app
  • Robart (the company behind it) is very approachable. I got all the info I needed to implement this quickly
  • they work on a Homeassistant plugin themselves and have OH at least on their wishlist

Features as of now
Currently, you can control the robot and see its status.

Working:

  • get the status of more or less all functions
  • send it to clean all, start/pause, send home, clean a specific map

Not working/not included yet:

  • spot cleaning
  • showing / retrieving a map image

Remarks
The local UI is not beautiful and, while you can fully control the robot locally, it is clearly designed for engineers. Good for us, bad for my mum :slight_smile:

This was built based on my L6, it was not tested with other Romy robots.

Robart will release official API documentation in the future (soon, I was told).
The local access will stay in future firmwares, but will be password protected in the future, according to an email conversation I had.

I found no way to change suction mode or other parameters on the robot while its cleaning already (maybe when the official API docu arrives…). So right now, these parameters that you set via items will be sent only during sending a command. So if you want to change for example the suction mode, you have to stop the bot, change the setting and tell it to continue.

Integration
This uses the HTTP binding and a JS automation.
you need to put the things file and items files in their respective places (open hab config folder/items respectively openhab config folder/things).
The JS file goes into openhab config folder/automation/js

In the items file I used a custom icon (romyiconbig). For this to work you have to put a file called romyiconbig.png into openhab config folder/icons/classic.
As I was using a screenshot from the official we shop, I did not include it here (not to violate someones rights by accident).

Things file:

Thing http:url:romy "Romy Roboter" [
        baseURL="http://<romy robot ip>:8080/",
        refresh=10,
        timeout=3000,
        bufferSize=2048,
        delay=1,
        stateMethod="GET",
        commandMethod="GET",
        contentType="application/json",
        ignoreSSLErrors=true] {
   Channels:
      Type string : robot_id "Robot ID"                     [ stateExtension="get/robot_id",stateTransformation="JSONPATH:$.unique_id", mode="READONLY" ]         
      Type string : product_nick_name "Product Nickname"    [ stateExtension="get/robot_id",stateTransformation="JSONPATH:$.name", mode="READONLY" ]   
      Type string : fw_version "Firmware Version"           [ stateExtension="get/robot_id",stateTransformation="JSONPATH:$.firmware", mode="READONLY" ]         
      Type string : os_version "OS Version"                 [ stateExtension="get/robot_id",stateTransformation="JSONPATH:$.os_version", mode="READONLY" ] 
      Type string : command "Command"                       [ commandExtension="set%2$s", mode="WRITEONLY" ]
      Type string : mode "Mode"                             [ stateExtension="get/status",stateTransformation="JSONPATH:$.mode", mode="READONLY" ] 
      Type string : active_pump_volume "Active Pump Volume" [ stateExtension="get/status",stateTransformation="JSONPATH:$.active_pump_volume", mode="READONLY" ] 
      Type number : battery_level "Battery Level"           [ stateExtension="get/status",stateTransformation="JSONPATH:$.battery_level", mode="READONLY" ] 
      Type string : charging "Charging"                     [ stateExtension="get/status",stateTransformation="JSONPATH:$.charging", mode="READONLY" ] 
      Type string : wifi_status "Wifi Status"               [ stateExtension="get/wifi_status",stateTransformation="JSONPATH:$.status", mode="READONLY" ] 
      Type string : wifi_ssid "Wifi SSID"                   [ stateExtension="get/wifi_status",stateTransformation="JSONPATH:$.ssid", mode="READONLY" ] 
      Type number : wifi_rssi "Wifi RSSI"                   [ stateExtension="get/wifi_status",stateTransformation="JSONPATH:$.rssi", mode="READONLY" ] 
      Type string : wifi_ip "Wifi IP address"               [ stateExtension="get/wifi_status",stateTransformation="JSONPATH:$.ip_address", mode="READONLY" ] 
      Type string : wifi_mac "Wifi MAC address"             [ stateExtension="get/wifi_status",stateTransformation="JSONPATH:$.mac_address", mode="READONLY" ] 
      Type string : power_status "Power Status"             [ stateExtension="get/power_status",stateTransformation="JSONPATH:$.power_status", mode="READONLY" ] 
      Type string : availableMaps "Available Maps"          [ stateExtension="get/maps", mode="READONLY" ] 
}   

Items file:

// Romy L6 vacuum robot
Group gRomyRobot "Romy Robot"  <romyiconbig>
String sRomyRobotID "Romy ID [%s]" <keyring> (gRomyRobot) {channel="http:url:romy:robot_id"}
String sRomyNickname "Romy Nickname [%s]" <smiley> (gRomyRobot) {channel="http:url:romy:product_nick_name"}
String sRomyFwVersion "Romy Firmware Version [%s]" <text> (gRomyRobot) {channel="http:url:romy:fw_version"}
String sRomyOsVersion "Romy OS Version [%s]" <text> (gRomyRobot) {channel="http:url:romy:os_version"}
String sRomyCharging "Charging: [%s]" <poweroutlet_eu> (gRomyRobot) {channel="http:url:romy:charging"}
String sRomyStatus "Status: [%s]" <switch> (gRomyRobot) {channel="http:url:romy:mode"}
Number nRomyBatteryLevel "Battery Level [%d %%]" <battery> (gRomyRobot) {channel="http:url:romy:battery_level"}
Number sRomyPowerStatus "Power Status" <energy> (gRomyRobot) {channel="http:url:romy:power_status"}

Group gRomyWifi "Romy Wifi" <network> (gRomyRobot)
String sRomyWifiStatus "Romy Wifi Status" (gRomyWifi) {channel="http:url:romy:wifi_status"}
String sRomyWifiSsid "Romy Wifi SSID" (gRomyWifi) {channel="http:url:romy:wifi_ssid"}
Number sRomyWifiRssi "Romy Wifi RSSI" <qualityofservice> (gRomyWifi) {channel="http:url:romy:wifi_rssi"}
String sRomyWifiMac "Romy Wifi MAC Address" (gRomyWifi) {channel="http:url:romy:wifi_mac"}
String sRomyWifiIp "Romy Wifi IP Addresse" (gRomyWifi) {channel="http:url:romy:wifi_ip"}

Group gRomyCommand "Romy Control" <settings> (gRomyRobot) 
String sRomyCommandProxy "Romy Command Proxy to be used in Rule"  {channel="http:url:romy:command"}
String sRomyCommand "Romy Command to execute" <settings> (gRomyCommand)  { 
    commandDescription = ""[
        options ="clean_all=clean all, clean_start_or_continue=clean start or continue,clean_spot=clean spot,clean_map=clean map,stop=stop,go_home=go home"
        ]
    }
String sRomyCommandSuctionMode "Romy Command suction mode" <pressure> (gRomyCommand) {
    commandDescription = ""[
        options ="1=Normal,2=Silent,3=Intensive,0=Default,4=Super Silent"
        ]
}
String sRomyCommandStrategy "Romy Command strategy" <movecontrol> (gRomyCommand)  {
    commandDescription = ""[
        options ="1=Normal,2=WallsCorners,3=Deep,4=Default"
        ]
}
String sRomyCommandPumpVolume "Romy Command pump volume" <water>(gRomyCommand)   {
    commandDescription = ""[
        options ="none=Pump: Default,low=Pump: Low,medium=Pump: Medium, high=Pump: High"
        ]
}
String sRomyAvailableMaps "Romy Available Maps" <settings>  {channel="http:url:romy:availableMaps"}
String sRomyMap "Romy Map" <presence> (gRomyCommand) 


and finally, the JS code to update the command options on the maps and execute the commands:

var logger = log(this.ruleUID);

rules.when().item('sRomyCommand').receivedCommand().then(event => { executeCommand(event) }).build("Romy received command","")
rules.when().item('sRomyAvailableMaps').changed().then(event => { updateMapMetadata(event) }).build("Romy got new maps","update map metadata for map choices")


/**
 * 
 * executes the value of sRomyCommand against sRomyCommand proxy which should be linked to the robots command channel
 * combines the  
 */
function executeCommand(event) {
    logger.info("romyrobot executeCommand because of event: "+event.itemName)
    let params = [];
    var command = items.getItem('sRomyCommand').state
    var pump_volume = items.getItem('sRomyCommandPumpVolume').state
    var strategy = items.getItem('sRomyCommandStrategy').state
    var suction_mode = items.getItem('sRomyCommandSuctionMode').state

    
    if (command === "clean_start_or_continue" || command === "clean_all" || command === "clean_spot" || command === "clean_map") {
        if (suction_mode != 'NULL') {
            params.push(`cleaning_parameter_set=${suction_mode}`);
        }
        if (strategy != 'NULL') {
            params.push(`cleaning_strategy_mode=${strategy}`);
        }
        if (pump_volume != 'NULL') {
            params.push(`pump_volume=${pump_volume}`);
        }
    }
    const query = (params.length === 0) ? `/${command}` : `/${command}?${params.join("&")}`;
    logger.info(`romyrobot query: ${query}`)
    items.getItem('sRomyCommandProxy').sendCommand(query)

}



/**
 * 
 * updates Metadata of sRomyMap so you can only pick a valid map there
 *  
 */
function updateMapMetadata(event) {
    logger.info("romyrobot updateMapMetadata because of event: " + event.itemName)

    var MetadataRegistry = osgi.getService('org.openhab.core.items.MetadataRegistry');
    var MetadataKey = Java.type('org.openhab.core.items.MetadataKey');
    var Metadata = Java.type('org.openhab.core.items.Metadata');
    var optionsValue = ""
    var jsonMapData = JSON.parse(items.getItem('sRomyAvailableMaps').state)
    for(let i = 0; i < jsonMapData.maps.length; i++) {
        let map = jsonMapData.maps[i];
        logger.info("found map: " + map.map_id + ": " + map.map_meta_data)
        if (map.permanent_flag === "true") {
            if (optionsValue.length > 0) {
                optionsValue + ","
            }
                optionsValue = optionsValue + map.map_id + "=" + map.map_meta_data
        }
    }
    MetadataRegistry.remove(new MetadataKey("commandDescription", "sRomyMap"));
    MetadataRegistry.add(new Metadata(new MetadataKey("commandDescription", "sRomyMap"), "value", {options:optionsValue}))
}

Screenshots

Thanks for the tutorial. I’ve moved it to a more appropriate category where it should be easier to find.

1 Like