Alexa, apri la dispensa!

Gestione delle scorte di casa con comandi vocali.

Istruzioni per realizzare una skill Alexa.

Nel precedente post, avevamo visto come realizzare un sistema per una gestione più razionale e sostenibile delle scorte domestiche. 

In pratica, tramite un semplice barcode reader, un programma e un server per salvare l'elenco dei prodotti che si acquistano, è diventato un gioco da ragazzi tenere sotto controllo tutto quello che quotidianamente utilizziamo in casa e che vale la pena controllare per evitare aquisti inutili o dimenticarsi di qualcosa.

Qualcuno può pensare: va bene, interessante, ma come fare per renderlo ancora più semplice e family friendly? 

Serve per forza di cose impugnare un barcode reader e scansionare ogni prodotto che preleviamo quotidianamente dalla dispensa?

La risposta è ovviamente NO. E qui ci sono tutte le istruzioni per far sì che sia Alexa a impartire i comandi al nostro database!





Figura 1



La skill non l'ho pubblicata nel catalogo di Alexa, è ancora in modalità "privata" ma di seguito ci sono tutte le istruzioni per crearla e utilizzarla come ho fatto io.


Sommario


1) Architettura di alto livello

Come rappresentato in figura 1, l'architettura possiamo definirla ibrida, ovvero le componenti sono in cloud AWS ma si integrano con la soluzione di casa e di cui abbiamo già parlato.

Nella parte bassa del disegno si vede infatti la soluzione esistente:
  • Un database per la memorizzazione dei prodotti e delle relative quantità
  • Un barcode reader per la scansione dei barcode dei prodotti in ingresso, ovvero gli acquisti effettuati con la spesa (non più indispensabile in questo progetto)
  • Il programma scritto in Python che contiene la logica e le interfacce per la scrittura dei dati nel DB
La soluzione esistente copre entrambe le fasi, sia quella di inserimento dei prodotto acquistati nella dispensa di casa (normalmente effettuata una volta a settimana quando si fa la spesa), che quella di prelievo (ogni volta che si prende un prodotto dalla dispensa per consumarlo).

La nuova soluzione via Alexa è nata proprio per questa fase di prelievo. E' molto più veloce dire ad Alexa di effettuare l'attività nel day-by-day, piuttosto che prendere lo scanner, aprire il programma e togliere il prodotto dal magazzino.

La skill si occuperà di ascoltare il nome del prodotto prelevato, proporti eventuali nomi alternativi (se ad esempio il nome che hai pronunciato è simile ad altri) e aiutarti a confermare quello desiderato, associando automaticamente al nome il relativo barcode e inviarlo al nostro DB a casa.

Vedremo che farà anche altro, come permetterti di conoscere in tempo reale la quantità di un determinato prodotto o, come già detto, aggiornarla se hai appena effettuato un acquisto. 

Nei passi successivi, vediamo nel dettaglio ciascuno di questi aspetti.

2) Amazon Simple Queue Service 

Il principale problema che si deve affrontare in soluzioni di questo tipo è come mettere in comunicazione il mondo Alexa con la propria rete domestica.

Qualcuno può pensare sia facile visto che un dispositivo Echo Alexa è praticamente presente in ogni casa...

Ma non è così. Un dispositivo Echo è semplicemente un altoparlante che si occupa di ascoltare una richiesta a fronte dell'ascolto della parola di attivazione Alexa, per poi inviarla al cloud AWS dove sono presenti tutte le componenti "server" in grado di trasformare quella richiesta in una risposta compiuta.



Quindi tra noi e Alexa, c'è di mezzo in realtà internet e i dati finiscono nel cloud AWS che ospita le funzioni di Alexa. 

Ci sono diverse soluzioni a questo problema di integrazione. Ad esempio si potrebbe rendere visibile su internet la nostra applicazione di casa, ma questo comporterebbe delle problematiche di sicurezza da risolvere, visto che a quel punto non solo Alexa potrebbe raggiungere il nostro server di casa...

