dec 132018
 

Toen ik besloot om de uitleg over mijn tweet van zondagavond op te knippen in drie in plaats van twee delen, was dat omdat ik vond dat na de uitleg over de terminologie in deel 1, het verstandiger was om eerst een eenvoudig voorbeeld uit te werken in deel 2. Maar dat heeft ook tot gevolg dat dit deel 3 een stuk eenvoudiger moet zijn om te volgen ondanks dat het resultaat dat zeker niet is.

De app van gisteren deed niet veel zinvols en had één intent (+ 2 ingebouwde) in totaal. Deze app combineert twee groepen vragen (in totaal 7 intents) in één app. Wat kun je doen?

  1. Je kunt vragen of een van de familieleden thuis is of niet.
  2. Je kunt vragen wat de temperatuur of de luchtvochtigheid is van een aantal sensoren in Home Assistant

Voor dit deel 3 ga ik er vanuit dat je deel 2, gelezen én nagebouwd hebt. Dat betekent dat je de Handmatige setup – deel 2 voor het configureren van Dialogflow uitgevoerd hebt, dat je een intent.yaml op je Home Assistant server hebt staan, dat je een webhook geconfigureerd hebt en die ook al eens succesvol gebruikt hebt (in je persoonlijke routeplanner). En ik ga er ook vanuit dat je al eens een app gemaakt hebt (als onderdeel van deel 2).

OK? Goed, dan aan de slag.

We starten weer in de Actions console op https://console.actions.google.com/
Klik op Add/import project. Maak een project aan genaamd “Home Assistant“, zet de standaard taal op Nederlands en land op Nederland. Klik op Create Project.
De eerste pagina die je dan ziet heeft als optie “Decide how your Action is invoked”. Klik op deze link en voeg ook heir als naam in “Home Assistant” en klik op “Save”.
Kies nu aan de linkerkant “Actions” en dan “ADD YOUR FIRST ACTION”. We gaan voor de default, de CUSTOM action, dus klik meteen op BUILD.

Je bent nu weer terug in Dialogflow voor je nieuwe project. Boven in het scherm staat (op dit moment) ook nu weer een melding: Dialogflow API V1 will be deprecated on October 23rd, 2019. Learn how to migrate to API V2 hereDie melding is voor ons relevant omdat Home Assistant op dit moment nog geen ondersteuning heeft voor API V2, maar klik hem met DISMISS voor nu toch nog maar even weg.

Let op: onder aan dit bericht leg ik je uit hoe je een app kunt exporteren en later weer kunt importeren. Ik heb het resultaat van mijn app ook geëxporteerd zodat jij hem kunt importeren. Het doel van deze blogpost is om je uit te leggen hoe en waarom het werkt!!

We hebben twee verschillende functies voor de app, die gaan we onafhankelijk van elkaar bouwen. Maar we gaan eerst even een drietal algemene intents maken. Klik links in het menu op het plusje om een nieuwe intent aan te maken. Noem deze “actions_intent_CANCEL”. Bij Events voeg je toe het event “actions_intent_CANCEL”

Bij Responses voeg je een aantal meldingen toe die worden gegeven op het moment dat de app wordt afgesloten. Bijvoorbeeld “Tot ziens. Als je me nodig hebt, dan weet je me te vinden.” Zie ook de afbeelding hierboven.

Wijzig het aanwezige “Default Fallback Intent” zodat er een aantal zinnen staan die uitgesproken worden als de gebruiker iets zegt dat niet door Dialogflow verwerkt kan worden (zie hieronder)

Voor de “Default Welcome Intent” kun je de openingszinnen eveneens aanpassen. Zie hieronder.

Vul nu bij Fulfillment (links in het menu) bij Webhook ook hier de url van je Home Assistent webhook in en klik onder aan de pagina op SAVE.

Nu zijn we klaar om te beginnen.

Als eerste optie: is een van de familieleden thuis of niet.
Dit is de eenvoudigste optie van de twee die we in dit bericht beschrijven. Het volgt eigenlijk de structuur van het eenvoudige voorbeeld van gisteren:
* Maak een Entity aan met als naam “Naam” en voor de variabelen de namen van de gezinsleden. Als je troetelnaampjes, koosnamen, bijnamen hebt voor de gezinsleden, voeg die dan in als synoniemen. Voeg ook een optie “Iedereen” toe met synoniemen “allemaal”, “het gezin” etc.

Maak nu een Intent aan met de naam “WieIsThuis” en voeg een aantal trainingszinnen toe (zie hieronder voor voorbeelden). De namen van de gezinsleden moeten door Dialogflow bij het invoeren al herkend worden.

