Integração NodeRed + RobockRock Xiaomi + Alexa Action



image


Esse tutorial tem a ideia de mostrar como é feito a integração entre um ROBOCK XIAOMI dos modelos S4 a S7 + Node Red + Alexa Action.

Primeiro de tudo temos que ter seu Robock configurado no aplicativo oficial da Xiaomi.

PASSO 1
Integra-lo ao Home Assistant via Xiaomi Miio
OBS: O ponto aqui é configurar ele de forma manual, para que possamos extrair o mapa dele.

Usuário → E-mail do APP
Senha → Senha do APP
País → Entrar no link disponível no pop-up para ver o código do país. Caso esteja no Brasil o código é US


IP → IP do equipamento que está na sua rede 162.168.X.X
Token da API → Seguir o passo a passo do link do Pop-up ou acessar esse repositório que tem um executável para gerar o Token API.

OBS: Salve o seu token pois vamos utilizar ele em outro momento.

PASSO 2
Instalar o Map extractor via HACS para o home assistant ter acesso ao mapa do RoboRock.
repositório do Map Extractor

Abrir seu arquivo configuration.yaml e adicionar uma câmera.

 camera:
  - platform: xiaomi_cloud_map_extractor
     host: !secret xiaomi_vacuum_host
     token: !secret xiaomi_vacuum_token
     username: !secret xiaomi_cloud_username
     password: !secret xiaomi_cloud_password
     draw: ['all']
    attributes:
       - calibration_points

OBS: Lembra do token que salvamos no passo anterior?

O meu está extenso pois eu li toda a documentação e apliquei de acordo com as minhas necessidades.
Para mais informações aqui está o repositório

Realizar um restart no home assistant:

Criar um painel com a câmera xiaomi_cloud_map_extractor:

Pronto passo 2 finalizado.

Passo 3
Mapeando sua residência:
Para mapear a sua residência primeiro faça o RoboRock (Na minha casa eu coloquei o apelido de Judite, para casos futuros vou colocar Judite… rs)
Depois que a Judite fez o mapeando da sua casa, a câmera vai estar com todos os cômodos.

Edite sua câmera e coloque a flag DEBUG: true e salve as alterações.

Clique no ícone de +, e redimensione para a área de limpeza e clique no Play, isso fara um alerta com as coordenadas do seu cômodo.

Os dados do JSON que nos importa é o ZONE.
Salve todos as “zonas” em algum lugar.
Pronto passo 3 finalizado.

Passo 4
Melhorando sua SKILL no Alexa Action:

Não vou postar toda minha skill aqui, pois estou criando ela para fins comerciais.

A ideia é que você acione a Judite via alexa para limpar um cômodo X vezes.

Realizar integrações e customizações com a Alexa no Home Assistant as vezes é uma tarefa bem árdua.
Neste passo vamos evoluir a Alexa Actions que existe em muitos tutorias na internet.
Caso não tenha a skill Alexa Actions segue o link com um tutorial bem completo que tem no YouTube.

Para evoluir a skill recomendo ter uma noção de desenvolvimento e entendimento da biblioteca da Amazona Alexa.
Pré-requisito para essa integração:
Ter o Media Player Alexa

Links matérias de apoio para quem não tem nenhuma dessas integrações:

Continuando o passo a passo.
Abra seu Developer Console e vá até sua skill.

Vá até SlotTypes, e nesse passo é necessário criar 3 novos Slots.
Acao (Ação que a Judite irá realizar é o “Limpar” a única obrigatória no cadastro de vocês)

Modo de trabalho (Velocidade é tipo de limpeza que a Judite irá fazer)

Cômodo da Casa (Insira todos os cômodos da sua casa)

Clique em BUILD MODEL

Após a criação dos Slot Types, vamos criar uma intenção para a Alexa, ou seja, a frase que a Alexa irá interpretar ao acionarmos nossa Skill.
Para isso acesse o menu Interaction Model => Intents.
Crie uma intenção (Intents) chamado TextoComplexo


{Acao} {ComodoCasa} {Qtd} vez
{Acao} {ComodoCasa} no modo {ModoTrabalho} {Qtd} vez
{Acao} {ComodoCasa} no modo {ModoTrabalho} {Qtd} vezes
{Acao} {ComodoCasa} {Qtd} vezes
Clique em BUILD MODEL

Pronto agora vamos a parte da codificação, clique em CODE acesse o lambda_function.py
Insira a função abaixo:

class TextoComplexoHandler(AbstractRequestHandler):
    """Handler for TextoComplexo Intent."""
    def can_handle(self, handler_input):
        return (ask_utils.is_intent_name("TextoComplexo")(handler_input))

    def handle(self, handler_input):
        home_assistant_object = HomeAssistant(handler_input)
        acao =  home_assistant_object.get_value_for_slot("Acao")
        comodo = home_assistant_object.get_value_for_slot("ComodoCasa")
        qtd =  ask_utils.get_slot_value(handler_input, "Qtd")
        modotrabalho =  ask_utils.get_slot_value(handler_input, "ModoTrabalho")
        
        
        if modotrabalho:
            selection  = acao + "-" + comodo + "-" + qtd + "-" + modotrabalho
        else:    
            selection  = acao + "-" + comodo + "-" + qtd
            
        if selection:
            home_assistant_object.post_ha_event(selection, RESPONSE_SELECT)
            data = handler_input.attributes_manager.request_attributes["_"]
            speak_output = data[prompts.STOP_MESSAGE]
        else:
            raise 

        return (
            handler_input.response_builder
                .speak(speak_output)
                .response
        )

Adicione a linha no final do arquivo onde todas as intenções são acopladas na Skill.

sb.add_request_handler(TextoComplexoHandler())

UFA tamo quase lá bora passo 5.

Passo 5
Node Red

Aqui está meu Workflow, porém coloquei um pequeno cenário de template para importarmos no NodeRed e entendermos melhor o flow.

