Mobile Alerts API
Von Julian Sauer
- 5 Minuten - 888 WörterEinleitung
Die Sensoren von Mobile Alerts stellen eine öffentliche Schnittstelle zur Verfügung, über die Sensordaten abgefragt werden können. Pro Sensor können die letzten Messwerte abgefragt werden. Die gesamte Dokumentation befindet sich hier.
In diesem Artikel schauen wir uns ein Skript in Go an, das Resty benutzt, um die Temperatur inklusive Messzeitpunkt für einen Sensor abzufragen.
Inhaltsverzeichnis
Der Aufruf
Es gibt zwar keine REST-typischen URIs, über die einzelne Sensoren anhand ihrer IDs abgefragt werden können.
Allerdings kann eine Liste an IDs im application/x-www-form-urlencoded
-Format mitgeschickt werden, um Messwerte und weitere Informationen über Sensoren zu erhalten.
Der einzige verfügbare Endpunkt unter /lastmeasurement
stellt uns die Werte der letzten Messung bereit, wenn wir einen POST Request an ihn schicken.
Rate Limits
Bei mehr als 3 Aufrufen pro Sensor pro Minute werden weitere Abfragen an den Sensor temporär blockiert.
Allerdings scheint das nur der Fall zu sein, wenn man einen einzelnen Sensor abfragt.
Das Verhalten bei der gleichzeitigen Abfrage mehrerer Sensoren habe ich nicht ganz durchblickt:
Es kommen zwar immer entweder Messwerte für alle Sensoren zurück oder der gesamte Aufruf wird mit dem Statuscode 429 - Too Many Requests
blockiert.
Allerdings ist es einerseits unabhängig davon, ob einer (oder alle) Sensoren vorher einzeln durch zu viele Aufrufe blockiert wurden.
Andererseits schlägt der Aufruf unregelmäßig fehl und funktioniert Sekunden später wieder…
Neben diesem Verhalten sind außerdem nur maximal 5 Aufrufe mit falschen Parametern innerhalb von 15 Minuten erlaubt. Andernfalls wird die IP Adresse temporär blockiert, auch wenn zwischendurch erfolgreiche Aufrufe stattgefunden haben.
Die Antwort
Fragen wir einen Temperatursensor ab, sehen wir unter t1
, dass es 21.3°C sind:
{
"devices": [
{
"deviceid": "XXXXXXXXXXXX",
"lastseen": 1679589238,
"lowbattery": false,
"measurement": {
"idx": 140476,
"ts": 1679589229,
"c": 1679589238,
"lb": false,
"t1": 21.3
}
}
],
"success": true
}
Abhängig vom Sensortyp unterscheiden sich die gemessenen Werte.
So liefert ein Temperatur- und Luftfeuchtigkeitssensor neben der Temperatur auch etwa die Luftfeuchtigkeit von 55.0% im Feld h
mit:
{
"devices": [
{
"deviceid": "XXXXXXXXXXXX",
"lastseen": 1679589278,
"lowbattery": false,
"measurement": {
"idx": 92507,
"ts": 1679589275,
"c": 1679589278,
"lb": false,
"t1": 19.3,
"h": 55.0
}
}
],
"success": true
}
Go Client
Structs für die Antwort
Um Abfragen verarbeiten zu können, legen wir einige Structs passend zum erwarteten JSON an.
Wie in der Einleitung bereits angekündigt, fokussieren wir uns auf die Temperatur t1
und deren Messzeitpunkt ts
.
Entsprechend enthält Measurement
nur diese beiden Felder.
package dto
type LastMeasurementResponse struct {
Devices []Device `json:"devices"`
Success bool `json:"success"`
}
type Device struct {
DeviceId string `json:"deviceid"`
Measurement Measurement `json:"measurement"`
}
type Measurement struct {
Timestamp int64 `json:"ts"`
Temperature float64 `json:"t1"`
}
Rest Client
Anschließend können wir eine Funktion schreiben, die die ID eines einzelnen Sensors übergeben bekommt und seine Temperaturmessung zurück gibt.
Wir verwenden Resty, um einen POST Request abzuschicken.
Die Antwort parsen wir in unsere vorgegebene Struktur von LastMeasurementResponse
.
Die einzige Überprüfung, die wir vornehmen, bezieht sich auf die Anzahl der zurückerhaltenen Geräte.
Denn solange wir genau eine ID angeben, erwarten wir auch genau eine Messung als Antwort.
Darüber hinaus kann es natürlich sinnvoll sein, z.B. den HTTP Statuscode und das Feld success
auszulesen und Fehlerfälle entsprechend zu behandeln.
package client
const url = "https://www.data199.com/api/pv1/device/lastmeasurement"
func GetLastMeasurementOf(deviceId string) (*dto.Measurement, error) {
parameters := "deviceids=" + deviceId
client := resty.New()
response, e := client.R().
SetHeader("Content-Type", "application/x-www-form-urlencoded").
SetBody(parameters).
Post(url)
if e != nil {
return nil, e
}
var parsedResponse dto.LastMeasurementResponse
if e = json.Unmarshal(response.Body(), &parsedResponse); e != nil {
return nil, e
}
if len(parsedResponse.Devices) != 1 {
return nil, errors.New(fmt.Sprintf("found %d devices, expected 1", len(parsedResponse.Devices)))
}
return &parsedResponse.Devices[0].Measurement, nil
}
Datum parsen
Der zurückgegebene Zeitstempel ist in Unix Zeit angegeben - also in vergangenen Sekunden seit 01.01.1970.
Das time
Package von Go bietet eine Funktion, um einen solchen Wert in time.Time
umzuwandeln.
Verwenden wir ihn als Typ in unserem Struct Measurement
, müssen wir zusätzlich eine Funktion UnmarshalJSON()
bereitstellen, die vom JSON Parser aufgerufen werden kann:
package dto
import (
"encoding/json"
"time"
)
type Measurement struct {
Timestamp time.Time
Temperature float64
}
type measurementInternal struct {
Timestamp int64 `json:"ts"`
Temperature float64 `json:"t1"`
}
func (measurement *Measurement) UnmarshalJSON(data []byte) error {
var measurementInternal measurementInternal
if err := json.Unmarshal(data, &measurementInternal); err != nil {
return err
}
measurement.Timestamp = time.Unix(measurementInternal.Timestamp, 0)
measurement.Temperature = measurementInternal.Temperature
return nil
}
Programm ausführen
Rufen wir unsere Funktion mit der ID eines Sensors auf, erhalten wir Temperatur und Zeitstempel in einem lesbaren Format.
func main() {
lastMeasurement, e := client.GetLastMeasurementOf("XXXXXXXXXXXX")
if e != nil {
log.Fatalln(e.Error())
}
log.Printf("%.1f°C", lastMeasurement.Temperature)
log.Println(lastMeasurement.Timestamp)
}
2023/03/23 17:39:01 21.3°C
2023/03/23 17:39:01 2023-03-23 17:33:49 +0100 CET
Fazit
Grundsätzlich bewerte ich die Bereitstellung einer öffentlichen API als positiv. Auch wenn sie nur wenige Funktionen bietet, sind diese doch für die meisten Zwecke ausreichend und gut dokumentiert.
Bedenklich finde ich dagegen die fehlende Sicherheit: Ich habe nicht die IDs meiner eigenen Sensoren als Beispiel angegeben. Denn ich möchte natürlich nicht, dass jemand pro Minute 4 Anfragen an meine Sensoren schickt, sodass ich sie nicht mehr benutzen kann. In dem Fall bliebe mir vermutlich nichts anderes übrig, als einen neuen Sensor zu kaufen und zu hoffen, dass niemand die ID von diesem errät.
Abhängig vom Einsatzgebiet des Sensors könnte es sogar gefährlich werden. Durch das Tracken der Raumtemperatur lässt sich beispielsweise ablesen, ob die Person zu Hause ist oder etwa verreist.