[Tuto] Bash et aspirateur robot sous Valetudo

Bonjour,

je vous propose un tuto sur l’envoi de données d’un aspirateur robot Roborock S51 sous Valetudo à Gladys via MQTT.

Si votre robot n’est pas compatible avec Valetudo, le code pourra être adapté à vos besoins, cela vous fera une base.


TUTO n°4 : Gestion de son robot aspirateur sous Valetudo dans Gladys.
EDIT 28/06/2023 : Refont complète du code du fichier RobotAspiToGladys.sh qui simplifie son utilisation
EDIT 25/06/2023 : Modification de code (JsonArrayToString en autre), d’infos sur Valetudo RE ainsi que sur la carte


Côté Aspirateur Robot :
Il faut installer Valetudo RE dessus.

Valetudo (pour la partie installation) : Getting Started | Valetudo
Valetudo RE : GitHub - rand256/valetudo: Valetudo RE - experimental vacuum software, cloud free
Commandes MQTT Valetudo RE : MQTT API · rand256/valetudo Wiki · GitHub

Il m’a fallut configurer la connexion MQTT dans Valetudo RE.
URL : mqtt://gladys:XYZ@192.168.XXX.XXX:1883

Je n’ai pas touché à la config de base :
Identifiant : rockrobo
Topic Prefix : valetudo
Autoconfiguration Prefix : homeassistant


Côté BASH :

  1. Fichier partagé : /opt/mosquitto/MQTTConfig.sh

Je vais utiliser le fichier partagé proposé avec mes autres tuto : [TUTO] Partage d'informations entre BASH et Gladys
Il permet de tester la présence de commandes, d’avoir un partage des variables de connexion à Gladys et d’apporter de la couleur dans les messages.

Il faut y rajouter le code suivant dans le bloc « Variables à personnaliser » :

# Raccourcis des topics Gladys
GladysSet="gladys/master/device"
GladysGet="gladys/device"

# Raccourcis à des appareils MQTT
AspiRobot="mqtt:salle:aspirobot/feature/mqtt:aspirobot"

En adaptant AspiRobot avec votre topic.

Il semble aussi qu’il faille modifier la variable ORANGE par :

ORANGE="\e[1m\e[38;5;202m"
  1. Script BASH : /opt/mosquitto/RobotAspiToGladys.sh

L’idée est de faire une boucle qui va surveiller l’arrivée des informations afin de les afficher dans Gladys.
Le script va envoyer toutes les informations qu’il trouve à Gladys qui ne mettra à jour que ceux affichés.
jq va remplir un array associatif avec toutes les clés et valeurs qu’il trouve pour chaque topic.
Cette notion de topic est importante car elle sera utilisée dans Gladys.

Il est possible de personnaliser les valeurs (format date par ex) et les types (text ou state).
Il faut regarder un peu le code qui est normalement assez détaillé.

Il faudra automatiser son lancement au démarrage du pc/raspberry.

Nécessite les commandes mosquitto_pub et mosquitto_sub (paquet debian mosquitto-clients) ainsi que de jq (paquet debian jq).

#!/bin/bash

#########################################################
## Renvoi des informations de l'aspirateur vers Gladys ##
#########################################################
# Nécessite les paquets : mosquitto-clients jq
# À exécuter au démarrage, sa boucle infinie surveillera en permanence ce qu'il se passe

# Chargement du fichier commun
source /opt/mosquitto/MQTTConfig.sh

# Vérification de la présence des dépendances
! CommandCheck "mosquitto_pub:mosquitto-clients" "mosquitto_sub:mosquitto-clients" "jq:jq" && exit 1

### Variables à personnaliser

# Variable raccourci définie dans Valétudo
ValeRock="valetudo/rockrobo"

# Variables pour la concaténation en texte lors de la récupération de valeurs au format array json
ValueSeparator=", "
LineSeparator="@"

### Variables à personnaliser

# Calculs pour la création du tableau
TerminalWidth=$(($(tput cols) - 2 - 54))
printf -v ColumnValueDash '─%.0s' $(seq 1 ${TerminalWidth})
printf -v ColumnValueSpace ' %.0s' $(seq 1 $(( ${TerminalWidth} - 8 )))

# Affichage des en-têtes du tableau
echo "Base du topic : ${GladysSet}/${AspiRobot}:"
echo "┌─────────────────────────────────────────────┬───────┬${ColumnValueDash}┐"
echo -e "│ ${ORANGE}Topic                                      ${RAZ} │ ${FUCHSIA}Type${RAZ}  │ ${BLEUFONCE}Valeur${RAZ} ${ColumnValueSpace}│"
echo "├─────────────────────────────────────────────┼───────┼${ColumnValueDash}┤"



