Skip to content

Commit

Permalink
fix(vendor.viomi): Various fixes for 2020.01.1
Browse files Browse the repository at this point in the history
* Fix output of checksum errors (was binary, now is '<Buffer xx …>')

* Make the map upload server listen on 0.0.0.0 for the local development setup.

* Support hex cloudSecrets as mentioned in the documentation.

* Viomi: fix status parsing (#641)

* Merge local dev instructions for viomi and roborock. Add a few missing details.

* Simplify dummycloud setup.

We now require a patch to miio_client to change the http_dns address to
127.0.0.1. Similarly /etc/hosts should be updated to map the ot.mio
names to 127.0.0.1.
  • Loading branch information
rumpeltux committed Jan 30, 2021
1 parent 54bc2b7 commit a38987a
Show file tree
Hide file tree
Showing 13 changed files with 66 additions and 74 deletions.
45 changes: 34 additions & 11 deletions docs/_pages/development/building-and-modifying-valetudo.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,40 @@ Therefore, you need to edit the newly created file in order to be able to talk w
"robot": {
"implementation": "RoborockS5ValetudoRobot",
"implementationSpecificConfig": {
"ip": "192.168.xxx.xxx",
"ip": "192.168.xxx.robotIp",
"deviceId": 12345678,
"cloudSecret": "aBcdEfgh",
"localSecret": "123456788989074560w34aaffasf"
"localSecret": "123456788989074560w34aaffasf",
"mapUploadUrlPrefix": "http://192.168.xxx.valetudoIp:8079"
}
}
}
```

Setting embedded to `false` disables all functionality that assumes that Valetudo runs on the robot such as some file-system related things.

For a list of possible values for `implementation` consult
https://github.com/Hypfer/Valetudo/blob/master/lib/core/ValetudoRobotFactory.js#L57

The config key `robot` specifies the ValetudoRobot implementation Valetudo should use as well as some implementation-specific configuration parameters.
When running on the robot itself, these are usually detected automatically.

For roborock robots, `deviceId` and `cloudSecret` can be found in the `/mnt/default/device.conf` as `did` and `key` on the robot.
Since both values are static, you'll only need to do that once.
| Vendor | Config Key | Robot Location | Robot Key |
|----------|---------------|-----------------------------------------|-----------|
| Roborock | valetudo.conf | /mnt/data/valetudo/valetudo_config.json | |
| | deviceId | /mnt/default/device.conf | did |
| | cloudSecret | /mnt/default/device.conf | key |
| | localSecret | /mnt/data/miio/device.token | |
| Viomi | valetudo.conf | /mnt/data/valetudo/config.json | |
| | deviceId | /etc/miio/device.conf | did |
| | cloudSecret | /etc/miio/device.conf | key |
| | localSecret | /etc/miio/device.token | |

The `localSecret` can be found in the robots FS as well: `/mnt/data/miio/device.token`.
Note that this one might change when you're switching wireless networks etc.
Since `deviceId` and `cloudSecret` are static, you'll only need to do that once.
Note that `localSecret` might change when you're switching wireless networks etc.

It's possible to specify both secrets as either hex or a regular string.


Once you finished editing the configuration, you should be all set.

Please note that Valetudo will replace the configuration with a default one if it fails to parse it correctly.
Expand All @@ -86,15 +97,28 @@ npm run start

If your configuration is correct, Valetudo should now be working on your development host.

### 6. Code!
### 6. Enable dummycloud connection

The dummycloud is implemented by Valetudo, but the robot needs to connect to it.
To enable this mode (which is required for many of the functionalities such as map uploading):

1. Install Valetudo on the robot (if you haven’t done so already)
2. `ssh root@vacuum`, then stop Valetudo: `/etc/init.d/valetudo stop`.
3. Edit the `valetudo.conf` _on the robot_ and point `robot.implementationSpecificConfig.dummycloudIp`
to your local development host.
This will instruct the Valetudo process on the robot to tell the miio_client app that it should
try to connect to your development host instead.
4. `reboot`

### 7. Code!

Modify the source code according to your needs, and restart the server as needed -- you can always run it as:

```
npm run start
```

### 7. Build and install on the device
### 8. Build and install on the device

When you're done with your modifications, here's how to build the executable for the robot:

Expand All @@ -105,8 +129,7 @@ npm run build
The output file `valetudo` is a binary file that you can copy to the device:

```
scp ./valetudo root@192.168.1.42:/usr/local/bin/
scp ./valetudo root@vacuum:/usr/local/bin/
```

Once you're that far, you hopefully don't need any further advice.

36 changes: 10 additions & 26 deletions docs/_pages/installation/viomi.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ Support is still somewhat experimental, [see below](#current-state-of-viomi-supp
The default settings here will be for running Valetudo on the robot itself.
If you want to develop as well, check out the [Local Development section](#local-development-setup).

There’s a tool that aims to automate rooting and Valetudo installation at
https://github.com/rumpeltux/viomi-rooting/.
There’s a [tool to automate rooting and Valetudo installation](https://github.com/rumpeltux/viomi-rooting/).

Please give it a try and [file any issues that you encounter there](https://github.com/rumpeltux/viomi-rooting/issues).

## Current state of viomi support

Note: This list is probably out of date and some of the features may not yet be
working in the current Valetudo preview release.

* Cloud & local connection work.
* Reading basic status properties work though the rendering within the web UI
may not always be accurate.
Expand Down Expand Up @@ -51,30 +54,13 @@ instead of the xiaomi cloud:

```shell
ssh root@vacuum
sed -i 's/110.43.0.8./127.00.00.1/g' /usr/bin/miio_client
for domain in "" de. ea. in. pv. ru. sg. st. tw. us.; do
echo "203.0.113.1 ${domain}ot.io.mi.com ${domain}ott.io.mi.com" >> /etc/hosts
echo "127.0.0.1 ${domain}ot.io.mi.com ${domain}ott.io.mi.com" >> /etc/hosts
done
cat >/etc/rc.d/S51valetudo <<EOF
#!/bin/sh
iptables -F OUTPUT
iptables -t nat -F OUTPUT
# for local development enter your local development host here
# and change port to 8080
dest=127.0.0.1
port=80
for host in 203.0.113.1 203.0.113.5; do
iptables -t nat -A OUTPUT -p tcp --dport 80 -d \$host -j DNAT --to-destination \$dest:\$port
iptables -t nat -A OUTPUT -p udp --dport 8053 -d \$host -j DNAT --to-destination \$dest:8053
iptables -A OUTPUT -d \$host/32 -j REJECT
done
EOF
chmod +x /etc/rc.d/S51valetudo
reboot
```

Note: To temporarily revert this while needing to use the Mi Home App,
you can do a `iptables -F; iptables -F -t nat` and comment out the line in `/etc/hosts`.

## Deploying

Run
Expand All @@ -90,10 +76,7 @@ And deploy the `valetudo` binary to your robot:

## Local Development Setup

Follow the [development guide](https://valetudo.cloud/pages/development/building-and-modifying-valetudo.html)
in spirit, but note that path names etc. may be different.
You can get the required settings by doing `cat /etc/miio/device.conf` and
`cat /etc/miio/device.token` on the robot.
Follow the [development guide](https://valetudo.cloud/pages/development/building-and-modifying-valetudo.html).

## Firmware updates

Expand All @@ -119,7 +102,8 @@ This will remove Valetudo, free the diskspace and re-enable the cloud interface.
```shell
ssh root@vacuum
/etc/init.d/valetudo stop
rm /etc/rc.d/S51valetudo /etc/init.d/valetudo /mnt/UDISK/valetudo
rm /etc/init.d/valetudo /mnt/UDISK/valetudo
rm /overlay/usr/bin/miio_client
```

## Enable logging
Expand Down
2 changes: 1 addition & 1 deletion docs/_pages/knowledge_base/supported-3irobotix-devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ and there are multiple Device IDs in the Mi ecosystem as well:

* viomi.vacuum.v6
* viomi.vacuum.v7
* viomi.vacuum.v8 (TBC)
* viomi.vacuum.v8

![CRL-200S Viomi V2 Top](./img/devices/3irobotix/CRL-200S-viomi-v2-top.jpg)

Expand Down
2 changes: 1 addition & 1 deletion lib/miio/Codec.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Codec {
let msg = null;
if (!checksum.equals(digest)) {
if (encrypted.length > 0) {
Logger.error("Invalid packet, checksum was " + checksum + " should be " + digest);
Logger.error("Invalid checksum:", {checksum, expected: digest, packet: response, token: this.token});
} else {
// If we receive an empty packet with a wrong checksum, assume that we're getting
// a new token.
Expand Down
24 changes: 16 additions & 8 deletions lib/robots/MiioValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ class MiioValetudoRobot extends ValetudoRobot {
this.setEmbeddedParameters();

this.robotConfig = this.config.get("robot");
this.implConfig = this.robotConfig ? this.robotConfig.implementationSpecificConfig : {};
this.implConfig = (this.robotConfig && this.robotConfig.implementationSpecificConfig) || {};

this.ip = this.implConfig && this.implConfig.ip ? this.implConfig.ip : "127.0.0.1";
this.mapUploadUrlPrefix = this.implConfig && this.implConfig.mapUploadUrlPrefix ? this.implConfig.mapUploadUrlPrefix : "http://127.0.0.1:8079";
this.ip = this.implConfig.ip || "127.0.0.1";
this.embeddedDummycloudIp = this.implConfig["dummycloudIp"] || "127.0.0.1";
this.dummycloudBindIp = this.implConfig["dummycloudBindIp"] || (
this.config.get("embedded") ? "127.0.0.1" : "0.0.0.0");

this.dummycloudBindIp = this.config.get("embedded") ? this.embeddedDummycloudIp : "0.0.0.0";
this.mapUploadUrlPrefix = this.implConfig.mapUploadUrlPrefix || (
"http://" + this.embeddedDummycloudIp + ":8079");

this.localSocket = new RetryWrapper(
(() => {
Expand All @@ -53,7 +56,7 @@ class MiioValetudoRobot extends ValetudoRobot {
);

this.dummyCloud = new Dummycloud({
spoofedIP: "203.0.113.1", //RFC 5735 TEST-NET-3
spoofedIP: this.embeddedDummycloudIp,
cloudSecret: this.cloudSecret,
deviceId: this.deviceId,
bindIP: this.dummycloudBindIp,
Expand Down Expand Up @@ -141,7 +144,7 @@ class MiioValetudoRobot extends ValetudoRobot {
const info = {
"host_list": [
{
"ip": "203.0.113.1",
"ip": this.embeddedDummycloudIp,
"port": Dummycloud.PORT
}
],
Expand All @@ -158,7 +161,7 @@ class MiioValetudoRobot extends ValetudoRobot {
});
});

this.mapUploadServer.listen(8079, "127.0.0.1", function() {
this.mapUploadServer.listen(8079, this.dummycloudBindIp, function() {
Logger.info("Map Upload Server running on port " + 8079);
});
}
Expand Down Expand Up @@ -209,6 +212,12 @@ class MiioValetudoRobot extends ValetudoRobot {
cloudSecret = MiioValetudoRobot.READ_DEVICE_CONF(this.deviceConfPath)["key"];
}

if (cloudSecret && cloudSecret.length >= 32) {
// For local development, people might put in the hex representation of the token.
// Make this work too.
return Buffer.from(cloudSecret.toString().slice(0, 32), "hex");
}

return Buffer.from(
cloudSecret ? cloudSecret : "0000000000000000" // This doesnt work but it wont crash the system
);
Expand All @@ -217,7 +226,6 @@ class MiioValetudoRobot extends ValetudoRobot {
setEmbeddedParameters() {
this.deviceConfPath = "/dev/null";
this.tokenFilePath = "/dev/null";
this.embeddedDummycloudIp = "203.0.113.1";
}

/**
Expand Down
1 change: 0 additions & 1 deletion lib/robots/dreame/DreameValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class DreameValetudoRobot extends MiioValetudoRobot {
setEmbeddedParameters() {
this.deviceConfPath = DreameValetudoRobot.DEVICE_CONF_PATH;
this.tokenFilePath = DreameValetudoRobot.TOKEN_FILE_PATH;
this.embeddedDummycloudIp = "203.0.113.1"; //required for the lo alias approach
}

onMessage(msg) {
Expand Down
5 changes: 0 additions & 5 deletions lib/robots/roborock/RoborockS5MaxValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ class RoborockS5MaxValetudoRobot extends RoborockValetudoRobot {
return "S5 Max";
}

setEmbeddedParameters() {
super.setEmbeddedParameters();
this.embeddedDummycloudIp = "203.0.113.1"; //required for the lo alias approach
}

static IMPLEMENTATION_AUTO_DETECTION_HANDLER() {
const deviceConf = MiioValetudoRobot.READ_DEVICE_CONF(RoborockValetudoRobot.DEVICE_CONF_PATH);

Expand Down
6 changes: 0 additions & 6 deletions lib/robots/roborock/RoborockS6MaxVValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ class RoborockS6MaxVValetudoRobot extends RoborockValetudoRobot {
return "S6 MaxV";
}

setEmbeddedParameters() {
super.setEmbeddedParameters();
this.embeddedDummycloudIp = "203.0.113.1"; //required for the lo alias approach
}


static IMPLEMENTATION_AUTO_DETECTION_HANDLER() {
const deviceConf = MiioValetudoRobot.READ_DEVICE_CONF(RoborockValetudoRobot.DEVICE_CONF_PATH);

Expand Down
6 changes: 0 additions & 6 deletions lib/robots/roborock/RoborockS6PureValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ class RoborockS6PureValetudoRobot extends RoborockValetudoRobot {
return "S6 Pure";
}

setEmbeddedParameters() {
super.setEmbeddedParameters();
this.embeddedDummycloudIp = "203.0.113.1"; //required for the lo alias approach
}


static IMPLEMENTATION_AUTO_DETECTION_HANDLER() {
const deviceConf = MiioValetudoRobot.READ_DEVICE_CONF(RoborockValetudoRobot.DEVICE_CONF_PATH);

Expand Down
5 changes: 0 additions & 5 deletions lib/robots/roborock/RoborockS6ValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ class RoborockS6ValetudoRobot extends RoborockValetudoRobot {
return "S6";
}

setEmbeddedParameters() {
super.setEmbeddedParameters();
this.embeddedDummycloudIp = "203.0.113.1"; //required for the lo alias approach
}

static IMPLEMENTATION_AUTO_DETECTION_HANDLER() {
const deviceConf = MiioValetudoRobot.READ_DEVICE_CONF(RoborockValetudoRobot.DEVICE_CONF_PATH);

Expand Down
1 change: 0 additions & 1 deletion lib/robots/roborock/RoborockValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ class RoborockValetudoRobot extends MiioValetudoRobot {
setEmbeddedParameters() {
this.deviceConfPath = RoborockValetudoRobot.DEVICE_CONF_PATH;
this.tokenFilePath = RoborockValetudoRobot.TOKEN_FILE_PATH;
this.embeddedDummycloudIp = "127.0.0.1"; //required for the iptables redirect to work
}


Expand Down
5 changes: 3 additions & 2 deletions lib/robots/viomi/ViomiValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ class ViomiValetudoRobot extends MiioValetudoRobot {
setEmbeddedParameters() {
this.deviceConfPath = ViomiValetudoRobot.DEVICE_CONF_PATH;
this.tokenFilePath = ViomiValetudoRobot.TOKEN_FILE_PATH;
this.embeddedDummycloudIp = "127.0.0.1"; //required for the iptables redirect to work
}

onMessage(msg) {
Expand Down Expand Up @@ -122,7 +121,9 @@ class ViomiValetudoRobot extends MiioValetudoRobot {
const response = await this.sendCommand("get_prop", STATE_PROPERTIES);

if (response) {
this.parseAndUpdateState(response[0]);
let statusDict = {};
STATE_PROPERTIES.forEach((key, index) => statusDict[key] = response[index]);
this.parseAndUpdateState(statusDict);
}

return this.state;
Expand Down
2 changes: 1 addition & 1 deletion lib/webserver/WebServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class WebServer {
const info = {
"host_list": [
{
"ip": "203.0.113.1",
"ip": this.robot.embeddedDummycloudIp,
"port": 8053
}
],
Expand Down

1 comment on commit a38987a

@Kaan88
Copy link

@Kaan88 Kaan88 commented on a38987a Jan 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on viomi, cannot configure zones and change power mode anymore. should I create an issue?

Please sign in to comment.