La strada che ho scelto è stata invece quella di usare Amazon SQS, un componente "server" realizzato tramite delle code visibili da entrambi i "client" di casa e di Alexa, permettendo quindi di disaccoppiare le relative reti.  

Amazon SQS è disponibile, sotto certi limiti, nel piano gratuito di AWS. Questi limiti sono ampiamente sufficienti per esigenze di questo tipo.


  




Ma come funziona? 

Alexa invierà un messaggio in una coda sempre attiva con l'identificativo del prodotto e la relativa quantità da aggiornare. Periodicamente un client installato su Raspberry PI (lo stesso dove è anche attivo il DB) controllerà se ci sono nuovi messaggi, li preleverà nell'ordine con cui sono stati scritti e li cancellerà dalla coda.

Esistono già numerosi documenti in rete in cui è spiegato come creare e gestire le code SQS. Qui mi limiterò ai parametri scelti per la configurazione.



Qui sono ricapitolate le informazioni per raggiungere la coda tramite la relativa URL:


Nel paragrafo relativo alla lambda function, vedremo anche come configurare lo strato di autenticazione e autorizzativo in modo che Alexa sia in grado di comunicare con questa coda.

3) Alexa skill overview

E' la prima volta che mi accingo a creare una skill Alexa. Devo dire che è un'ottima opportunità quella di poter rendere comandabile con la voce, un'applicazione progettata inizialmente con interfacce classiche e che quindi richiederebbero l'utilizzo di un PC o di un dispositivo fisico.

Per prima cosa occorre creare un account da sviluppatore Amazon e accedere ad Alexa Skill Kit, ovvero al framework con tutti gli strumenti necessari allo sviluppo della skill.




Tramite Skills Kit è possibile sia accedere alla componente Build per la gestione dei parametri in ingresso, e sia alla componente "backend" (Code) che invece li elabora e produce un output, che può essere il recupero di un'informazione, l'attivazione di un terminale domotico o, come in questo caso, l'aggiornamento di un database di un'applicazione situata da un'altra parte.





Quindi il primo pezzo da definire è l'interaction model, ovvero il raggruppamento logico che partendo dal nome dello skill, a cascata identifica le azioni o intents che questo può gestire, e per ciascun intent le varie utturences, ovvero le possibili frasi con cui può essere richiamato.



Come si può vedere, l'utturance al suo interno può contenere degli slot, ovvero le variabili che di volta in volta specificano a cosa applicare le azioni di Alexa. 

La componente backend è quella che contiene la logica e permette di fare qualcosa una volta che le informazioni sono recepite.

Questo processo avviene in più step, ovvero man mano che la conversazione procede, si affinano quelle che sono le informazioni essenziali per restituire un risultato.

Ci sono più modalità per gestire queste iterazioni, si può delegare Alexa affinchè faccia domande, finchè i valori (gli slot) non sono popolati, oppure effettuarla tramite il codice in modo da aumentarne le possibilità di gestione. 

4) Alexa catalog e slot management. 

Abbiamo visto che le utturance sono le frasi che Alexa riconoscerà per innescare un determinato intent e fare qualcosa. Come dicevamo, queste frasi normalmente hanno al loro interno delle variabili (gli slot), che di volta in volta saranno gestite dalla particolare esecuzione dello skill.

Uno slot a sua volta fa riferimento a un elenco di possibili valori, chiamati Slot Type, che di volta in volta Alexa cercherà di abbinare nel momento in cui l'utente lo cita.

Questi valori sono o predefiniti già da Amazon, per cui non sarà necessario ricreare ed hanno in tal caso un prefisso Amazon (es. Amazon.Number), oppure possono essere delle liste create all'occorrenza. 

Per esempio a {SlotProduct}, che identifica in partenza un prodotto generico, verrà passato un particolare prodotto che noi vogliamo sia tra quelli del nostro DB di casa appartenente allo Slot Type ITEMSNEW