# Fonction descendante dans les objets {} et qui ajoute les clés et sous-clés dans un array avec sa valeur
function AutoJsonReader()
{
    # $1 : Topic / Objet mère
    # $2 : Code JSON
    # AllValues : Nom de la variable globale qui sera remplie

    # Définition des variables locales
    local Key Value Line
    
    # Utilisation de NULL comme séparateurs, le -j permet de ne pas avoir de saut de ligne entre les entrées
    mapfile -td '' Lines < <(jq -rj 'to_entries[] | "\(.key)@@@\(.value)\u0000"' <<< "${2}")

    # Boucle sur le code JSON et renvoie clé@@@valeur
    for Line in "${Lines[@]}"
    do
        # Valeur de la clé avec ajout de la clé mère si présente
        Key="${1}${Line%%@@@*}"

        # Valeur de la clé
        Value="${Line##*@@@}"

        # Si la valeur est un objet {}
        if [[ "${Value:0:1}${Value: -1:1}" == '{}' ]]
        then
            # Relance la fonction avec ce code pour remplir les sous-clés
            AutoJsonReader "${Key}." "${Value}"

        else
            # Ajout de la (sous-)clé et sa valeur dans l'array
            AllValues["${Key}"]="${Value}"
        fi
    done
}



# Fonction convertissant un array json en texte
# L'array peut en contenir d'autres mais pas de {}
function JsonArrayToString()
{
    # Variables locales
    local NewString Line

    # Si l'array contient un sous array
    if [[ "${1:0:2}" == '[[' ]]
    then
        # Boucle traitant les array un à un
        while read Line
        do
            # Appel de la fonction avec un sous-array et récupération de sa valeur texte
            NewString+="$(JsonArrayToString "${Line}")${LineSeparator:-@}"
        done < <(jq -c '.[]' <<< "${1}")

        # Renvoi du texte final avec suppression du dernier LineSeparator
        echo "${NewString/%${LineSeparator:-@}}"

    elif [[ "${1:0:1}" == "[" ]]
    then
        # Conversion de l'array en texte
        NewString=$(jq -r --arg Separator "${ValueSeparator:-:}" 'join($Separator)' <<< "${1}")

        # Renvoi du texte soit final soit d'un sous-array
        echo "${NewString}"
    fi
}


