DA MASER A MONFUMO | MOTO GUZZI V85TT | PURE SOUND POV 4K

🏍️ Il mio nuovo canale YouTube: giri in moto in POV, solo audio, tra le Dolomiti in 4K. Niente musica, niente parole — solo il motore e le Alpi. Vieni a fare un giro!

Iscriviti

Il Raspberry Pi usa uno strumento chiamato motion, che scatta foto (con la camera Pi) quando viene rilevato un movimento. Le foto vengono poi caricate su Cloud Storage, e una Cloud Function rimane in ascolto degli eventi che si attivano quando viene caricato un nuovo oggetto nel bucket. Quando viene caricata una nuova foto, la funzione cerca di etichettare l'immagine usando Vision API e archivia il risultato in Firestore. Firestore a sua volta genera un evento, che ascolto in una diversa Cloud Function, la quale usa IFTTT per notificarmi quando è stato rilevato un movimento e cosa ha trovato Vision API nell'immagine. Lo scopo di questo articolo è spiegare come fare il parsing degli eventi Firestore, che vengono consegnati alla funzione in un formato piuttosto confuso!

Struttura e Forma dello Struct Event

La Cloud Function che elabora i nuovi eventi (= nuovi caricamenti dal Raspberry Pi, quando viene rilevato un movimento), dopo aver etichettato la foto con Vision API, crea un nuovo oggetto Event e lo archivia nella collection Events in Firestore. Lo struct Event si presenta così:

go
type Event struct {
	URI     string    `json:"uri" firestore:"uri"`
	Created time.Time `json:"created" firestore:"created"`
	Labels  []Label   `json:"labels" firestore:"labels"`
}

Lo struct fa anche riferimento a un array di Label. Label è uno struct definito come:

go
type Label struct {
	Description string  `json:"description" firestore:"description"`
	Score       float32 `json:"score" firestore:"score"`
}

Questo è il risultato una volta che le informazioni sono state persistite in Firestore:

Syn - Firestore

Creare una Funzione per Ascoltare gli Eventi Firestore

Un'altra funzione, chiamata Notify, ascolta gli eventi da Firestore (e poi notifica l'utente tramite IFTTT), che vengono attivati quando vengono aggiunti nuovi dati al database. Ho usato Terraform per configurare la funzione:

resource "google_cloudfunctions_function" "notify" {
  project               = data.google_project.this.project_id
  region                = "europe-west1"
  name                  = "Notify"
  description           = "Notifies of newly labeled uploads"
  service_account_email = google_service_account.functions.email
  runtime               = "go113"
  ingress_settings      = "ALLOW_INTERNAL_ONLY"
  available_memory_mb   = 128
 
  entry_point = "Notify"
 
  source_repository {
    url = "https://source.developers.google.com/projects/${data.google_project.this.project_id}/repos/syn/moveable-aliases/master/paths/functions"
  }
 
  event_trigger {
    event_type = "providers/cloud.firestore/eventTypes/document.create"
    resource   = "Events/{ids}"
  }
 
  environment_variables = {
    "IFTTT_WEBHOOK_URL" : var.ifttt_webhook_url
  }
}

Il blocco event_trigger definisce l'evento che la funzione deve ascoltare. In questo caso, ascolto gli eventi providers/cloud.firestore/eventTypes/document.create nella collection Events.

Come Si Presenta un Evento Firestore "Grezzo"?

Usando fmt.Printf("%+v", event), possiamo vedere che l'oggetto evento Firestore si presenta così:

json
{OldValue:{CreateTime:0001-01-01 00:00:00 +0000 UTC Fields:{Created:{TimestampValue:0001-01-01 00:00:00 +0000 UTC} File:{MapValue:{Fields:{Bucket:{StringValue:} Name:{StringValue:}}}} Labels:{ArrayValue:{Values:[]}}} Name: UpdateTime:0001-01-01 00:00:00 +0000 UTC} Value:{CreateTime:2021-07-27 09:22:03.654255 +0000 UTC Fields:{Created:{TimestampValue:2021-07-27 09:22:01.4 +0000 UTC} File:{MapValue:{Fields:{Bucket:{StringValue:} Name:{StringValue:}}}} Labels:{ArrayValue:{Values:[{MapValue:{Fields:{Description:{StringValue:cat} Score:{DoubleValue:0.8764283061027527}}}} {MapValue:{Fields:{Description:{StringValue:carnivore} Score:{DoubleValue:0.8687784671783447}}}} {MapValue:{Fields:{Description:{StringValue:asphalt} Score:{DoubleValue:0.8434737920761108}}}} {MapValue:{Fields:{Description:{StringValue:felidae} Score:{DoubleValue:0.8221824765205383}}}} {MapValue:{Fields:{Description:{StringValue:road surface} Score:{DoubleValue:0.807261049747467}}}}]}}} Name:projects/cvln-syn/databases/(default)/documents/Events/tVhYbIZBQypHtHzDUabq UpdateTime:2021-07-27 09:22:03.654255 +0000 UTC} UpdateMask:{FieldPaths:[]}}