Abbiamo sostanzialmente due possibilità per popolare l'elenco dei valori possibili per ciascuno slot, ovvero inserirli manualmente in un elenco, oppure, meglio, far accedere dinamicamente Alexa ad una vista aggiornata esterna proveniente dal nostro DB di casa.   

Grazie alla funzionalità Reference-Based Catalog Management, c'è la possibilità infatti di creare veri e propri data source che lo skill Alexa referenzierà per la gestione degli slot.

La comodità è che così facendo si potrà disaccoppiare lo skill dal catalogo esterno e con opportune API rilasciate si potrà gestirne l'aggiornamento e periodicamente far puntare la skill alla nuova versione del catalogo.



I passaggi necessari saranno:
  • Creazione del catalogo in formato JSON localmente sul server locale a casa
  • Creazione della definizione del catalogo a livello della skill Alexa
  • Creazione di una versione del catalogo
  • Aggiornamento della configurazione JSON della skill Alexa per recepire la nuova versione 

Creazione del catalogo in formato JSON 

La definizione degli slot in un catalogo ha questo formato di esempio: 
  {
"values":
[
{
"id": "8004263667118",
"name": {
"value": "Aceto di vino decolorato"
}
},
{
"id": "80752431",
"name": {
"value": "Aceto balsamico di Modena"
}
},       ...       {         ...       }      ] } 

}

Da notare che è possibile assegnare un id al nome dello slot. Associando il relativo barcode al valore del id, abbiamo quindi la possibilità di associare automaticamente il barcode al prodotto specificato via voce.

Ovviamente questa lista non vogliamo farla manualmente, ma la vogliamo creare automaticamente partendo dal database esistente.

Vediamo come si può fare usando python. 

In python l'elenco di cui sopra si traduce infatti nello scrivere un dizionario con un'unica coppia chiave valore del tipo:

catalog={"values": products} 

dove products rappresenta a sua volta una lista con l'elenco completo dei prodotti del DB. Ogni elemento di questa lista è a sua volta strutturato come un dizionario con unica coppia chiave/valore del tipo:

{"id":barcode, "name":{"value":descriptionValue}}

dove barcode e descriptionValue sono derivati da ciascun record del DB. 

Come?

Come visto nel post precedente, per accedere ad un DB PostgreSQL, si può usare il modulo psycopg2 e, come indicato di seguito, è possibile immagazzinare il contenuto del DB in una lista records del tipo:

records = [('80752431     ', 'Aceto balsamico di Modena (250 ml)'), ('8032754434581', 'aceto bianco di alcol Casaceto'),...]

Ogni elemento di questa lista corrisponde ad un record del DB. 
Es. per primo record (record 0):

records[0] = ('80752431     ', 'Aceto balsamico di Modena (250 ml)')

A sua volta ciascun singolo record, è una tupla composta da due elementi. 

Es.:

records[0][0] = '80752431     '
records[0][1] = 'Aceto balsamico di Modena (250 ml)'

Ecco così ottenuti, i valori di barcode descriptionValue corrispondenti al primo record:

barcode = (records[0][0]).strip()
descriptionValue = records[0][1]

(con il metodo strip() eliminiamo gli spazi presenti nel campo barcode).

Come detto precedentemente, products è una lista. E' pertanto possibile usare il metodo append() per aggiungere un nuovo elemento alla lista semplicemente iterando su tutti i record del DB.

Una volta ottenuto il dizionario python catalog, è necessario effettuare la conversione in formato JSON. Ci viene in soccorso il modulo JSON, la cui funzione dumps() converte l'oggetto python in una stringa JSON:

catalogJson=json.dumps(catalog)

Resta dunque da fare solo la memorizzazione di quest'ultimo oggetto in un file con la funzione print()

Di seguito il codice corrispondente:

pi@raspberrypi:~/dispensa/others $ more UpdateAlexaCatalog.py
import sys
sys.path.append(r'/home/pi/dispensa/others')
import createJSON
def NewCatalog():
    import subprocess
    # create a new catalog file
    print("create a new catalog file")
    createJSON.catalog()
    # send to AWS S3 bucket
    print("send the catalog to S3")
    subprocess.run("/home/pi/dispensa/script/SendToS3.sh")
def main():
    NewCatalog()
if __name__ == '__main__':
    main()

Il programma importa il modulo createJson che avrà al suo interno:

  • una classe Database() che si occupa di gestire la connessione e le esecuzioni delle query sul DB
  • una funzione catalog() che utilizzerà l'oggetto Database e utilizzerà i record estratti per formattare il file del catalogo 

Il fileJson.json precedentemente creato è un nome temporaneo. Viene poi spostato, con nome products.json, nella cartella definitiva dove il file precedente viene sostituito e archiviato:


pi@raspberrypi:~/dispensa/others $ more /home/pi/dispensa/script/SendToS3.sh
#!/bin/bash
cp -p /home/pi/dispensa/products.json /home/pi/dispensa/logs/products.json.$(date +"%Y-%m-%d")
cp -p /home/pi/dispensa/others/fileJson.json /home/pi/dispensa/products.json
aws s3api put-object --bucket  myslotscatalogs --key products.json --body /home/pi/dispensa/products.json



La creazione del nuovo catalogo è utile sia quando creiamo il catalogo la prima volta sia quando si fa una modifica al database e vogliamo che questa sia resa visibile nel catalogo, ovvero quando si crea un nuovo prodotto nel database ad esempio utilizzando l'interfaccia grafica.

Pertanto il programma che abbiamo visto nel post Dispensa 2.0, è stato modificato in modo che venga richiamata la nuova funzione UpdateAlexaCatalog.NewCatalog() all'occorrenza.

Creazione del catalogo

Dopo aver installato Alexa Skills Kit Command Line Interface (ASK CLI), è possibile gestire diverse operazioni sullo skill da un client, come per esempio dal Raspberry Pi dove è installato il database.

Per esempio è possibile richiamare le Skill Management API (SMAPI) che tra le varie possibilità, consente di operare sui cataloghi:

ask smapi create-interaction-model-catalog --catalog-name "Products" --catalog-description "list of products"

In questo modo abbiamo creato la definizione di un catalogo dal nome "Products" con descrizione "list of products".

Come risultato, l'API ritornerà il catalogId che utilizzeremo successivamente, che sarà del tipo:

{ "catalogId": "amzn1.ask.interactionModel.catalog.fdee74xx-xxxx-xxxx-xxxx-xxxxxxxx8d"

}

Con questa operazione abbiamo dunque solo creato il catalogo e ottenuto il relativo ID.

A questo punto si potrà creare la prima versione del catalogo, utilizzando il file JSON creato al punto precedente.

Ma prima che questo accada, dobbiamo fare in modo che il file sia visibile su internet.

Pertanto lo sposteremo in un bucket S3 precedentemente creato.

ask smapi create-interaction-model-catalog-version --catalog-id <YOUR-CATALOG-ID> --source-type URL  --source-url https://<YOUR-BUCKET-URL>/products.json

(<YOUR-BUCKET-URL> va sostituito con la URL di un vostro bucket s3)

A questo punto si può editare direttamente il file JSON dell'Interaction Model con la funzione JSON Editor:



inserendo l'ID del catalogo e relativa versione all'interno dello slot type ITEMSNEW, specificando che si tratta di un tipo di slot derivante da un catalogo:

"types": [
                {
                    "name": "ITEMSNEW",
                    "valueSupplier": {
                        "type": "CatalogValueSupplier",
                        "valueCatalog": {
                            "catalogId": "<YOUR-CATALOG-ID>",
                            "version": "1"
                        }
                    }
                }
            ]


