Skip to content

Commit

Permalink
fix(vendor.viomi): Viomi minor bugfixes (#820)
Browse files Browse the repository at this point in the history
* viomi: Move timezone adjustment handling to status poll

* viomi: Fix null value read

* viomi: Remove onCloudConnected override

* viomi: Fail early when rooms data end cannot be found

* viomi: map: Try to use angle reported by the robot

* viomi: map: Handle after-clean truncated map

* viomi: map: Calculated robot angle is off by 180°
  • Loading branch information
depau committed Apr 29, 2021
1 parent 908d5dd commit 4742214
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 41 deletions.
27 changes: 18 additions & 9 deletions assets/kaitai_structs/viomi_map_file.ksy
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ types:
- id: position
type: coordinate

- id: orientation
- id: angle
type: f4

virtual_wall:
Expand Down Expand Up @@ -229,8 +229,9 @@ types:
- id: target
type: coordinate

- id: unk2
type: u4
# Very wild guess
- id: angle
type: f4

realtime_pose:
seq:
Expand All @@ -240,8 +241,9 @@ types:
- id: pose
type: coordinate

- id: unk2
type: u4
# Very wild guess
- id: angle
type: f4

map:
seq:
Expand Down Expand Up @@ -350,6 +352,7 @@ types:
- id: unk1
size: 6

# TODO: reverse-engineer properly
- id: rooms2
type: rooms2
#if: rooms_len != 0
Expand All @@ -362,31 +365,37 @@ types:
repeat: expr
repeat-expr: unk_pose_stuff_len

# Hack - I wasn't able to understand this structure, it has a variable length that seems grow together with the
# number of segments, but not linearly, exponentially or anything else.
# This simply tries to eat up as many bytes until the start of the known tag value is found. Note that the tag
# varies from vacuum to vacuum
- id: unk2
type: u1
repeat: until
repeat-until: _ == rooms2.first_b_of_tag
if: map_id != 0

- id: some_header_ignore
size: 3
if: map_id != 0

- id: unk_pose_stuff
type: u8
repeat: expr
repeat-expr: 6
if: map_id != 0

- id: unk3
size: 3
if: map_id != 0

- id: pose_len
type: u4
if: map_id != 0

- id: pose
type: pose
repeat: expr
repeat-expr: pose_len
if: map_id != 0

instances:
map_id:
pos: 0x4
type: u4
66 changes: 53 additions & 13 deletions lib/robots/viomi/ViomiMapParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ class ViomiMapParser {
}
if (featureFlags & 0b000000010000000) {
let realtimePose = this.take(21, "realtime");
this.realtimePose = {position: this.readFloatPosition(realtimePose, 9)};
this.realtimePose = {
position: this.readFloatPosition(realtimePose, 9),
orientation: realtimePose.readFloatLE(13)
};
}

if (featureFlags & 0b000100000000000) {
Expand All @@ -198,11 +201,13 @@ class ViomiMapParser {
this.take(8, "unknown8");
this.parseRooms();

this.points = [];
try {
this.parsePose();
} catch (e) {
Logger.warn("Unable to parse Pose", e); //TODO
if (this.mapId !== 0) {
this.points = [];
try {
this.parsePose();
} catch (e) {
Logger.warn("Unable to parse Pose", e); //TODO
}
}
}
this.take(this.buf.length - this.offset, "trailing");
Expand All @@ -217,13 +222,30 @@ class ViomiMapParser {
path: {points: this.history},
goto_target: this.navigateTarget && this.navigateTarget.position,
robot: this.realtimePose && this.realtimePose.position,
robot_angle: this.realtimePose?.orientation,
charger: this.chargeStation && this.chargeStation.position,
charger_angle: this.chargeStation?.orientation,
virtual_wall: this.virtual_wall,
no_go_area: this.no_go_area,
clean_area: this.clean_area
});
}

/**
* Convert viomi angles to valetudo angles
*
* @private
* @param {number} angle
* @return {number}
*/
viomiToValetudoAngle(angle) {
let result = (-180 - (angle * 180 / Math.PI)) % 360;
while (result < 0) {
result += 360;
}
return result;
}

/**
* This is a temporary conversion function which should at some point be replaced with a complete rewrite
* of the viomi parser.
Expand All @@ -240,13 +262,19 @@ class ViomiMapParser {
* @param {object} [mapContents.clean_area]
* @param {object} [mapContents.goto_target]
* @param {object} [mapContents.robot]
* @param {number} [mapContents.robot_angle]
* @param {object} [mapContents.charger]
* @param {number} [mapContents.charger_angle]
*/
convertToValetudoMap(mapContents) {
const layers = [];
const entities = [];

let angle = 0;
// The charger angle is usually always provided.
// The robot angle may be 0, usually when the robot is docked.
let chargerAngle = mapContents.charger_angle !== undefined ? this.viomiToValetudoAngle(mapContents.charger_angle) : 0;
let robotAngle = mapContents.robot_angle !== undefined || mapContents.robot_angle !== 0 ? this.viomiToValetudoAngle(mapContents.robot_angle) : chargerAngle;
Logger.trace("Raw robot angle", mapContents.robot_angle, mapContents.robot_angle * 180 / Math.PI, "calculated", robotAngle);

if (mapContents.image) {
layers.push(new Map.MapLayer({
Expand Down Expand Up @@ -285,16 +313,15 @@ class ViomiMapParser {
type: Map.PathMapEntity.TYPE.PATH
}));

//TODO: This might actually be provided by the robot
//If yes, use that and only use this as a fallback
// Calculate robot angle from path if possible - the robot-reported angle is less accurate
if (mapContents.path.points.length >= 4) {
angle = (Math.round(Math.atan2(
robotAngle = (Math.round(Math.atan2(
mapContents.path.points[mapContents.path.points.length - 1] -
mapContents.path.points[mapContents.path.points.length - 3],

mapContents.path.points[mapContents.path.points.length - 2] -
mapContents.path.points[mapContents.path.points.length - 4]
) * 180 / Math.PI) + 90) % 360; //TODO: No idea why
) * 180 / Math.PI) + 270) % 360; //TODO: No idea why
}
}

Expand All @@ -309,7 +336,7 @@ class ViomiMapParser {
entities.push(new Map.PointMapEntity({
points: mapContents.robot,
metaData: {
angle: angle
angle: robotAngle
},
type: Map.PointMapEntity.TYPE.ROBOT_POSITION
}));
Expand All @@ -318,6 +345,9 @@ class ViomiMapParser {
if (mapContents.charger) {
entities.push(new Map.PointMapEntity({
points: mapContents.charger,
metaData: {
angle: chargerAngle
},
type: Map.PointMapEntity.TYPE.CHARGER_LOCATION
}));
}
Expand Down Expand Up @@ -458,15 +488,25 @@ class ViomiMapParser {
//Repeated for every room there is
this.take(unkLength * 2, "room data");

if (this.mapId === 0) {
// Sometimes the map is truncated here, when that happens the second word is zeroes
return;
}

// Hack - I wasn't able to understand this structure, it has a variable length that seems grow together with the
// number of segments, but not linearly, exponentially or anything else.
// This simply tries to eat up as many bytes until the start of the known tag value is found. Note that the tag
// varies from vacuum to vacuum
let takenBytes;
let count = 0;
do {
takenBytes = this.peek(4);
this.offset += 1;
} while (Buffer.compare(takenBytes, rooms2header.slice(0, 4)));
count += 1;
if (count >= 300) {
throw new Error("Unable to seek to end of room data");
}
} while (Buffer.compare(takenBytes, rooms2header.slice(0, 4)) && this.offset < this.buf.length);

this.take(3, "rest of tag");
}
Expand Down
28 changes: 9 additions & 19 deletions lib/robots/viomi/ViomiValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,24 +187,6 @@ class ViomiValetudoRobot extends MiioValetudoRobot {
return super.sendCloud(msg, options);
}

onCloudConnected() {
super.onCloudConnected();

setTimeout(() => {
this.sendCommand("get_prop", ["timezone"], {timeout: 12000}).then((res) => {
if (res.length > 0) {
const timezone = res[0];
if (timezone !== 0) {
// Set timezone to UTC
this.sendCommand("set_timezone", [0], {timeout: 12000}).then(_ => {
Logger.info("Viomi timezone adjusted to UTC");
});
}
}
});
}, 5000);
}

onMessage(msg) {
switch (msg.method) {
case "_sync.gen_tmp_presigned_url":
Expand All @@ -231,7 +213,7 @@ class ViomiValetudoRobot extends MiioValetudoRobot {
}
}

if (msg.method.startsWith("prop.")) {
if (msg.method?.startsWith("prop.")) {
this.parseAndUpdateState({
[msg.method.substr(5)]: msg.params[0]
});
Expand Down Expand Up @@ -478,6 +460,13 @@ class ViomiValetudoRobot extends MiioValetudoRobot {
}));
}

// Adjust timezone if != UTC
if (data["timezone"] !== undefined && data["timezone"] !== 0) {
this.sendCommand("set_timezone", [0], {timeout: 12000}).then(_ => {
Logger.info("Viomi timezone adjusted to UTC");
});
}

this.emitStateAttributesUpdated();
}

Expand Down Expand Up @@ -586,6 +575,7 @@ const STATE_PROPERTIES = [
"has_map",
"is_mop",
"has_newmap",
"timezone"
];

const STATUS_MAP = Object.freeze({
Expand Down

0 comments on commit 4742214

Please sign in to comment.