Request for comment: Protocol Buffer Format für OBS-Daten

Hallo Freunde, ich habe in den letzten Wochen daran gearbeitet, ein Binärformat für künftige Entwicklungen, insbesondere in Bezug auf den OpenBikeSensor Lite zu entwerfen. Das hatten wir schon lange mal überlegt aber nie angegangen, nur wird es jetzt langsam nötig, da das Zeilenbasierte CSV eben nicht ausreicht für die Art, wie der OBS-Lite die Daten verarbeiten wird (oder eben gerade nicht verarbeiten kann).

Ich möchte hier nun gern eure Meinung zu meinem Entwurf hören. Ihr könnt ihn direkt auf Github finden: GitHub - openbikesensor/proto

Das ganze basiert auf Protocol Buffers, einer Technik die es einfach macht binäre Formate zu entwickeln, die dann in verschiedenen Programmiersprachen genutzt werden können, und die vor allem auch in Zukunft veränderbar/erweiterbar sind. Gutes Beispiel ist OpenStreetMap, dort unterstützen die meisten Tools das neue, wesentlich kompaktere und schnellere Format .osm.pbf.

Schaut euch meinen Entwurf gern mal an. In der README sind ziemlich klar meine Designideen und -ansprüche beschrieben. Die Formatspezifikationsdatei (openbikesensor.proto) enthält dann die technischen Details. Ich finde, die lässt sich auch ohne großartige Vorkenntnisse der Sprache ganz gut lesen.

Mit dabei ist auch ein kleines Python-Skript, das von USB lesen und die Abstandswerte ausgeben kann. Das ist relativ einfach zu machen und das gleiche (mit Umweg über eine aufgezeichnete Datei) muss die Auswertungslogik, also das Portal, in Zukunft dann auch unterstützen. Außerdem plane ich ein Konvertierungstool, das aus einer Binärdatei wieder ein CSV machen kann, damit man sich seine Dateien in Excel anschauen und drin rumfummeln kann, wenn man das denn möchte (was im Prinzip leider immer ein Informationsverlust ist, weil die Daten interpretiert und dann in ein zeilenbasiertes Format „reinquetscht“ werden – aber das sollte trotzdem noch gut funktionieren).

Für den OpenBikeSensor Lite wird es dann eine (erstmal eigene, später gern gemeinsame) Firmware geben, die eben Ereignisse in diesem Format generiert für alles, was der Sensor misst, und dann über USB ausgibt. Eine erste Version dieser Firmware habe ich auch veröffentlicht auf meinem privaten Github Account (hier), falls das wen interessiert wie das dann aussieht in C/C++.

Ich freue mich auf euer Feedback. LG Paul

Hallo Paul,
den Einsatz von Protocol Buffers halte ich für einen guten Plan. So ein Tool hätte ich vor 25 Jahren schon gut gebrauchen können ;-). Bei der Definition der Messages in openbikesensor.proto dachte ich an einigen Stellen dass da auch kleinere Datentypen ausreichen würden um etwas Platz zu sparen, aber mit Ausnahme von „bool“ gibt es solche bei Protocol Buffers gar nicht. Den Wert time_of_flight halte ich nur bei Schall für sinnvoll. Daher könnte dort der Datentyp int32 mit Einheit Mikrosekunden ausreichen. Schöne Grüße von Michael

1 „Gefällt mir“

Danke für dein Feedback @mihue.

Warum meinst du, dass das bei Licht nicht sinnvoll ist? Also dieser Laser Abstandsmesser machen ja immer noch time-of-flight, oder?

Jetzt wo ich aber nochmal nachgerechnet habe sind Nanosekunden dann natürlich Quatsch, das wäre eine Auflösung von ~0.3m. Mit einer Picosekunde sind es dann 0.3mm, das hatte ich eigentlich vor. Habe es wohl falsch aufgeschrieben :slight_smile:

In 32 Bit kann ich in Picosekunden kann höchstens die Laufzeit von Schall auf eine Distanz 1.28m kodieren. Also brauchen wir dort die 64 bit – oder wir nehmen Licht raus und ändern die Einheit auf μs.

Der Unterschied sind etwa 1MB pro Stunde Fahrt (4 bytes) × (30 hertz) × (1 hour) × 2 = 864 kB. Finde ich vernachlässigbar, um ehrlich zu sein. Was meinst du?

Kompromiss wäre 32bit, aber μs für Schall und ps für Licht. Muss man halt wissen was der Sensor für ein Typ ist. Das wäre über Metadaten abbildbar.

LG Paul

Die Schallgeschwindigkeit ändert merklich mit der Temperatur. Die Lichtgeschwindigkeit in Luft ist quasi konstant. Daher bringt die Laufzeit bei Licht keinen Informationsvorteil gegenüber dem Abstandswert in Metern. Eine Alternative wäre die Laufzeit in s als float32 zu speichern. Das passt dann für beide Varianten.
Schöne Grüße, Michael