E' possibile automatizzare la creazione di una nuova versione del catalogo e aggiornare l'interaction model di conseguenza.

Questo è utile quando inseriamo nuovi prodotti a casa tramite l'applicazione desktop e vogliamo che Alexa sia informata di conseguenza.

Basterà programmaticamente forzare un aggiornamento del catalogo e reinviare sul bucket S3 il nuovo file products.json

Vediamo quindi come definire i Job per 
  • CatalogAutoRefresh
In questo modo verrà generata una nuova versione del catalogo al giorno/orario stabilito. Ad esempio ogni giorno alle 00 Pacific Time corrispondenti alle 09 del mattino CET.

ask smapi create-job-definition-for-interaction-model --job-definition "file:./CatalogAutoRefresh.json"

{

   "jobId": "<JOB-ID-SAMPLE>"

}

pi@raspberrypi:~/dispensa $ more CatalogAutoRefresh.json

{

    "vendorId": "<YOUR-VENDOR-ID>",
    "jobDefinition": {
        "type": "CatalogAutoRefresh",
        "resource": {
            "type": "Catalog",
            "id": "<YOUR-CATALOG-ID>"
        },
        "trigger": {
            "type": "Scheduled",
            "hour": 0
        },
        "status": "ENABLED"
    }
}

 

       

  • ReferenceVersionUpdate

Con questo job, l'interaction model della skill recepirà la modifica. 

Crea quindi un legame tra l'aggiornamento del catalogo effettuato al passo precedente e la skill. Ovvero fa automaticamente quanto fatto manualmente poco fa.

pi@raspberrypi:~/dispensa $  ask smapi create-job-definition-for-interaction-model --job-definition "file:./ReferenceVersionUpdate.json" 

gi@raspberrypi:~/dispensa $ more ReferenceVersionUpdate.json

{
    "jobDefinition": {
        "type": "ReferenceVersionUpdate",
        "resource": {
            "type": "InteractionModel",
            "locales": ["it-IT"],
            "id": "<YOUR-SKILL-ID>"
        },
        "references": [{
            "type": "Catalog",
            "id": "<YOUR-CATALOG-ID>"
        }],
        "trigger": {
            "type": "ReferencedResourceJobsComplete"
        },
        "publishToLive": false,
        "status": "ENABLED"
    }

} 


Approfondimenti: 

https://developer.amazon.com/en-US/blogs/alexa/alexa-skills-kit/2020/09/automatic-updates-for-reference-based-catalogs 

5) Implementazione AWS Lambda function

Abbiamo visto come parametrizzare l'Alexa Interaction Model in modo che possano essere gestiti gli input in ingresso correttamente.

Ora è venuto il momento di elaborare questi dati per fornire un'operazione consistente, ovvero aggiornare o fornire una risposta in linea con la richiesta.

Per far questo dobbiamo entrare nel merito della logica della skill.

Amazon mette a disposizione una funzione lambda

Le funzioni lambda hanno il vantaggio di utilizzare l'infrastruttura sottostante solo quando necessario, per cui sono molto utili in cloud dove si vuole ottimizzare il tempo necessario alle varie elaborazioni e impostare delle logiche cosiddette pay-per-use. Anche in questo caso si riesce tranquillamente ad utilizzare il free tier di AWS Lambda senza preoccuparsi di altro. 

Di seguito vediamo come è strutturato il codice

Dopo la fase iniziale di import dei vari moduli necessari, viene inizializzato lo strato di persistenza che Alexa fornisce basato su Dynamodb.     

In questo modo, potremmo conservare i dati relativi agli utenti che useranno lo skill, così da creare delle statistiche legate all'utilizzo e fornire una classifica.

Questo permette anche di personalizzare le risposte che Alexa fornirà in funzione di chi sta utilizzando l'applicazione.

