Roborock/Xiaomi Vacuum: Backup und Restore der gespeicherten Karte über MQTT

Begonnen von Thyraz, 05 Juni 2019, 23:09:57

Vorheriges Thema - Nächstes Thema

MadMax-FHEM

Zitat von: dominik am 16 Mai 2020, 15:16:35
Danke für die Anleitung!

Könnte man das auch direkt am Saugroboter lösen? Die lastmap kann man in einem anderen Verzeichnis wegspeichern, aber mir fehlt noch die Information im Saugroboter wann er lädt. Hast du da vielleicht eine Idee?

Entweder beim Booten oder man "schießt" die player "App" ab...
...startet autom. nach...

Gruß, Joachim
FHEM PI3B+ Bullseye: HM-CFG-USB, 40x HM, ZWave-USB, 13x ZWave, EnOcean-PI, 15x EnOcean, HUE/deCONZ, CO2, ESP-Multisensor, Shelly, alexa-fhem, ...
FHEM PI2 Buster: HM-CFG-USB, 25x HM, ZWave-USB, 4x ZWave, EnOcean-PI, 3x EnOcean, Shelly, ha-bridge, ...
FHEM PI3 Buster (Test)

dominik

Nach dem Booten ist zu selten, da der Saugroboter fast nie neu startet.

Ich habe schon einen Weg gefunden. Man kann in /run/shm/navmap* monitoren. Solange diese Datei vorhanden ist, ist der Roboter unterwegs, wenn die Datei weg ist, kann man die last_map ersetzen.

Hier mal meine Testversion:
#!/bin/bash

NAVMAP=/run/shm/navmap*.ppm
SAVEDMAP=/mnt/data/rockrobo/saved_map
LASTMAPPATH=/mnt/data/rockrobo/last_map

while true; do
  if ls $NAVMAP 1> /dev/null 2>&1; then
    echo "Roborock cleaning, do not update last_map"
  else
    echo "Roborock docked or stopped, check last_map update"
    if cmp -s "$SAVEDMAP" "$LASTMAPPATH"; then
      echo "No new last_map, everything ok"
    else
      echo "New last_map found, overwrite last_map with saved one"
      cp $SAVEDMAP $LASTMAPPATH
      killall player
    fi
  fi
  sleep 60
done


Ich lass das mal morgen in der Console per SSH laufen und wenn es funktioniert, bau ich den Start in rc.local ein, damit laeuft es dann automatisch und macht alle 60s den Check.
fhempy -  https://github.com/fhempy/fhempy: GoogleCast, Tuya, UPnP, Ring, EQ3BT, Nespresso, Xiaomi, Spotify, Object Detection, ...
Kaffeespende: https://paypal.me/todominik

MadMax-FHEM

Zitat von: dominik am 16 Mai 2020, 19:20:35
Nach dem Booten ist zu selten, da der Saugroboter fast nie neu startet.

Ich weiß aber du wolltest ja wissen "wann" ;)

Viel Erfolg!

Gruß, Joachim
FHEM PI3B+ Bullseye: HM-CFG-USB, 40x HM, ZWave-USB, 13x ZWave, EnOcean-PI, 15x EnOcean, HUE/deCONZ, CO2, ESP-Multisensor, Shelly, alexa-fhem, ...
FHEM PI2 Buster: HM-CFG-USB, 25x HM, ZWave-USB, 4x ZWave, EnOcean-PI, 3x EnOcean, Shelly, ha-bridge, ...
FHEM PI3 Buster (Test)

dominik

Achso, macht der Saugroboter bei jedem neu laden in der Dock einen Reboot?
fhempy -  https://github.com/fhempy/fhempy: GoogleCast, Tuya, UPnP, Ring, EQ3BT, Nespresso, Xiaomi, Spotify, Object Detection, ...
Kaffeespende: https://paypal.me/todominik

MadMax-FHEM

