Request Roborock App Binding

Hello all,

I have a Roborock S7 maxV Ultra and I want to integrate it into Openhab. I know it ist possible to do it with the miio Binding, but I don’t want to use the Xiaomi App. The Xiaomi App will not have the full functionalities as the Roborock App. I found out in the IO forum that it is possible to connect to the Roborock API. I tried out some scripts and it worked to get connection and some data. My experience in programming is not really good, so hopefully someone can generate a Binding for the Roborock API.

I think this would be very useful, because I have seen there are a lot of people who asked about it.

Thanks !

Mike

3 Likes

I’m also interested in that. Can anyone program?

You might consider to open a feature request on github and put a bounty on it. This might attract some developers to spent their free time for you.

I am also interested in this.

I have roborock s7.
I think that Xiaomi app should be needed only to extract the token.
There are several ways to extract the token… I try setting up home assistant and activating the debug log… to see the token. But doesnt’ work…
In openhab I get Error:comm i suppose because i have wrong device id… i’m not sure where i have to get. device id…

Hi,
I am working on an mqtt bridge service that provides the essentials that you can see via the Roborock app via mqtt.
I’m not creating an openhab binding because a) i don’t know how and b) it’s hard enough to debug the protocol without thinking about openhab integration.
But, since the protocol is essentially known I don’t see a reason why it won’t be possible to create a binding.
I own a S8 and things I can basically do are:

  • List homes and devices via mqtt (including some basic stats)
  • start a schema (that’s the roborock term for the routines you see on the home screen of the app
  • poll the current status (not sure that is even worth the effort, not really interested in seeing the battery drain)
  • get the map and position of things (which is still mirrored for some reason I have to investigate)
  • send the robot home (to the dock)

Using the roborock apis you don’t really need a device token. You can simply query the roborock api for the device key, which will then be used to communicate with the robot via the roborock mqtt server. Some operations, like starting a routine/schema don’t even require that, but are simple POST requests to one of the roborock apis (well, as simple as the obfuscation of the roborock api is).

Much of the reverse engineering was done and provided by rovo89 in a gist. Reading the payload of map responses is the same as for the Xiaomi app and was done by PiotrMachowski.

I’m not sure if my service will ever see the light of day, whether it will work with any other robot than the S8, or whether the whole application is way to bloated to run on my openhabian raspberry pi :person_shrugging:. I’m mostly doing it to learn Spring Boot and Kotlin (not my favorites), also for lulz and obviously automation. I’ll post again if I ever make my repository public. If there are willing Kotlin Developers who want to take a look at the source or help out, just ping me and I’ll give you access to the repo.

cheers :beers:

2 Likes

Screenshot 2023-12-04 210904

I’m running out of excuses not to publish my service. Sure it’s buggy and needs lots of improvements, but it works.
If you want to check it out, here you go

I’m aware that the instructions on how to setup and use it are … uhm … missing. It’s on the agenda. At the moment you’ll likely have to download the source code and adjust the application.yaml file and then start the whole thing with ./gradlew start (Java 17 required). I actually haven’t tried this, since I only start the application through IntelliJ at the moment.
There is a little react app in src/test/resources/map-poc for demonstration purposes (shows map, can send robot back to dock, can start cleanup routine).

Edit (1.1.2024): Instructions on how to configure and run are now in the readme, some scenarios might need more explanation.
Edit (3.1.2024): Added github actions to create releases, so you can simply download the service releases on github

In unrelated news: I read that it is possible to root the robots without disassembly see robotinfo.dev. It is definitely not recommended at the moment and there is no full valetudo support. But it looks like it might be possible “soon”.

1 Like

Do you have any updates on this and how you achieved the communication?

At the moment i’m using this:

But the Konqi solution is VERY VERY interesting:

unfortunately i have no time to migrate to this!

Hey,

thanks for that. I would love to use ist. I just tried that jar. I had to change my roborock password, because I forgot it… :slight_smile: I can login with iobroker and also with the robrock app with th password. But if I try to login with your bridge, I got “user does not exist”.

I really would love to use your bridge, because the way I’m using right now: iOBroker<–>mqtt<–>openhab is not really stable…

Any sugesstions?

Thanks

I tried it on one of my linux system an geht


org.springframework.web.client.RestClientException: Error while extracting response for type [de.konqi.roborockbridge.remote.rest.dto.login.ApiResponseDto<de.konqi.roborockbridge.remote.rest.dto.login.HomeDetailData>] and content type [application/json]
        at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:119) ~[spring-web-6.1.11.jar!/:6.1.11]
        at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1159) ~[spring-web-6.1.11.jar!/:6.1.11]
        at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1142) ~[spring-web-6.1.11.jar!/:6.1.11]
        at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:892) ~[spring-web-6.1.11.jar!/:6.1.11]
        at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790) ~[spring-web-6.1.11.jar!/:6.1.11]
        at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:701) ~[spring-web-6.1.11.jar!/:6.1.11]
        at de.konqi.roborockbridge.remote.rest.HomeApi.getHome(HomeApi.kt:35) ~[!/:0.0.8-SNAPSHOT]
        at de.konqi.roborockbridge.BridgeService.init(BridgeService.kt:62) ~[!/:0.0.8-SNAPSHOT]
        at de.konqi.roborockbridge.BridgeService.worker(BridgeService.kt:118) ~[!/:0.0.8-SNAPSHOT]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
        at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:365) ~[spring-context-6.1.11.jar!/:6.1.11]
        at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:237) ~[spring-context-6.1.11.jar!/:6.1.11]
        at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:168) ~[spring-context-6.1.11.jar!/:6.1.11]
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185) ~[spring-context-6.1.11.jar!/:6.1.11]
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178) ~[spring-context-6.1.11.jar!/:6.1.11]
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156) ~[spring-context-6.1.11.jar!/:6.1.11]
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:452) ~[spring-context-6.1.11.jar!/:6.1.11]
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:385) ~[spring-context-6.1.11.jar!/:6.1.11]
        at org.springframework.boot.context.event.EventPublishingRunListener.ready(EventPublishingRunListener.java:109) ~[spring-boot-3.3.2.jar!/:3.3.2]
        at org.springframework.boot.SpringApplicationRunListeners.lambda$ready$6(SpringApplicationRunListeners.java:80) ~[spring-boot-3.3.2.jar!/:3.3.2]
        at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
        at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) ~[spring-boot-3.3.2.jar!/:3.3.2]
        at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112) ~[spring-boot-3.3.2.jar!/:3.3.2]
        at org.springframework.boot.SpringApplicationRunListeners.ready(SpringApplicationRunListeners.java:80) ~[spring-boot-3.3.2.jar!/:3.3.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:349) ~[spring-boot-3.3.2.jar!/:3.3.2]
        at de.konqi.roborockbridge.RoborockBridgeApplicationKt.main(RoborockBridgeApplication.kt:38) ~[!/:0.0.8-SNAPSHOT]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
        at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:91) ~[roborock-bridge-0.0.8-SNAPSHOT.jar:0.0.8-SNAPSHOT]
        at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:53) ~[roborock-bridge-0.0.8-SNAPSHOT.jar:0.0.8-SNAPSHOT]
        at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:58) ~[roborock-bridge-0.0.8-SNAPSHOT.jar:0.0.8-SNAPSHOT]
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.lang.Integer` from Object value (token `JsonToken.START_OBJECT`)
        at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:406) ~[spring-web-6.1.11.jar!/:6.1.11]
        at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:354) ~[spring-web-6.1.11.jar!/:6.1.11]
        at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:104) ~[spring-web-6.1.11.jar!/:6.1.11]
        ... 35 common frames omitted
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.Integer` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 119] (through reference chain: de.konqi.roborockbridge.remote.rest.dto.login.ApiResponseDto["data"]->de.konqi.roborockbridge.remote.rest.dto.login.HomeDetailData["deviceListOrder"])
        at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1767) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1541) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1446) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:958) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseInteger(StdDeserializer.java:808) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer.deserialize(NumberDeserializers.java:531) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$IntegerDeserializer.deserialize(NumberDeserializers.java:506) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:545) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:576) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:446) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1493) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:348) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:545) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:576) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:446) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1493) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:348) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2125) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1501) ~[jackson-databind-2.17.2.jar!/:2.17.2]
        at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:395) ~[spring-web-6.1.11.jar!/:6.1.11]
        ... 37 common frames omitted

Maybe my current approach to reuse the iobroker Adapter standalone is interesting here.

It uses the iobroker Adapter as basis together with your local Mqtt server instead of iobroker.