class LaunchRequestHandler

  • La prima classe LaunchRequestHandler è quella eseguita al lancio della skill
  • Questa si occuperà del messaggio di benvenuto, di inizializzare gli attributi di sessione che memorizzare i dati legati alla singola esecuzione della skill

class RemoveIntentHandler

  • Questa classe viene richiamata quando ad Alexa diciamo di aver prelevato un determinato prodotto dalla dispensa.
  • Verifica il nome e, nel caso ne esistano più di uno simile, chiede conferma all'utente.
  • Invia prodotto e relativa quantità da rimuovere al servizio SQS.
  • Avvisa l'utente del completamento dell'azione richiesta e eventualmente fornisce qualche indicazione sul relativo posizionamento in classifica o qualche messaggio personalizzato

class AddIntentHandler

  • Come la precedente, ma in questo caso serve per aggiungere un prodotto in dispensa

class StatusIntentHandler

  • La classe si differenzia dalle due precedenti, perchè in questo caso non vengono effettuate azioni sul database dei prodotti, ma viene fornita in real time la quantità disponibile di un determinato prodotto
  • Il dato viene letto dal bucket S3 che è sempre aggiornato rispetto a quanto presente nel DB di casa
  • Questa funzione è utile quando si vuole sapere sia la quantità di un determinato prodotto (es.: aceto bianco marca X, marca Y, ecc.) che di un'intera tipologia di prodotti (es.: aceto bianco)

funzioni generiche

Le classi di cui sopra sono legate ad intent specifici anche se le azioni sono molto simili tra di loro.

Ho cercato di evitare quindi il più possibile la ridondanza del codice e per questo motivo ho creato delle funzioni "cross" che sono richiamate da tutte le classi. 

Di seguito l'elenco con una breve spiegazione:

check_PersonID

  • Verifica che l'utente in questione sia tra quelli riconosciuti e validi per il salvataggio delle statistiche di utilizzo
  • Viene personalizzato il messaggio di benvenuto 
  • Viene incrementato il contatore di utilizzo dell'applicazione per l'utente

verify_input

  • Il codice verificherà che il nome del prodotto fornito dall'utente sia uno di quelli possibili.
  • Nel caso il nome non rientri tra quelli definiti, il codice chiamante "solleciterà" un nome corretto tramite la ElicitSlotDirective.

check_other_slot_values

  • verifica se ci sono altri valori che contengono il nome del prodotto
  • per esempio se viene chiesto aceto bianco, e ci sono due valori aceto bianco marca X e aceto bianco marca Y, verrà richiesto all'utente di confermare:  aceto bianco marca X e aceto bianco marca Y.
  • Nel caso di utilizzo dell'intent per il check dello stato attuale di un determinato prodotto (StatusIntentHandler) e viene fornito un valore comune a più prodotti (es.: aceto bianco) potrà essere confermato anche il valore iniziale e in tal caso il sistema calcolerà il totale dei singoli prodotti
  • Se invece viene richiesto direttamente un valore univoco (es. aceto bianco X o Y), non sarà richiesto di confermare nulla.

confirm_product

  • Nel caso a seguito dei passaggi precedenti sia stato scelto o confermato un nome di prodotto, il valore fornito viene immagazzinato in uno slot dedicato (ConfirmedProduct)
  • Questa funzione fa sì che il codice utilizzi proprio questo nome come nome definitivo del prodotto   

get_authenticated

  • Questa funzione è responsabile dell'autenticazione affinchè il codice possa fare uso delle risorse AWS create (SQS, S3)
  • Vengono acquisite credenziali temporanee tramite AWS STS (Security Token Service)

send_to_sqs

  • grazie a questa funzione, sia la classe di aggiunta che di rimozione prodotti, possono inviare i dati corrispondenti sulla coda (AlexaMovementsQueue.fifo)

check_quantity

  • abbiamo visto in precedenza che utilizzando il database dei prodotti su S3, è possibile calcolare la quantità attuale di un determinato prodotto oppure la somma di prodotti simili