Zitat von: dominik am 16 Mai 2020, 20:06:27
Achso, macht der Saugroboter bei jedem neu laden in der Dock einen Reboot?

Denke nicht...

Das mit dem "killen" der "player App" ist schon der richtige Weg...

Gruß, Joachim
FHEM PI3B+ Bullseye: HM-CFG-USB, 40x HM, ZWave-USB, 13x ZWave, EnOcean-PI, 15x EnOcean, HUE/deCONZ, CO2, ESP-Multisensor, Shelly, alexa-fhem, ...
FHEM PI2 Buster: HM-CFG-USB, 25x HM, ZWave-USB, 4x ZWave, EnOcean-PI, 3x EnOcean, Shelly, ha-bridge, ...
FHEM PI3 Buster (Test)

dominik

Ich hab es jetzt mal in die rc.local mit aufgenommen und das Script wird erfolgreich beim Starten geladen. Mal sehen ob morgen dann alles automatisch klappt.

Hier meine Anpassungen:

/opt/rockrobo/scripts/lastmaprestore.sh
#!/bin/bash

sleep 600

NAVMAP=/run/shm/navmap*.ppm
SAVEDMAP=/mnt/data/rockrobo/top40_map
LASTMAPPATH=/mnt/data/rockrobo/last_map

while true; do
  if ls $NAVMAP 1> /dev/null 2>&1; then
    echo "Roborock cleaning, do not update last_map"
  else
    echo "Roborock docked or stopped, check last_map update"
    if cmp -s "$SAVEDMAP" "$LASTMAPPATH"; then
      echo "No new last_map, everything ok"
    else
      echo "New last_map found, overwrite last_map with saved one"
      cp $SAVEDMAP $LASTMAPPATH
      killall player
    fi
  fi
  sleep 60
done


chmod +x /opt/roborock/scripts/lastmaprestore.sh

/etc/rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

/opt/rockrobo/scripts/lastmaprestore.sh &

exit 0


Bei rc.local sollte man gut aufpassen, ein Tippfehler kann dazu fuehren, dass man einen Factory Reset machen muss! Das sleep 600 nach dem Start soll nur dafuer sorgen, dass man in jeglichem Fehlerfall noch 10min Zeit hat sich per ssh zu connecten.
fhempy -  https://github.com/fhempy/fhempy: GoogleCast, Tuya, UPnP, Ring, EQ3BT, Nespresso, Xiaomi, Spotify, Object Detection, ...
Kaffeespende: https://paypal.me/todominik

dominik

Ganz perfekt ist es leider noch nicht, wenn der Saugroboter beim Fahren die Karte komplett dreht, dann sind die Spuren spaeter mit last_map ausserhalb der Karte. Ich glaube man muss die robot.db vielleicht auch noch genauer analysieren.
ChargerPos.data und StartPos.data ebenfalls zu ueberschreiben bringt keine Aenderung.

Hat sich schon jemand die robot.db mit einem SQLite Viewer angeschaut?
fhempy -  https://github.com/fhempy/fhempy: GoogleCast, Tuya, UPnP, Ring, EQ3BT, Nespresso, Xiaomi, Spotify, Object Detection, ...
Kaffeespende: https://paypal.me/todominik

dominik

In der robot.db sind 3 Tabellen drin:
- cleanmaps
- cleanrecords
- snapshots

cleanmaps:
Beinhaltet glaub ich die "Spuren".

cleanrecords:
Letzten 20 Reinigungen (m2, Zeit, Flaeche)

snapshots:
leer

Vielleicht bringt es etwas wenn man eine alte robot.db nimmt um die Karte zu drehen.
fhempy -  https://github.com/fhempy/fhempy: GoogleCast, Tuya, UPnP, Ring, EQ3BT, Nespresso, Xiaomi, Spotify, Object Detection, ...
Kaffeespende: https://paypal.me/todominik

MadMax-FHEM