[{"id":"ce1025655096ab75","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[]},{"id":"52716ff8f5b1c259","type":"switch","z":"ce1025655096ab75","name":"Tipo","property":"payload_acao_judite","propertyType":"msg","rules":[{"t":"eq","v":"Comodo1","vt":"str"},{"t":"eq","v":"Comodo2","vt":"str"},{"t":"eq","v":"Comodo3","vt":"str"},{"t":"eq","v":"Comodo4","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":810,"y":260,"wires":[["561df9f1affd233f"],["6d1b61c187ecc2fe"],["dc236d37e751e25a"],["03e5e7826cf774eb"]]},{"id":"af0293a2d46ecbf6","type":"function","z":"ce1025655096ab75","name":"","func":"const arrayMsg = msg.payload.event.event_response.split(\"-\");\nif (arrayMsg.length == 3){\n    switch (arrayMsg[0]) {\n        case \"Limpar\":\n            msg.payload_acao_judite = arrayMsg[1];\n            msg.payload_qtd_repeats_judite = parseInt(arrayMsg[2]);\n            msg.payload_set_fan_speed = \"Silent\";\n            break;\n        default:\n    }\n}\n\nif (arrayMsg.length == 4) {\n    switch (arrayMsg[0]) {\n        case \"Limpar\":\n            msg.payload_acao_judite = arrayMsg[1];\n            msg.payload_qtd_repeats_judite = parseInt(arrayMsg[2]);\n            msg.payload_set_fan_speed = tratarMododeTrabalho(arrayMsg[3]);\n            break;\n        default:\n    }\n}\n\n\nreturn msg;\n\n/**\n* @param {any} valor\n*/\nfunction tratarMododeTrabalho(valor){\n    let retorno;\n    switch (valor) {\n        case \"passar pano\":\n            retorno = \"Gentle\"\n            break;\n        case \"normal\":\n            retorno = \"Standard\"\n            break;\n        case \"médio\":\n            retorno = \"Medium\"\n            break;\n        case \"máximo\":\n            retorno = \"Turbo\"\n            break;\n        default:\n            retorno = \"Silent\"\n            break;\n    }\n\n    return retorno;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":380,"y":260,"wires":[["c104d04c20373522"]]},{"id":"3933aef1598810df","type":"comment","z":"ce1025655096ab75","name":"Alexa me escute para o apirador","info":"Alexa me escute SKILL DESENVOLVIDA","x":210,"y":180,"wires":[]},{"id":"c104d04c20373522","type":"api-call-service","z":"ce1025655096ab75","name":"Modo de limpeza","server":"9f230d2c.1e00d","version":5,"debugenabled":false,"domain":"vacuum","service":"set_fan_speed","areaId":[],"deviceId":[],"entityId":["vacuum.roborock_vacuum_a15"],"data":"{\"fan_speed\":\"{{payload_set_fan_speed}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":590,"y":260,"wires":[["52716ff8f5b1c259"]]},{"id":"8a8b0987cc188eea","type":"api-call-service","z":"ce1025655096ab75","name":"Limpeza","server":"9f230d2c.1e00d","version":5,"debugenabled":false,"domain":"xiaomi_miio","service":"vacuum_remote_control_start","areaId":[],"deviceId":[],"entityId":["vacuum.roborock_vacuum_a15"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1220,"y":500,"wires":[[]]},{"id":"0d62b97256ed3f25","type":"api-call-service","z":"ce1025655096ab75","name":"Modo de limpeza","server":"9f230d2c.1e00d","version":5,"debugenabled":false,"domain":"vacuum","service":"set_fan_speed","areaId":[],"deviceId":[],"entityId":["vacuum.roborock_vacuum_a15"],"data":"{\"fan_speed\":\"Turbo\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":990,"y":500,"wires":[["8a8b0987cc188eea"]]},{"id":"588f639902ad6b26","type":"switch","z":"ce1025655096ab75","name":"Tipo","property":"payload.event.event_response","propertyType":"msg","rules":[{"t":"eq","v":"fazer faxina","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":814,"y":442,"wires":[["0d62b97256ed3f25"]]},{"id":"9f6bd2b49b8185ef","type":"switch","z":"ce1025655096ab75","name":"Resposta diferente de NONE","property":"payload.event.event_response","propertyType":"msg","rules":[{"t":"neq","v":"ResponseNone","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":400,"y":440,"wires":[["af0293a2d46ecbf6","588f639902ad6b26"]]},{"id":"cb2250f0cfe34351","type":"server-events","z":"ce1025655096ab75","name":"Evento Alexa me escute","server":"9f230d2c.1e00d","version":1,"event_type":"alexa_actionable_notification","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"str"}],"x":140,"y":440,"wires":[["9f6bd2b49b8185ef"]]},{"id":"561df9f1affd233f","type":"api-call-service","z":"ce1025655096ab75","name":"Limpar Comodo1","server":"9f230d2c.1e00d","version":5,"debugenabled":false,"domain":"xiaomi_miio","service":"vacuum_clean_zone","areaId":[],"deviceId":[],"entityId":["vacuum.roborock_vacuum_a15"],"data":"{\t   \"zone\":[[23539,19729,27035,23610]],\t   \"repeats\":$number(payload_qtd_repeats_judite)\t}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1030,"y":200,"wires":[[]]},{"id":"6d1b61c187ecc2fe","type":"api-call-service","z":"ce1025655096ab75","name":"Limpar Escritorio","server":"9f230d2c.1e00d","version":5,"debugenabled":false,"domain":"xiaomi_miio","service":"vacuum_clean_zone","areaId":[],"deviceId":[],"entityId":["vacuum.roborock_vacuum_a15"],"data":"{\t   \"zone\":[[23509,23688,26948,28214]],\t   \"repeats\":$number(payload_qtd_repeats_judite)\t}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1030,"y":240,"wires":[[]]},{"id":"dc236d37e751e25a","type":"api-call-service","z":"ce1025655096ab75","name":"Limpar Cozinha","server":"9f230d2c.1e00d","version":5,"debugenabled":false,"domain":"xiaomi_miio","service":"vacuum_clean_zone","areaId":[],"deviceId":[],"entityId":["vacuum.roborock_vacuum_a15"],"data":"{\t   \"zone\": [\t       [\t           23607,\t           16607,\t           26487,\t           19554        \t       ]    \t   ],\t   \"repeats\":$number(payload_qtd_repeats_judite)\t}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1020,"y":280,"wires":[[]]},{"id":"03e5e7826cf774eb","type":"api-call-service","z":"ce1025655096ab75","name":"Limpar Corredor","server":"9f230d2c.1e00d","version":5,"debugenabled":false,"domain":"xiaomi_miio","service":"vacuum_clean_zone","areaId":[],"deviceId":[],"entityId":["vacuum.roborock_vacuum_a15"],"data":"{\t   \"zone\":[[23633,11640,24513,16853]],\t   \"repeats\":$number(payload_qtd_repeats_judite)\t}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1010,"y":320,"wires":[[]]},{"id":"9f230d2c.1e00d","type":"server","name":"Home Assistant","version":4,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":false,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m"}]

Aqui está um video pequeno do funcionamento completo da minha skill com a Judite.

Espero que tenha ajudado alguem, não sou de entrar muito aqui para postar as coisas , mais esse forum vem me ajudando muito com minhas automações malucas.

1 Like

esta dano um erro no developer, com seguinte msg
developer

Outra coisa no payload ta como judite, esse nome e referente ao HA ou algum outro local, o meu se chama roborock s7 msm, sera interessante mudar o nome?

e tambem nao consegui colocar ela responder quando falo “alexa, me escuta”

Opa as intenções estão dando erro, pq a mensagem está repetida

{Acao} {ComodoCasa} {Qtd} vez

As intenções não podem se repetir… arrumei a DOC…

{Acao} {ComodoCasa} {Qtd} vez
{Acao} {ComodoCasa} no modo {ModoTrabalho} {Qtd} vez
{Acao} {ComodoCasa} no modo {ModoTrabalho} {Qtd} vezes
{Acao} {ComodoCasa} {Qtd} vezes

Outra coisa no payload ta como judite, esse nome e referente ao HA ou algum outro local, o meu se chama roborock s7 msm, sera interessante mudar o nome?

Essa variável que menciona é a que está na function, caso queira mudar vai de você, e sim vc tem q apontar para o seu device

e tambem nao consegui colocar ela responder quando falo “alexa, me escuta”

Me escuta é o nome da minha skill, se sua skill se chamar batatinha vc tem que acionar ela como Alexa batatinha…
Claro lembrando que a skill tem q ser customizada para ser acionada.

@luisfpadovani vc configurou o um script para fazer igual no video? quando eu chamo a alexa, unica coisa que ela responde " ola, estou aqui. comeco a escutar quando utilizar a palavra de ativacao que voce configurou". sabe que pode ser ( criei outra skill com o nome “me escuta”, para facilitar meu proprio entendimento)

@Rafael123 depende de como você configurou sua skill, o que eu posso ajudar no caso é tente acionar sua skill e falar para ela limpar um comodo da sua casa com esse flow ativo no nodered, lembrando que vc tem que ter a skill de actions configurada corretamente.

[
    {
        "id": "ce1025655096ab75",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "cb2250f0cfe34351",
        "type": "server-events",
        "z": "ce1025655096ab75",
        "name": "alexa_actionable_notification",
        "server": "9f230d2c.1e00d",
        "version": 1,
        "event_type": "alexa_actionable_notification",
        "exposeToHomeAssistant": false,
        "haConfig": [
            {
                "property": "name",
                "value": ""
            },
            {
                "property": "icon",
                "value": ""
            }
        ],
        "waitForRunning": true,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "eventData"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "$outputData(\"eventData\").event_type",
                "valueType": "jsonata"
            },
            {
                "property": "event_type",
                "propertyType": "msg",
                "value": "$outputData(\"eventData\").event_type",
                "valueType": "str"
            }
        ],
        "x": 140,
        "y": 440,
        "wires": [
            [
                "85d531862469bd56"
            ]
        ]
    },
    {
        "id": "85d531862469bd56",
        "type": "debug",
        "z": "ce1025655096ab75",
        "name": "debug 1",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 420,
        "y": 440,
        "wires": []
    },
    {
        "id": "9f230d2c.1e00d",
        "type": "server",
        "name": "Home Assistant",
        "version": 4,
        "addon": true,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "y|yes|true|on|home|open",
        "connectionDelay": false,
        "cacheJson": true,
        "heartbeat": false,
        "heartbeatInterval": "30",
        "areaSelector": "friendlyName",
        "deviceSelector": "friendlyName",
        "entitySelector": "friendlyName",
        "statusSeparator": "at: ",
        "statusYear": "hidden",
        "statusMonth": "short",
        "statusDay": "numeric",
        "statusHourCycle": "h23",
        "statusTimeFormat": "h:m"
    }
]

O certo é vc ter um retorno parecido com esse payload

,{"payload":{"event_type":"alexa_actionable_notification","event":{"event_id":"activate_alexa_actionable_me_escute","event_response":"Limpar-sala-1","event_response_type":"ResponseSelect","event_person_id":"amzn1.ask.person.AIT6G2DGK5TNTU7HFKIYUEBQDAWDU45T3GZY2APHWM4XUELF3CI3CXW27NBTHMJCXSVWTPOTINF2ZZDIYH3GEKCZTF6BFQV7FRQUDT4K"},"origin":"REMOTE","time_fired":"2022-08-28T14:54:07.493103+00:00","context":{"id":"01GBJF4645YVDP9RHT7F89AGFA","parent_id":null,"user_id":"c72b78f09cbc4e6c9ddee4eabc66415c"}},"topic":"alexa_actionable_notification","event_type":"$outputData(\"eventData\").event_type","_msgid":"29a8d4e3c4d64620"}

nesse flow deu certo, mais msm assim nao conseguir fazer funcionar, mais vou dar mais uma pesquisada de como fazer funcionar

e nem vai conseguir, o cara colocou 1/5 de todo o python aqui… não vai colocar tudo porque como ele disse, é para fins comerciais, inclusive esse post dele foi para divulgar o trabalho