# Boucle sur les informations envoyées par l'aspirateur
# ${ValeRock}/map_data non utilisée, remplacé la "caméra"
while read Topic JSONCode
do
    # Ne conserve que le dernier niveau du topic pour le case
    # ex : valetudo/rockrobo/state => state
    Topic="${Topic##*/}"

    # Remise au propre de l'array
    unset AllValues
    declare -A AllValues

    # Remplissage de l'array
    AutoJsonReader "${Topic}:" "${JSONCode}"

    # Dans le cas du champ erreur, il n'est retourné que si c'est le cas, je lui donne donc une valeur par défaut
    if [[ ${Topic} == "state" ]]
    then
        [[ -z ${AllValues["state:error"]} ]] && AllValues["state:error"]="Aucune erreur"
    fi

    # Boucle sur les noms des champs en récupérant.
    for Field in "${!AllValues[@]}"
    do
        # Récupération de la valeur correspondante.
        Value="${AllValues[${Field}]}"

        # Retraitement de la valeur de certaines informations, il suffit d'utiliser leur Topic comme clé
        case "${Field}" in
            # Format de type date
            "attributes:last_bin_out"|"attributes:last_bin_full"|"command_status:updated"|"attributes:last_run_stats.endTime"|"attributes:last_run_stats.startTime")
                Value=$((Value / 1000 ))
                Value=$(date +'%d/%m/%Y' -d "@$Value") ;;

            "attributes:last_run_stats.duration") Value=$((Value / 60 )) ;;
        esac

        # Type text par défaut
        ValueType="text"
        TypeSpace=" "

        # Si on veut forcer un type text, il suffit d'ajouter des clés séparées par | dans le @()
        if [[ "${Field}" != @() ]]
        then
            # Si le type est un numérique, on le passe en state
            [[ "${Value}" == ?(+|-)+([0-9])?(.|,)*([0-9]) ]] && { ValueType="state"; unset TypeSpace; }
        fi

        # Ne traite pas les valeurs null (non trouvées)
        if [[ ${Value} != "null" ]]
        then
            # Conversion des array json en texte si la valeur commence par un [ et finit par un ]
            [[ "${Value:0:1}${Value: -1:1}" == '[]' ]] && Value="$(JsonArrayToString "${Value}")"

            # Calculs pour le tableau
            printf -v FieldSpace ' %.0s' $(seq 1 $(( 43 - ${#Field} )))
            printf -v ValueSpace ' %.0s' $(seq 1 $(( ${#ColumnValueSpace} - ${#Value} + 7)))

            # Affichage des informations dans le terminal dans un tableau
            echo -e "│ ${ORANGE}${Field}${RAZ}${FieldSpace} │ ${FUCHSIA}${ValueType}${RAZ}${TypeSpace} │ ${BLEUFONCE}${Value}${RAZ}${ValueSpace}│"

            # Envoi de la valeur du bon topic à Gladys
            # Utilisation des raccourcis pour définir le topic entier
            # Il est important de bien configurer les appareils MQTT dans Gladys pour avoir le bon format de Topic
            mosquitto_pub -u "${User}" -P "${Pass}" -t "${GladysSet}/${AspiRobot}:${Field}/${ValueType}" -m "${Value}"

            # Récupération de la valeur retour de la commande mosquitto_pub
            MosquittoReturns=${?}

            # Affiche un message en cas d'erreur de mosquitto
            (( ${MosquittoReturns} )) && echo -e "[${ROUGE}Erreur${RAZ}] La commande mosquitto_sub a renvoyé le code ${MosquittoReturns}." 1>&2
        fi
    done

    # Séparateur entre les topics
    echo "├─────────────────────────────────────────────┼───────┼${ColumnValueDash}┤"
done < <(mosquitto_sub -v -u "${User}" -P "${Pass}" -t ${ValeRock}/state \
                                                    -t ${ValeRock}/attributes \
                                                    -t ${ValeRock}/command_status \
                                                    -t homeassistant/vacuum/valetudo_rockrobo/config)

Si on le lance, il affichera par ex :
bash RobotAspiToGladys.sh


Les topics sont importants, il faudra les indiquer à Gladys dans la suite.

  1. Script BASH : /opt/mosquitto/GladysToRobotAspi.sh

Il est possible d’envoyer des commandes mqtt à Valetudo.
Dans le code suivant, je propose 3 actions qui seront lancées lorsque j’appuie sur un bouton Xiaomi.
1 clic (code 1) : start
2 clics (code 2) : stop
clic long (code 5) : retour à la base

Pour changer les actions, il suffit de modifier la variable Actions.

Il faudra automatiser son lancement au démarrage du pc/raspberry.

Nécessite les commandes mosquitto_pub et mosquitto_sub (paquet debian mosquitto-clients).

#!/bin/bash

#####################################################################################################
## Surveillance du bouton spécial aspirateur et déclenchement des actions adéquate de l'aspirateur ##
#####################################################################################################
# Nécessite le paquet : mosquitto-clients

# Chargement du fichier commun
source /opt/mosquitto/MQTTConfig.sh

# Vérification de la présence des commandes
! CommandCheck "mosquitto_pub:mosquitto-clients" "mosquitto_sub:mosquitto-clients" && exit 1

### Variables à personnaliser

# Liste des actions, correspondance des codes MQTT et des commandes
Actions=(null start stop pause locate return_to_base clean_spot)

### Variables à personnaliser

# Boucle infinie de récupération de la valeur du bouton MQTT
while read Valeur
do
    # Blocage si le code envoyé est inconnu
    if [[ ${Valeur} != @([1-6]) ]]
    then
        echo -e "[${ORANGE}Attention${RAZ}] Valeur ${Valeur} inconnue..." 1>&2
        continue
    fi

    # Récupération de la commande associée au code du bouton
    Action="${Actions[${Valeur}]}"

    # Envoi de la commande à l'aspirateur via le brocker
    echo -e "Exécution de l'action '${BLEUFONCE}${Action}${RAZ}'."
    mosquitto_pub -u "${User}" -P "${Pass}" -t valetudo/rockrobo/command -m ${Action}

    # Récupération de la valeur retour de la commande mosquitto_pub
    MosquittoReturns=${?}

    # Affiche un message en cas d'erreur de mosquitto
    (( ${MosquittoReturns} )) && echo -e "[${ROUGE}Erreur${RAZ}] La commande mosquitto_sub a renvoyé le code ${MosquittoReturns}." 1>&2
done < <(mosquitto_sub -u "${User}" -P "${Pass}" -t "${GladysGet}/${AspiRobot}:action/state")

Si on l’exécute ça donne par ex :
bash GladysToRobotAspi.sh


Côté Gladys :

  1. Création de l’aspirateur au niveau MQTT :

Nom : Robot Aspirateur
Id externe : mqtt:salle:aspirobot (mqtt:Pièce:Élément)
Pièce : Salle

Dans mon cas ça donne ça :
03

Il faut adapter à ce que vous avez mis dans la variable AspiRobot du fichier bash MQTTConfig.sh

  1. Création des fonctionnalités voulues avec le bon type :

Cette étape va être longue puisqu’il faut créer toutes les fonctionnalités à gérer avec Gladys…

Je me suis créé des blocs pour rassembler les informations.
Il faut faire attention à utiliser les bons types de fonctionnalités afin que les données reçues soient compatibles.
Batterie, texte, surface ou durée…

Exemples de formatage de l’id externe des fonctionnalités :
L’important est de reprendre les topics indiqués par le script bash RobotAspiToGladys.sh et de les donner à Gladys.

Dans mon cas ça donne ça :

  1. Création de la camera affichant la carte :

Il est possible d’utiliser http://IpAspirateur/api/simple_map pour récupérer la carte, ex:

wget http://AspiRobotIP/api/simple_map -O "/tmp/AspiRobotMap.png"

Mais celle-ci redemande toutes les minutes une image, ce qui semble lourd pour l’aspirateur.
Il est possible d’installer GitHub - rand256/valetudo-mapper: Valetudo companion service qui générera une carte depuis les données MQTT.
On peut alors donner l’adresse http://ipServeur:3000/api/map/image (ou port 3002 ?) à l’appareil de type caméra.

Dans mon cas ça donne ça :
05

  1. Ajout des différentes fonctionnalités sur le tableau de bord :

Rien de spécifique ici, il faut procéder comme d’habitude.

Perso, voilà ce que j’ai fait :

  1. Gestion du bouton d’action :

Il suffit d’avoir une fonctionnalité spécifique dans l’appareil mqtt (visible dans mon screenshot)

  • Type : Inconnu
  • Nom : Action
  • Id : mqtt:aspirobot:action
  • Valeurs : de 1 à 10
  • Est-ce un capteur ? Non

D’avoir un déclencheur comme un bouton (boutton AspiRobot dans mon cas, avec une belle faute :slight_smile: )

De créer une scène au bouton qui enverra le code de l’action à la fonctionnalité ci-dessus :


Le code sera reçu par le fichier /opt/mosquitto/GladysToRobotAspi.sh.


En espérant que cela puisse servir, au moins de base ou d’idée de départ pour d’autres aspirateurs.


Valetudo :
Si vous utiliser cette version, il semble y avoir pas mal d’infos : MQTT | Valetudo

Merci à : Faire communiquer votre aspirateur robot Xiaomi Roborock en MQTT


Autres tuto :

TUTO n°1 : Afficher la température du Raspberry dans Gladys.

TUTO n°2 : Lancer une commande BASH via à une action Gladys.

TUTO n°3 : Indiquer la présence d’un utilisateur via le WI-FI de son téléphone.

3 « J'aime »

Arf, je vois que j’avais 2 fichiers bash et j’ai peut etre repris l’ancienne version, il va falloir que je vérifie ça…
Je mettrai à jour les codes si besoin.

Et pour l’image, il va falloir que je précise qu’il faut GitHub - rand256/valetudo-mapper: Valetudo companion service

je mettrai donc à jour le tuto.

J’adore ! Je possède un S6 je pense en changer un de ces jours pour un modèle avec serpillière.
Peut être qu’avant sa retraite je le passerai sous valetudo :+1:

Merci pour ce super tuto @Hizo :pray: Je vais le partager sur les réseaux sociaux Gladys

J’ai mis à jour le code avec mon autre version, cela ajoute surtout la fonction JsonArrayToString et son utilisation, du topic destinations qui n’existe pas chez moi.
Je viens de voir que j’avais commencé un taf pour récupérer automatiquement tous les champs disponibles sans avoir besoin de les préciser.
Il faut que je vois si ça vaut le coup ou non…

J’ai ajouté des précision vis à vis de Valetudo VR et de la récupération de la map.

J’avais également commencé un taf sur l’utilisation de REST API · rand256/valetudo Wiki · GitHub

Que de taf… :slight_smile:

2 « J'aime »

Je viens de refaire le code du fichier bash RobotAspiToGladys.sh

Il récupére par lui même toutes les clés et valeurs qu’il trouve et les envoie à Gladys, il est toujours possible de traiter les valeurs pour les changer en date par ex.

il détecte lui même le type de données (state ou text), mais il est possible de forcer.

Il affiche un beau tableau indiquant les topics, les types de données et les données.

2 « J'aime »