Was ich kenne ist, dass man aus der DB die Karten und Wege!? rausextrahieren kann...

Habe da irgendwo ein "Tool", muss ich mal suchen...

EDIT: so hier das "Tool" (paython script):


#!/usr/bin/env python3
# See https://github.com/marcelrv/XiaomiRobotVacuumProtocol/blob/master/RRMapFile/RRFileFormat.md

import sqlite3, gzip, io, struct, sys, argparse
from datetime import datetime


def make_file_name(output_folder, timestamp, map_index):
    return '%s/%s_%d' % (output_folder, datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d_%H.%M.%S'), map_index)


def read_int(data):
    return struct.unpack('<i', data.read(4))[0]


def read_short(data):
    return struct.unpack('<h', data.read(2))[0]


def charger(data):
    pos_x = read_int(data)
    pos_y = read_int(data)
    return (pos_x, pos_y)


def grayscale_color(pixel):
    if pixel == 1:  # wall pixel
        return 128  # gray color
    else:  # outside, inside or unknown pixel
        return pixel


def rgb_color(pixel):
    if pixel == 1:  # wall pixel
        return [105, 207, 254]
    elif pixel == 255:  # inside pixel
        return [33, 115, 187]
    else:  # outside or unknown pixel
        return [pixel, pixel, pixel]


# http://netpbm.sourceforge.net/doc/pgm.html
def export_image_grayscale(data, image_len, timestamp, map_index, output_folder):
    file_name = make_file_name(output_folder, timestamp, map_index) + '.pgm'

    if image_len == 0:
        print('Warning: %s - empty image. Will not extract.' % file_name, file=sys.stderr)
        return
    else:
        print('Extracting: %s' % file_name)

    top = read_int(data)
    left = read_int(data)
    height = read_int(data)
    width = read_int(data)

    pixels = [grayscale_color(p) for p in data.read(image_len)]

    with open(file_name, 'wb') as file:
        file.write(('P5\n%d %d\n255\n' % (width, height)).encode())
        for h in range(height)[::-1]:
            file.write(bytes(pixels[h * width: h * width + width]))


# http://netpbm.sourceforge.net/doc/ppm.html
def export_image_colored(data, image_len, timestamp, map_index, output_folder):
    file_name = make_file_name(output_folder, timestamp, map_index) + '.ppm'

    if image_len == 0:
        print('Warning: %s - empty image. Will not extract.' % file_name, file=sys.stderr)
        return
    else:
        print('Extracting: %s' % file_name)

    top = read_int(data)
    left = read_int(data)
    height = read_int(data)
    width = read_int(data)
    rgb_width = width * 3

    pixels = [rgb for pixel in data.read(image_len) for rgb in rgb_color(pixel)]

    with open(file_name, 'wb') as file:
        file.write(('P6\n%d %d\n255\n' % (width, height)).encode())
        for h in range(height)[::-1]:
            file.write(bytes(pixels[h * rgb_width: h * rgb_width + rgb_width]))


def path(data, path_len, charger_pos, timestamp, map_index, output_folder):
    set_point_length = read_int(data)
    set_point_size = read_int(data)
    set_angle = read_int(data)
    image_width = (read_short(data), read_short(data))
   
    # extracting path
    path = [(read_short(data), read_short(data)) for _ in range(set_point_length)]

    # rescaling coordinates
    path = [((p[0]) // 50, (p[1]) // 50) for p in path]
    charger_pos = ((charger_pos[0]) // 50, (charger_pos[1]) // 50)

    # Creating image
    width, height = image_width[0] // 25, image_width[1] // 25

    file_name = make_file_name(output_folder, timestamp, map_index) + '_path.pgm'
   
    pixels = [0] * width * height

    for x, y in path:
        pixels[y * width + x] = 155

    for off_x in range(-2, 2):
        for off_y in range(-2, 2):
            idx = (charger_pos[1] + off_y) * width + charger_pos[0] + off_x
            pixels[idx] = 255

    with open(file_name, 'wb') as file:
        file.write(('P5\n%d %d\n255\n' % (width, height)).encode())
        for h in range(height)[::-1]:
            file.write(bytes(pixels[h * width: h * width + width]))


def parse(timestamp, bytes, do_coloring, output_folder):
    data = gzip.GzipFile(fileobj=bytes)

    magic = data.read(2)
    header_len = read_short(data)
    checksum_pointer = data.read(4)
    major_ver = read_short(data)
    minor_ver = read_short(data)
    map_index = read_int(data)
    map_sequence = read_int(data)

    while True:
        block_type = read_short(data)
        unknown = data.read(2)
        block_size = read_int(data)

        if block_type == 1:
            charger_pos = charger(data)
        elif block_type == 2:
            if do_coloring:
                export_image_colored(data, block_size, timestamp, map_index, output_folder)
            else:
                export_image_grayscale(data, block_size, timestamp, map_index, output_folder)
        elif block_type == 3:
            path(data, block_size, charger_pos, timestamp, map_index, output_folder)
        else:
            break


def main():
    parser = argparse.ArgumentParser(description='Map Extractor for Xiaomi Vacuum.\n'.format(sys.argv[0]))
    parser.add_argument('-c', '--color', dest='color', action='store_true', help='Color extracted image')
    parser.add_argument('-o', '--output', dest='output', type=str, default='.', help='Output folder')
    parser.add_argument('-f', '--file', dest='file', type=str, required=True,
                        help="Path to database file (found in '/mnt/data/rockrobo/robot.db' on vacuum)")

    args, external = parser.parse_known_args()

    file = args.file
    do_coloring = args.color
    output_folder = args.output

    with sqlite3.connect(file) as conn:
        for row in conn.cursor().execute('SELECT * FROM cleanmaps'):
            parse(row[0], io.BytesIO(row[2]), do_coloring, output_folder)


if __name__ == '__main__':
    main()


Ob man da was reinsropfen kann oder ob der sauger überhaupt danach geht, also auch reinschaut oder nur Historie reinschreibt...

Gruß, Joachim
FHEM PI3B+ Bullseye: HM-CFG-USB, 40x HM, ZWave-USB, 13x ZWave, EnOcean-PI, 15x EnOcean, HUE/deCONZ, CO2, ESP-Multisensor, Shelly, alexa-fhem, ...
FHEM PI2 Buster: HM-CFG-USB, 25x HM, ZWave-USB, 4x ZWave, EnOcean-PI, 3x EnOcean, Shelly, ha-bridge, ...
FHEM PI3 Buster (Test)

dominik

fhempy -  https://github.com/fhempy/fhempy: GoogleCast, Tuya, UPnP, Ring, EQ3BT, Nespresso, Xiaomi, Spotify, Object Detection, ...
Kaffeespende: https://paypal.me/todominik

dominik

Ich habe gerade eine alte robot.db drueber geschrieben. Funktioniert!

History ist dann natuerlich weg. Ich muss nochmals eine Drehung der Karte hinbekommen, dann sehe ich auch ob sich damit die Karte wieder sauber zurueck stellt.

//Edit
Ich glaube man braucht sowohl robot.db den richtigen Plan als letzten Eintrag und die passende last_map dazu. Dann sollte es auch 100% genau passen. Weil last_map zurueck schreiben, erzeugt meistens keine 100% richtige Darstellung der Spuren.
fhempy -  https://github.com/fhempy/fhempy: GoogleCast, Tuya, UPnP, Ring, EQ3BT, Nespresso, Xiaomi, Spotify, Object Detection, ...
Kaffeespende: https://paypal.me/todominik

MadMax-FHEM

Ich hatte allerdings schon mal das Problem, dass (beim V1) nur ein laden der gespeicherten Karte nicht genügt hat...

Da war wohl sein internes Koordinatensystem "durcheinander"...

Ich musste ihn mal kurz aus dem Dock rauslassen und wieder zurückschicken...
Dann die Karte "drüberbügeln"
Erst dann hat die Karte wieder gepasst...

Hmmm, evtl. muss ich dann (langsam) doch mal einen meiner Sauger auf MQTT-Anbindung umstellen...

Aktuell bin ich dabei das "Firmware-Erstellungs-Script" zu "zerpflücken"...
Eigentlich ganz einfach was da gemacht wird ;)

Habe schon mal das FW-Image gemounted...
...jetzt kann man ja "lustig" rumfuhrwerken etc. :)

Gruß, Joachim
FHEM PI3B+ Bullseye: HM-CFG-USB, 40x HM, ZWave-USB, 13x ZWave, EnOcean-PI, 15x EnOcean, HUE/deCONZ, CO2, ESP-Multisensor, Shelly, alexa-fhem, ...
FHEM PI2 Buster: HM-CFG-USB, 25x HM, ZWave-USB, 4x ZWave, EnOcean-PI, 3x EnOcean, Shelly, ha-bridge, ...
FHEM PI3 Buster (Test)

MadMax-FHEM

Zitat von: dominik am 17 Mai 2020, 13:13:13
Ich habe gerade eine alte robot.db drueber geschrieben. Funktioniert!

History ist dann natuerlich weg. Ich muss nochmals eine Drehung der Karte hinbekommen, dann sehe ich auch ob sich damit die Karte wieder sauber zurueck stellt.

Drehung weiß ich nicht aber prüfen, ob das mit der Karte geht:

einfach ganz böse aus der Doking aufheben und rumtun und irgendwo absetzen...

Dann stand zumindest meiner "im Wald"...

Wollte mal die Bürste reinigen und dachte gut stell ich ihn halt wieder ab und lass ihn zurücklaufen...
...hat nicht geklappt ;)

Danach hat die Karte nicht mehr gepasst...
Zum Glück hatte ich eine Sicherung :)