1 „Gefällt mir“

:see_no_evil: Du hast vollkommen Recht.

Super Idee, die hatte ich auch schon länger auf meiner TODO-Liste. Ich würde noch einen Schritt weiter gehen und direkt gRPC als Transport-Protokoll verwenden.

Deine Protos schaue ich mir mal in Ruhe an.

Paul hat mit mir letzte Woche ein paar Details des Protokolls durchgesprochen. Ich bin weitere ein Freund eines lesbaren und damit nachvollziehbaren Protokolls. Die Anforderungen der geplanten Lite Variante liegen jedoch auch etwas anders.

Ich finde die Idee gut - Und ich denke wir können die Transparenz befördern indem wir einen einfach zugänglichen Konverter z.b. nach JSON drauflegen. Im OBS-Lite-Szenario sehen die Nutzer mangels SD-Karte zwar keine herumliegenden Daten auf dem OpenBikeSensor - der „human readable export“ könnte aber z.B. vom Portal aus als Download passieren.

Wenn ich mit make das Modul obs/proto/openbikesensor_pb2.py erstellen lasse, dann hat die erzeugte Datei einen Import import any_pb2 as any__pb2, der zur Laufzeit mit einem ModuleNotFoundError fehl schlägt.

Erstelle ich das fehlende Module obs/any_pb2.py, dann funktioniert der Import. Dafür schlägt DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(...) in diesem Modul fehl mit TypeError: Couldn't build proto file into descriptor pool: duplicate symbol 'google.protobuf.Any'.

Ändere ich in openbikesensor.proto den Import für any.proto so ab

// import "any.proto";                                                                             
import "google/protobuf/any.proto";

dann enthält Modul obs/proto/openbikesensor_pb2.py den Import from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 der funktioniert.

Welchen Einfluss haben die Dateien *.options? Unabhängig davon, ob sie existieren oder nicht, der generierte C++/Python Code ist identisch. Ober muss der Protobuf-Compiler zur Verwendung anders aufgerufen werden?

Also die .options Datei ist für nanopb, den compiler für embedded Geräte. Die Optionen darin vereinfachen Teilweise das generierte Interface, z.B. indem Arrays potenziell limitiert sind und keine dynamische Liste oder so.

Die Sache mit Any ist schwierig. Ich würde sie vielleicth erstmal rausnehmen. google/protobuf/any.proto funktioniert für Python aber nicht für NanoPB, any.proto andersherum. Eigentlich brauchen wir das erstmal nicht, können wir immer noch lösen wenn wir das wirklich haben wollen. Ich nehm’s wohl mal raus.

@gluap: Nen convertert nach CSV/XLSX/JSON hatte ich angedacht. Gerade die Tabellenformate haben halt einen Informationsverlust, aber das könnte ja trotzdem ok sein. Das Portal kann das gern machen, ein Python Script um das gleiche Offline zu tun ist ebenfalls denkbar/geplant.

@boldt: Wo dachtest du an gRPC? Also für den OBS-Lite macht das ja wenig Sinn, der streamt ja einfach Daten raus, das ist unidirektional. Und der Upload ins Portal ist ein dummer HTTP File Upload, da brauchts IMO keine krassere API als Multipart HTTP POST.

Danke. Das erklärt warum es mit dem Google protoc nicht funktioniert.

Würde nanopb dann nicht Byte-Streams erstellen, die mit dem „normalen“ Protbuf nicht mehr byte-kompatibel sind?

Thema: JSON

In der C#-Welt bekommt man auf einem gRPC-Objekt mit toString() direkt das Objekt als JSON. Da sollte es ohne viel Aufwand auch etwas in der Python-Welt geben.

Thema gRPC:

Ja, ich dachte hierbei an den Transport vom Sensor/Smartphone ins Backend. Das Binar-Format reduziert die Datenmenge deutlich, sodass hierdurch ein schnellerer Upload und weniger Datenvolumen anfällt, wenn über ein Hotspot übertragen wird. Btw.: Das hat mit Multipart-Formdata nichts zu tun.

Thema google/protobuf:

Man muss diversen proto-kompilieren beibringen, wo diese Standard-Dateien herkommen. Bei protoc muss man diese manuell herunterladen und an eine bestimmte Stelle anlegen.

Das ist dann aber ja schon in diesem protobuf Format. Der File Upload ist dann ja binär gepackt. Kleiner wird’s nicht mehr wenn du das ganze dann als BLOB in gRPC verpackst.

I know, aber platformio liefert NanoPB mit, nur eben scheinbar kein google/…, und dann hab ich any.proto ins repo getan um nicht herausfinden zu müssen wo nanopb das herbekommt oder wie man das automatisch bei pio run mit installieren lässt. Ich droppe einfach mal den any kram wieder, das können wir ja wie gesagt immer noch einbauen wenn wir es brauchen.

Edit: Zack, schon weg.

1 „Gefällt mir“