...il che è estremamente confuso! Mi aspettavo che l'evento assomigliasse esattamente ai dati che avevo precedentemente archiviato nel database, ma per qualche motivo questo è il formato degli eventi Firebase. Fortunatamente, il plugin IntelliJ IDEA "JSON to Go Struct" aiuta a dare un senso a quanto sopra:

go
type FirestoreUpload struct {
	Created struct {
		TimestampValue time.Time `json:"timestampValue"`
	} `json:"created"`
	File struct {
		MapValue struct {
			Fields struct {
				Bucket struct {
					StringValue string `json:"stringValue"`
				} `json:"bucket"`
				Name struct {
					StringValue string `json:"stringValue"`
				} `json:"name"`
			} `json:"fields"`
		} `json:"mapValue"`
	} `json:"file"`
	Labels struct {
		ArrayValue struct {
			Values []struct {
				MapValue struct {
					Fields struct {
						Description struct {
							StringValue string `json:"stringValue"`
						} `json:"description"`
						Score struct {
							DoubleValue float64 `json:"doubleValue"`
						} `json:"score"`
					} `json:"fields"`
				} `json:"mapValue"`
			} `json:"values"`
		} `json:"arrayValue"`
	} `json:"labels"`
}

Sebbene ancora confuso, almeno ora posso suddividere lo struct in modo da poter fare riferimento ai tipi correttamente altrove nell'applicazione, ad esempio per iterare sulle label.

Pulire la Struttura dell'Evento

FirestoreUpload può essere suddiviso per avere campi con nome invece di struct anonimi. Questo è utile per poter fare riferimento ai campi e ai tipi corretti altrove nell'applicazione, ad esempio quando si itera sulle label.

go
package events
 
import (
	"github.com/thoas/go-funk"
	"time"
)
 
//FirestoreEvent is the payload of a Firestore event
type FirestoreEvent struct {
	OldValue   FirestoreValue `json:"oldValue"`
	Value      FirestoreValue `json:"value"`
	UpdateMask struct {
		FieldPaths []string `json:"fieldPaths"`
	} `json:"updateMask"`
}
 
// FirestoreValue holds Firestore fields
type FirestoreValue struct {
	CreateTime time.Time       `json:"createTime"`
	Fields     FirestoreUpload `json:"fields"`
	Name       string          `json:"name"`
	UpdateTime time.Time       `json:"updateTime"`
}
 
// FirestoreUpload represents a Firebase event of a new record in the Upload collection
type FirestoreUpload struct {
	Created Created `json:"created"`
	File    File    `json:"file"`
	Labels  Labels  `json:"labels"`
}
 
type Created struct {
	TimestampValue time.Time `json:"timestampValue"`
}
 
type File struct {
	MapValue FileMapValue `json:"mapValue"`
}
 
type FileMapValue struct {
	Fields FileFields `json:"fields"`
}
 
type FileFields struct {
	Bucket StringValue `json:"bucket"`
	Name   StringValue `json:"name"`
}
 
type Labels struct {
	ArrayValue LabelArrayValue `json:"arrayValue"`
}
 
type LabelArrayValue struct {
	Values []LabelValues `json:"values"`
}
 
type LabelValues struct {
	MapValue LabelsMapValue `json:"mapValue"`
}
 
type LabelsMapValue struct {
	Fields LabelFields `json:"fields"`
}
 
type LabelFields struct {
	Description StringValue `json:"description"`
	Score       DoubleValue `json:"score"`
}
 
type StringValue struct {
	StringValue string `json:"stringValue"`
}
 
type DoubleValue struct {
	DoubleValue float64 `json:"doubleValue"`
}
 
// GetUploadLabels returns the labels of the image as an array of strings
func (e FirestoreEvent) GetUploadLabels() []string {
	return funk.Map(e.Value.Fields.Labels.ArrayValue.Values, func(l LabelValues) string {
		return l.MapValue.Fields.Description.StringValue
	}).([]string)
}

La funzione GetUploadLabels() è un esempio di come accedere all'oggetto evento FirestoreUpload. Qui sto anche usando il pacchetto go-funk, che aggiunge alcune funzionalità aggiuntive in stile funzionale a Go (ma le prestazioni non sono buone quanto un ciclo "nativo").

In questo articolo ho spiegato come leggere gli eventi Firestore dalle Cloud Function che li ascoltano. Gli esempi sono scritti in Golang, ma linguaggi diversi dovranno fare il parsing dei messaggi in modo simile. Sebbene non sia comodo, questo è il formato attuale degli eventi Firestore! Fortunatamente, una volta che sai come leggerli, il resto è semplice!