ranking

  • questa funzione si occupa della costruzione del messaggio finale verso l'utente
  • viene letta la tabella delle statistiche di utilizzo e fornito un messaggio personalizzato in funzione del posto in classifica
  • i messaggi non sono sempre uguali, ma per renderli più interessanti, vengono scelti casualmente
  • inoltre questa lettura personalizzata non viene letta sempre, ma solo a intervalli di alcuni giorni, per evitare che diventi troppo ripetitiva
  • l'obiettivo è che l'utilizzatore sia incentivato ad usare la skill
Nella parte conclusiva del codice vengono richiamate tutte le utility necessarie alla costruzione dello skill. 

6) Aggiornamento DB dei prodotti gestiti con Alexa

Nel momento in cui la skill Alexa ha completato la sua esecuzione, e nella coda SQS ci saranno dei messaggi da elaborare, occorre occuparsi della relativa acquisizione e trasformazioni in aggiornamenti sul database di casa.

Ogni ora viene richiamato lo script che si occupa di scaricare nuovi dati dalla coda SQS:






Il programma importFromAlexa.py si occuperà di:

  • Utilizzare AWS SDK per Python (Boto3) per gestire i servizi AWS (in questo caso la coda SQS)
  • Immagazzinare il risultato della lettura della coda in una variabile response
  • Come tipo di variabile questa sarà un dizionario e quindi "navigabile". Esempio:
>>> response
{'Messages': [{'MessageId': '9b9b8a9c-9e11-47a1-90ae-94e699b01afa', 'ReceiptHandle': 'AQEBOA3KqNOiVRLm73bELk6wBdEClBjVfOoC/6ysNt9ntdyPfyaT1Nyv8jfKK8pHyMps2LU8uAfM3hh/Kp5kI2Ul4QybT4D1YFx6pW62YnF9JVvZMkm1SmfgV8wpCc+JbLl8kQ4mBPz+MHT4vhdqHnLW0ke02r1zAmdDDtqR84h63bejV1WRgtKZiv+RyM6y2jhJ7R5wPfAY36Yt54SHcHemm79zBuoTtf12yaWs1aJQVbnnAt0sk46wTSR8yN84/XczKPxVYnnjtNPFl+CnYAxRxtZYgF8T6X+xK+Nw4Lmm0vQ=', 'MD5OfBody': '2d5f31dc178b1a5f83de081786e6b1dd', 'Body': '{"barcode": "8002330012878", "quantity": -1, "Timestamp": "2022-05-21 14:38:51.929222"}'}], 'ResponseMetadata': {'RequestId': '89c7aa26-ae2e-502e-8f00-f124524b4031', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': '89c7aa26-ae2e-502e-8f00-f124524b4031', 'date': 'Sat, 21 May 2022 14:41:41 GMT', 'content-type': 'text/xml', 'content-length': '880'}, 'RetryAttempts': 0}}

Come si può vedere dall'esempio, è possibile individuare la chiave Body che nell'esempio come valore ha una stringa in formato JSON:

>>> response["Messages"][0]["Body"]
'{"barcode": "8002330012878", "quantity": -1, "Timestamp": "2022-05-21 14:38:51.929222"}'

Si può vedere che il tipo di variabile è proprio una stringa tramite il comando type:

>>> response_body=response["Messages"][0]["Body"]
>>> print(type(response_body))
<class 'str'>

Per rendere la stringa navigabile, occorre convertirla in dizionario. E' molto semplice fare questo, in quanto essendo un JSON, si può parsarla con l'istruzione JSON.loads:

>>> response_body_dict=json.loads(response_body)
>>> response_body_dict
{'barcode': '8002330012878', 'quantity': -1, 'Timestamp': '2022-05-21 14:38:51.929222'}
>>> print(type(response_body_dict))
<class 'dict'>
  
Avendo ottenuto un oggetto dizionario, abbiamo tutti i nostri valori chiave per aggiornare il database, ovvero il valore del barcode e la relativa quantità da modificare che passeremo alla classe DB del programma principale magazzino.

Così facendo, abbiamo praticamente riutilizzato lo stesso codice che avremmo usato da interfaccia grafica, solo che il dato da aggiornare non è più provenuto dal barcode reader ma da Alexa :-)

Ecco nel dettaglio come è fatto il programma importFromAlexa.py:

pi@raspberrypi:~ $ more /home/pi/dispensa/others/importFromAlexa.py
import boto3
import json
import UpdateAlexaDB
from magazzino import DB
import sys
sys.path.append(r'/home/pi/dispensa/others')
def DownloadMessages():
    queue_url = "https://sqs.eu-west-1.amazonaws.com/XXXXXXXXXXXX/AlexaMovementsQueue.fifo"
    client = boto3.client('sqs')
    response = client.receive_message(QueueUrl = queue_url, MaxNumberOfMessages=10)
    i=0
    message=[]
    try:
        for key, value in response.items():
            message=json.loads(response["Messages"][i]["Body"]) #message = message body
            print(message)

            #cancellazione messaggio
            receipt_handle = response['Messages'][i]['ReceiptHandle'] #message = response['Messages'][i]
            client.delete_message(QueueUrl=queue_url,ReceiptHandle=receipt_handle)

            barcode=message["barcode"]
            DeltaQuantity=message["quantity"]
            record=DB.command(DB(barcode,"","","","sel"))
            print(record)
            CurrentQuantity=record[2]
            NewQuantity=CurrentQuantity+DeltaQuantity
            DB.command(DB(barcode,record[0],record[1],NewQuantity,"upd"))
            i=i+1
    except:
        print("No messages to process")
    if i>0:
        UpdateAlexaDB.NewDB

def main():
    DownloadMessages()

if __name__ == '__main__':
    main()

Il programma UpdateAlexaDB serve per inviare nel bucket S3 l'immagine JSON aggiornata del Database. Come abbiamo visto questo serve per far funzionare l'intent corrispondente alla verifica dello stato di un particolare prodotto (StatusIntent). 

Ecco nel dettaglio il programma UpdateAlexaDB:

pi@raspberrypi:~/dispensa/others $ more UpdateAlexaDB.py
import sys
sys.path.append(r'/home/pi/dispensa/others')
import createJSON
def NewDB():
    import subprocess
    # create a new DB file
    print("create a new DB file")
    createJSON.DBrecords()
    # send to AWS S3 bucket
    print("send the DB to S3")
    subprocess.run("/home/pi/dispensa/script/SendDbToS3.sh")
def main():
    NewDB()
if __name__ == '__main__':
    main()


Viene utilizzato nuovamente il modulo createJSON, in modo da riutilizzare nuovamente le funzioni di accesso al DB, ma questa volta con la funzione DBrecords.

Il DBrecords.json precedentemente creato viene poi inviato a S3, archiviando il precedente file:

pi@raspberrypi:~/dispensa/others $ more /home/pi/dispensa/script/SendDbToS3.sh

#!/bin/bash

cp -p /home/pi/dispensa/DBrecords.json /home/pi/dispensa/logs/DBrecords.json.$(date +"%Y-%m-%d")

cp -p /home/pi/dispensa/others/DBrecords.json /home/pi/dispensa/DBrecords.json

aws s3api put-object --bucket  my-products-database --key DBrecords.json --body /home/pi/dispensa/DBrecords.json

Anche in questo caso il programma principale è stato adattato in modo da permettere l'invio ad Alexa delle informazioni aggiornate anche quando viene utilizzato il programma desktop.

7) Conclusioni

Abbiamo visto un esempio di applicazione di una skill Alexa in un contesto di sviluppo "amatoriale". 

Spero di essere stato abbastanza chiaro, in caso di dubbi, scrivete!



Commenti

Post più popolari