Bij Action and parameters voeg je “WieIsThuis” in als actie die je in intent.yaml aan wilt roepen. en je vinkt de optie voor het gebruik van een webhook aan.

Sla de intent op.

Net als bij deel 2 moeten we nu nog de benodigde code toevoegen aan intent.yaml op de Home Assistant server.

Hierboven zie je de code die ik daar voor gebruik. Die vergt waarschijnlijk wat toelichting:

{% set x = Naam %} slaat de waarde die door Dialogflow doorgegeven wordt in de lokale variabele x
{% if Naam.lower() == 'iedereen' %} als je vraag “Waar is iedereen?” in plaast van bijvoorbeeld “Waar is Pierre?” dan loopt Home Assistent met de for – endfor loop die volgt door alle sensoren heen die onderdeel uitmaken van groep group.personen_track waarin voor elk lid van de familie een sensor zit die de waarde “Thuis” of “niet thuis” kan bevatten.

Voor mij ziet die er bijvoorbeeld zo uit:

Dat helpt je wellicht nog niet veel, want die device_tracker.xxxxx waarde is weer redelijk specifiek voor mijn situatie. Ik heb een Asus router die ondersteund wordt door Home Assistent via de Auswrt Device Tracker. Daarmee kun je in de gaten houden of een apparaat, bijvoorbeeld een smartphone, verbinding maakt met het netwerk. Ik heb dat ingesteld voor alle vier onze telefoons.
Het werkt zeker niet perfect. Een van mijn kids zet ’s nachts de wifi en 4G van de telefoon uit. Dan wordt de telefoon als afwezig weergegeven. Een van de andere telefoons gaat soms in een soort besparingsstand en lijkt dan ook offline. Het is ook zeker niet meer dan een proof of concept, want met WhatsApp weer je tegenwoordig gemakkelijk genoeg wie waar is en hoe laat iedereen weer thuis is (op het moment dat je dat echt wilt weten).
Maar goed, het gaat even voor het idee. Ik heb een verzameling sensoren en op deze manier kan ik een statusoverzicht van ze allemaal krijgen (voeg ik er eentje toe in Home Assistant, dan wordt hij automatisch meegenomen in het overzicht. De code:

{% for entity in states.group.personen_track.attributes.entity_id -%}
  {% set parts = entity.split('_') %}
  {% set name = parts[0].split('.') %}
  {%- if loop.first %} Ik loop ze allemaal even langs: {% elif loop.last %} en {% else %}, {% endif -%}{{ name[1] }} is {{ states(entity)}}
  {%- endfor %}

De “for” regel geeft aan dat ik door de ID’s van de sensoren in de groep person_track wil lopen. Die ID’s hebben als naam de volgende structuur: sensor.pierre_aanwezig. Daarom haal ik eerst het laatste deel er vanaf en dan het eerste deel om (in dit geval) “pierre” over te houden. Zonder hoofdletter, die moet ik eigenlijk nog toevoegen (automatisch).

Dan komt er een mooi stukje met if loop.first, loop.last waarbij je eenvoudig kunt kijken of het de eerste keer (dan zeg je “Ik loop ze allemaal even langs: “), de laatste keer (” en “) of een van de tussenliggende keren (“, “)  zegt voordat je de naam en de thuis / niet thuis status zegt.

Heb je nou niet om iedereen gevraagd, maar om iemand specifiek, dan is de code wat gemakkelijker:

{% else %}
  {% set y = 'sensor.%s_aanwezig' | format(x) %}
  {{ Naam }} is op dit moment {{ states(y) }}
{% endif %}

Ook die tweede met “set” is een mooie constructie om te kennen: x bevat de naam waar naar gevraagd wordt, bijvoorbeeld “pierre”. In de tweede regel wordt “%s” vervangen door de waarde van x waardoor y uiteindelijk de waarde “sensor.pierre_aanwezig” krijgt. Die kun je dan weer in de regel eronder gebruiken om de status op te vragen.

Na het invoegen van de code in intent.yaml moet je de Home Assistant opnieuw opstarten en kun je dit deel van de app al testen.

We zijn halverwege!

De tweede optie: het uitlezen van de temperatuur of luchtvochtigheid.

Bij de tweede optie van de app gaat het om het uitlezen van de temperatuur of de luchtvochtigheid Er is steeds 1 sensor per ruimte, dus kun je “ruimte” gebruiken als identificatie voor de sensor. In mijn geval heb ik die variabele SensorRuimte genoemd.

Je ziet hierboven de synoniemen voor de ruimte (SensorRuimte). De synoniemen maken het zaakje wat flexibeler. Dus als ik niet “tuinhuis” maar “schuur” zeg, dan begrijpt Dialogflow dat dat hetzelfde is (ik heb wel een tuinhuis, geen aparte schuur). De tweede variabele is Sensortype, hiermee kun je aangeven of je de luchtvochtigheid of de temperatuur in de ruimte wilt weten. De waarden die opgeslagen worden zijn respectievelijk “temperature” of “humidity” (dat zijn de namen van de attributen in Home Assistant). Maar ook hier heb ik synoniemen opgenomen zodat ik in plaats van “temperature” ook de synoniemen “temperatuur”, “luchttemperatuur”, “warm”, “warmte” kan gebruiken. Mede daardoor (en dankzij de trainingszinnen) kan ik bv vragen “hoe warm is het onder de carport?” maar ook “wat is de temperatuur onder de carport?”.
De derde optie is er eentje die vast eenvoudiger kan, maar waar ik even geen andere oplossing voor wist. Als ik op de Home Assistant server een zin moet maken, dan moet ik weten waar de sensor hangt. En dan is het niet genoeg dat ik alleen “carport”, “serre”, “studeerkamer” weet.  Want dan krijg je zinnen als:

“De luchtvochtigheid in carport is 86%” of “De luchtvochtigheid in de tuinhuis is 90%”. Dat ziet niet netjes uit. Ik wil niet voor elke kamer een nieuw zin definiëren in het script. En dat doe ik ook niet

Je kunt hierboven de inhoud van intent.yamlvoor dit tweede deel.
Wat ik gedaan heb is dat ik Dialogflow geleerd heb dat het systeem ook moet kijken naar de tekst voor de SensorRuimte. Hoe? Door een variabele WaarVoorvoegsel te definiëren met een aantal mogelijke waarden: “op de”, “in de”, “onder de”, “in het”, “op het”, “onder het”. Ik heb geen sensor die achter iets hangt anders hadden “achter de” en “achter het” ook nog goede opties geweest.
Door bij de trainingszinnen van de Intent te markeren welke woorden voorbeelden zijn van die variabele leert Dialogflow heel goed welk deel gesproken wordt.
Dat betekent wel onzin in – onzin uit. De sensor hangt “onder de carport”. Vraag ik aan Home Mini naar de temperatuur “op de carport” dan krijg ik geen foutmelding (daar hangt immers geen sensor), maar dan krijg ik de waarde van de sensor die onder de carport hangt met de zin “De temperatuur op de carport is …”

Als iemand een slimmere oplossing weet, dan hou ik me aanbevolen.

Met deze drie Entities is dat deel klaar en kunnen we Intents gaan definiëren. De “Default Welcome Intent” ziet er als volgt uit:

 

 

Het “Default Fallback Intent” als volgt (je hoeft hier geen trainingszinnen in te vullen):

En het “actions_intent_CANCEL” ziet er zo uit:

 

Hierbij is belangrijk dat bij Events het event “actions_intent_CANCEL” gekoppeld wordt. Dit zorgt er voor dat de intent aangeroepen wordt als de gebruiker de app afsluit. Je krijgt nu netjes een tot ziens melding.

We naderen de eindstreep, nog 2 intents: “SensorWaarde” en “SensorWaardeFollowUp”. Eerst SensorWaarde:

 

Klik op de afbeeldingen voor een grotere versie. Ook hier hebben we trainingszinnen die uitleggen welk soort vragen deze intent moeten triggeren. Je ziet dat er gebruik gemaakt wordt van de variabelen Sensortype, SensorRuimte en WaarVoorvoegsel. Die heb ik hierboven uitgelegd.
Voor Sensortype en SensorRuimte geldt dat die verplicht zijn en daar heb ik ook een prompt voor gedefinieerd:

Denk aan het activeren van de Webhook. Daarvoor voer je bij “Action and parameters” de waarde “SensorWaarde” in. Dat is de naam van de functie in intent.yaml op de server. De code heb je hierboven al gezien als afbeelding. Als tekst ziet hij er zo uit:

SensorWaarde:
  speech:
    text: >
      {% set x = SensorRuimte %}
      {% if Sensortype == 'temperature' %}
        {% set y = 'sensor.%s_temperature' | format(x) %}
        De temperatuur {{ WaarVoorvoegsel }} {{ x }} is {{ states(y) }} graden Celcius.
      {% elif Sensortype == 'humidity' %}
        {% set y = 'sensor.%s_humidity' | format(x) %}
        De luchtvochtigheid {{ WaarVoorvoegsel }} {{ x }} is {{ states(y) }} %.
      {% else %}
        Sorry, er is in {{ x }} geen sensor die een waarde voor {{ Sensortype }} kan geven.
      {% endif %}

Je ziet hier een traditionele if – else if – else constructie. Als het sensortype temperatuur (‘temperature’) is, dan wordt het attribuut “temperatur” van de sensor teruggegeven. Als het om de luchtvochtigheid (‘humidity’) gaat krijg je de luchtvochtigheid in procenten. En anders krijg je een nette foutmelding.

Een ding wat je in de afbeeldingen had mogen opvallen is het stukje bij Contexts:

Hier wordt een output context gedefinieerd, nameljk “NextSensor”. Een context is een mogelijkheid om waarden van variabelen behouden te laten tussen contexten. En dat komt van pas als we gaan kijken naar de inhoud van de follow-intent “SensorWaardeFollowup”:

 

Hier zie je namelijk de context “NexSensor” als een input context. Wat je ook kunt zien is dat de zinnen een stuk korter zijn. Het zijn logische vervolgzinnen. als je al een vraag voor SensorWaarde gesteld hebt. Dus je zegt bijvoorbeeld eerst “Wat is de temperatuur onder de carport?” – krijgt een antwoord. En dan wil je meestal niet helemaal zeggen “Wat is de luchtvochtigheid onder de carport?”. Dat voelt onnatuurlijk. Het is veel logischer om te vragen “en de luchtvochtigheid?”. En dan verwacht je dat Dialogflow nog weet dat je dan de luchtvochtigheid van onder de carport wilt hebben. Of, als je tweede vraag zou zijn “en in de serre?” dat je dan de temperatuur in de serre wilt weten (omdat je het net ook over temperatuur had).

Het grappige is dat dat eenvoudiger te regelen is dan dat je eerst (of de eerste paar uur) denkt. Je kunt namelijk een standaardwaarde instellen voor een variabele. De optie daarvoor zit een beetje verstopt. Als je met je muis helemaal rechts op de regel van de variabele gaat zie je drie puntjes boven elkaar. Die heb je eerder al gebruikt om onnodige variabelen te verwijden. Nu kies je de andere optie “default value”.

Hierboven zie je dan wat je in moet vullen. Bij Sensortype wil je als waarde de inhoud van de variabele Sensortype zoals die in de context NextSensor is opgeslagen. Dat schrijf je dan als #NextSensor.Sensortype. Voor de Variabele WaarVoorvoegsel is dat #NextSensor.WaarVoorvoegsel en voor SensorRuimte is dat #NextSensor.SensorRuimte
Hiermee krijgen de drie variabelen binnen deze intent allemaal een standaardwaarde in het begin, namelijk de waarde die de variabele gekregen heeft als gevolg van de input in “SensorWaarde”. Zeg je alleen “en op de studeerkamer? ” nadat je eerst vroeg “Hoe warm is het onder de carport?”, dan wordt WaarVoorvoegsel gelijk aan “op de”, SensorRuimte wordt dan “studeerkamer” maar Sensortype is niet opnieuw gegeven. Dat is niet erg, want Sensortype had al een waarde, namelijk die van de vorige vraag (temperatuur).

Door de koppeling van deze twee intents krijg je een heel mooi en flexibel gesprek.

Tot zover deel 3

Als je bovenstaande entities, intents, fulfilments etc. allemaal braaf ingevuld hebt, dan zou je mijn tweet van zondag na moeten kunnen praten:

Mocht het allemaal toch nog een beetje tegenvallen, kom je fouten tegen die je niet kunt verklaren, dan kan ik je nog een beetje helpen.

Als je op het tandwieltje naast de naam van je app klikt, krijg je het instellingenscherm. Daarbinnen kun je dan kiezen voor “Export and Import“.

Ik heb dat gedaan met mijn app. Je kunt hem hier downloaden: HomeAssistant_voorbeeld 20181213

Let op! Ik heb de url van de Webhook verwijderd door een versie die aangeeft dat je hem moet wijzigen. Je kunt hem vinden in het bestand “agent.json”. Ik heb ook een paar namen van huisgenoten aangepast in “entities\Naam_entries_nl.json”.
Maar voor het overige zou hij gewoon moeten werken als je kiest voor het importeren ervan. Na het importeren kun je de betreffende waarden gewoon via Dialogflow wijzigen.

Tot zover voor nu even deze serie. Maar er komen vast nog meer berichten over de voortgang op de verschillende deelgebieden van het gebruik van Google Assistant!

Deel dit bericht:

  4 reacties aan “Praten met je Home Assistant installatie via Google Home – deel 3”

Reacties (2) Trackbacks (2)
  1. Praten met je Home Assistant installatie via Google Home – deel 3 https://t.co/PWLljwVuAy

  2. Voor wie het niet meegekregen had, het ging om deze dialoog.
    Deel 1: https://t.co/SetLJ153Ma
    Deel 2:… https://t.co/juEQj2CbPd

Sorry, het reactieformulier is momenteel gesloten.