Viel Erfolg, Joachim
FHEM PI3B+ Bullseye: HM-CFG-USB, 40x HM, ZWave-USB, 13x ZWave, EnOcean-PI, 15x EnOcean, HUE/deCONZ, CO2, ESP-Multisensor, Shelly, alexa-fhem, ...
FHEM PI2 Buster: HM-CFG-USB, 25x HM, ZWave-USB, 4x ZWave, EnOcean-PI, 3x EnOcean, Shelly, ha-bridge, ...
FHEM PI3 Buster (Test)

dominik

robot.db ueberschreiben bringt leider nix. Bringt nur die History durcheinander, aber die Karte wird leider nicht zurueck gedreht. Hmm...ich weiss noch nicht wo das gespeichert wird.
fhempy -  https://github.com/fhempy/fhempy: GoogleCast, Tuya, UPnP, Ring, EQ3BT, Nespresso, Xiaomi, Spotify, Object Detection, ...
Kaffeespende: https://paypal.me/todominik

MadMax-FHEM

Zitat von: dominik am 17 Mai 2020, 13:24:25
robot.db ueberschreiben bringt leider nix. Bringt nur die History durcheinander, aber die Karte wird leider nicht zurueck gedreht. Hmm...ich weiss noch nicht wo das gespeichert wird.

Wo was gespeichert wird!?

Gruß, Joachim
FHEM PI3B+ Bullseye: HM-CFG-USB, 40x HM, ZWave-USB, 13x ZWave, EnOcean-PI, 15x EnOcean, HUE/deCONZ, CO2, ESP-Multisensor, Shelly, alexa-fhem, ...
FHEM PI2 Buster: HM-CFG-USB, 25x HM, ZWave-USB, 4x ZWave, EnOcean-PI, 3x EnOcean, Shelly, ha-bridge, ...
FHEM PI3 Buster (Test)