Weltweiter kostenloser Versand ab 120 € Bestellwert – Zahlung mit PayPal und Stripe – Hergestellt in Finnland

Ruuvi Firmware – Teil 9: Bluetooth Broadcasting

Einführungsbild der Ruuvi Firmware-Serie Teil 9

In diesem Teil des Tutorials fügen wir die Bluetooth-Broadcast-Funktionalität hinzu. Wir beginnen auch damit, Schnittstellen für Kommunikationsschnittstellen zu definieren, die so unabhängig wie möglich von der Datenübertragungsmethode, wie Bluetooth oder NFC, sind. Der finale Code dieses Blogbeitrags kann auf Ruuvi GitHub im ruuviblog-Branch, Tag 3.9.0-alpha, heruntergeladen werden.

Bitte folge Teil 1 der Serie für Details zum Klonen des Repositories und Kompilieren des Codes. Die finale Hex-Datei dieses Tutorials kann vom Ruuvi Jenkins heruntergeladen werden.

ruuvi.firmware.c Architektur 3.9.0

Kommunikationsschnittstelle

In Zukunft werden wir viele verschiedene Wege haben, mit der Außenwelt zu kommunizieren. Dazu könnten Bluetooth Low-Energy Generic ATTributes (BLE GATT), eine Art Mesh-Netzwerk, Universal Asynchronous Receiver-Transmitter (UART), Real-Time Transfer (RTT) und Near-Field Communication (NFC) gehören. Wir möchten, dass unsere Anwendung jedes Kommunikations-Backend so transparent wie möglich nutzt, daher definieren wir eine Kommunikations-Schnittstelle, so wie wir eine Sensor-Schnittstelle definiert haben.

Jede Schnittstelle, die wir implementieren, wird eine gemeinsame Funktionalität haben. Wir werden haben:

  • Initialisierung
  • Deinitialisierung
  • Daten senden
  • Daten lesen

Einige Datenübertragungsmethoden, wie die Kommunikation über Bluetooth, sind von Natur aus asynchron. Wir müssen warten, bis ein Slot für Funkaktivität verfügbar ist, und erst dann können wir übertragen. Daher bedeutet unser Senden nur, dass Daten im Treiber gepuffert wurden. Ebenso ist unser Empfangen ein Ereignis, das vom Treiber behandelt werden muss, und unser Programm wird die Daten aus dem Empfangspuffer lesen, wenn die Anwendung Zeit hat.

Manchmal möchten wir die Daten immer wieder wiederholen, zum Beispiel bei iBeacon-Übertragungen. In anderen Fällen, wie bei Sensordaten, kann es sinnvoll sein, sie nur einmal zu senden. Wir werden Platz für ein „Wiederholen“-Attribut in unserem Nachrichtenformat reservieren, es aber noch nicht implementieren.

ruuvi_interface_communication.h
ruuvi_interface_communication.h

Andere Parameter, wie Übertragungsintervalle oder Baudraten, können als implementierungsspezifisch betrachtet werden und werden nicht in die Kommunikations-Schnittstelle aufgenommen, sondern sind für jeden Kommunikations-Kanal einzigartig.

Die Chunk-Größe ist für jeden Kanal unterschiedlich. Bei einem BLE-Broadcast können wir 24 Bytes als herstellerspezifische Nutzlast senden, bei der BLE GATT-Übertragung können wir 20 Bytes senden, es sei denn, wir passen die Maximum Transmission Unit (MTU)-Einstellungen an, und bei BLE Mesh sind wir auf 3 Byte Opcode + 8 Byte Nutzlast pro einzelnem unsegmentierten Paket beschränkt. Die Sendefunktion wird die Nachricht ablehnen, wenn die Nachricht zu groß ist, um in die Puffer des Kanals zu passen. In Zukunft könnten wir die Segmentierung der Nachrichten entweder auf Treiber- oder auf Anwendungsebene implementieren.

Wir werden einige Standard-Nachrichtenformate definieren, damit jede Anwendung die Nachrichten verstehen kann, auch wenn sie über mehrere Chunks aufgeteilt sind. Dies führt uns zu den Endpunkten.

Ruuvi Endpunkte

Die Ruuvi-Endpunkte sind seit etwas über einem Jahr in Arbeit. Die Grundidee ist einfach: Wir definieren Datentypen und verwenden diese festen Typen über alle Anwendungen und Kommunikationsmethoden hinweg.

In diesem Teil des Tutorials benötigen wir nur die Broadcast-Typen. Wir verwenden das erste Byte als Header, und die restlichen Bytes werden durch den Header definiert. Wir werden vorerst nur herstellerspezifische Daten-Advertisements implementieren, was bedeutet, dass das Eddystone-URL-Format noch nicht implementiert werden kann. Wir werden die Header-Werte 00 bis 0F für die Broadcasts festlegen, um die Kompatibilität mit der bestehenden Ruuvi Firmware zu gewährleisten.

Während wir Spezifikationen schreiben, einigen wir uns auf ein allgemeines 11-Byte-Format für die Daten.

  • Ruuvi-Daten sind 11 Byte lang, sofern nicht anders angegeben
  • Ruuvi-Daten sind in 3 Bytes Header und 8 Bytes Nutzlast aufgeteilt
  • Der Header hat ein Ziel, eine Quelle und einen Typ bei 11 Byte Daten
  • Die Nutzlast wird durch den Typ bei 11 Byte Daten definiert
  • Endpunkte 00 .. 0F sind für Broadcasts reserviert, einschließlich der aktuellen Formate. Jedes Format ist „speziell“ und hat nur das erste Byte als Header.
  • Endpunkte 10 .. 7F sind für Standarddaten reserviert und müssen der obigen 11-Byte-Definition folgen.
  • Endpunkte C0 .. FF sind für kommunikations-kanalspezifische Übertragungen reserviert und müssen nicht interoperabel sein.
  • Endpunkte 80 .. BF sind anwendungsspezifisch und können jedes Format haben, solange das erste Byte im richtigen Bereich liegt.

Die Begründung für die Reservierung von C0FF liegt in BLE Mesh: Das unsegmentierte Paket hat einen 3-Byte-Opcode, der die BLE SIG Vendor ID in Byte 1 und 2 enthält, und die Werte 0b11xx xxxx von Byte 0 sind für den eigentlichen Opcode reserviert.

Für den Umfang dieses Tutorials müssen wir nur Endpunkt 3 implementieren – auch bekannt als RAWv1. Dies ist das herstellerspezifische Datenformat, das von RuuviTags beim Booten nach der Auslieferung aus der Fabrik gesendet wird.

ruuvi_endpoint_3.h
ruuvi_endpoint_3.h

Unsere Endpunkt-Datei hat nur eine Funktion, die einen Puffer zum Schreiben der Daten, die für das Format benötigten Daten und eine Konstante entgegennimmt, die von den Treibern als ungültige Daten markiert wird. Da Datenformat 3 keinen spezifischen Wert für „ungültig“ definiert hat, werden wir nur alle Werte auf Null setzen, die nicht von den Sensoren gelesen werden können.

Bluetooth

Bluetooth-Schnittstelle

Zusätzlich zu den oben beschriebenen Sende- und Lesefunktionen haben wir einige BLE-spezifische Überlegungen, wie Übertragungsintervall, Sendeleistung, ob die Advertisement scannbar oder verbindbar ist und so weiter. Vorerst unterstützen wir nur nicht-verbindbare, nicht-scannbare herstellerspezifische Datenübertragungen, d.h. Eddystone wird nicht unterstützt.

Um die Dinge noch komplexer zu machen, verwenden das Scannen von Advertisements über BLE oder die Verwendung von GATT dasselbe Funkperipheriegerät. Eines unserer Leitprinzipien war es, die Dinge so einfach wie möglich zu halten, daher erlauben wir nur einen Funkbetriebsmodus gleichzeitig. Vorerst müssen wir uns nicht um die Interoperabilität mit anderen Funknutzungen kümmern, wir müssen nur die Daten senden. Wir werden eine Reservierung für das BLE-Advertisement-Scanning vornehmen, damit wir die Lesefunktion in Zukunft implementieren können. Die oben genannte Kommunikations-Schnittstelle wird schließlich in unserem Bluetooth-Advertising-Treiber implementiert.

ruuvi_interface_communication_radio.h
ruuvi_interface_communication_radio.h
ruuvi_interface_communication_ble4_advertising.h
ruuvi_interface_communication_ble4_advertising.h

Bluetooth-Treiber

Unser Treiber verfügt über einige Funktionen, die für die gesamte Bluetooth-Kommunikation gemeinsam sein werden. Wir werden dies in ruuvi_platform_communication_radio aufteilen. Idealerweise muss die Anwendung nichts über die zugrunde liegenden Details wissen, wie z.B. welche Takte und Timer initialisiert werden.

ruuvi_platform_communication_radio.c
ruuvi_platform_communication_radio.c

Unser Nordic Software Development Kit 15 (SDK)-spezifischer Funkcode aktiviert das Softdevice für ein Modul und kann den Funk deaktivieren, falls der Betriebsmodus geändert werden sollte. Theoretisch könnte das Softdevice GATT-Verbindung und Advertising gleichzeitig unterstützen, aber wir erlauben nur einen Modus gleichzeitig.

Das Advertising selbst profitiert von einer etwas komplexen Interaktion mit dem SDK: Wir richten Advertisement-Datenstrukturen ein, lassen das SDK die Daten in einen Rohpuffer kodieren und übergeben den Puffer schließlich an das SDK. Später könnten wir einen ordnungsgemäßen Ringspeicher implementieren, der Daten nach dem First-In-First-Out-Prinzip sendet und alle Nachrichten mit der Einstellung „Wiederholen“ wieder in die Warteschlange legt. Vielleicht unterstützen wir sogar eine anpassbare Übertragungsrate, indem wir Nachrichten schneller senden, wenn der Sendepuffer gefüllt ist, und anhalten, wenn der Sendepuffer leer ist.

Vorerst sind wir mit einer minimalistischen Implementierung von init, uninit und send zufrieden. Read gibt vorerst RUUVI_DRIVER_ERROR_NOT_IMPLEMENTED zurück.

ruuvi_platform_communication_ble4_advertising.c – Initialisierung
ruuvi_platform_communication_ble4_advertising.c – Initialisierung

Integration in die Anwendung

Wie zuvor ist der Button unser einziger Input für die Anwendung. Wir ändern die Button-Aufgabe, um die Sensorabtastung auszulösen und die Daten über task_advertisement zu senden.

ruuvi_driver_status_t task_button_on_press(void)
{
  static uint64_t last_press = 0;
  // returns UINT64_MAX if RTC is not running.
  uint64_t now = ruuvi_platform_rtc_millis();
  ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;

  // Debounce button
  if((now - last_press) > RUUVI_BOARD_BUTTON_DEBOUNCE_PERIOD_MS)
  {

    err_code |= task_led_write(RUUVI_BOARD_LED_RED, TASK_LED_ON);
    /*err_code |= task_acceleration_on_button();
    err_code |= task_environmental_on_button();
    err_code |= task_adc_on_button();*/
    err_code |= task_advertisement_send_3();
    err_code |= task_led_write(RUUVI_BOARD_LED_RED, TASK_LED_OFF);
    RUUVI_DRIVER_ERROR_CHECK(err_code, ~RUUVI_DRIVER_ERROR_FATAL);
  }

  // store time of press for debouncing if possible
  if(RUUVI_DRIVER_UINT64_INVALID != now) { last_press = now; }
  return err_code;
}

Rohdaten anzeigen

Knopfdruck ruft task_advertisement_send_3() auf

task_advertisement.c
task_advertisement.c

Wir haben die „Wiederholen“-Funktionalität in unserem Nachrichtenformat noch nicht implementiert, aber das Softdevice sendet die zuletzt gegebenen Daten fröhlich weiter, bis etwas anderes befohlen wird. Die Aktualisierung der Daten erfolgt jedoch nur bei Knopfdruck.

In der Ruuvi Station App dargestellte Daten
Unser Tag sendet im gültigen Ruuvi-Datenformat, das von der Ruuvi Station geparst wird

Leistungsprofil

Wie immer ist der Stromverbrauch der treibende Faktor in unserem Code. Da wir uns den Funktionen der offiziellen Ruuvi Firmware, die auf SDK12 basiert, nähern, können wir unseren Fortschritt mit der offiziellen Ruuvi Firmware vergleichen, die bei Raumtemperatur etwa 24 μA bei 3 V verbraucht. Wir sind noch nicht auf Funktionsgleichheit, da die NFC offline ist und Sensoren nur bei Knopfdruck abgetastet werden. Mal sehen, wie wir uns jetzt schlagen.

Verbrauch während des Broadcastings
Verbrauch während des Broadcastings

Eine Sache, die uns ins Auge fällt, ist der Spitzenstromverbrauch. Unsere BLE-Broadcasts verbrauchen 20 mA. Das liegt daran, dass wir den internen Direct Current to Direct Current Converter (DC/DC) nicht aktiviert haben. Wir werden im nächsten Teil der Blogbeiträge eine Schnittstelle für die Stromversorgung von Peripheriegeräten hinzufügen, um den Stromverbrauch zu senken. Insgesamt liegen wir jetzt bei etwa 27 μA, was ziemlich nahe an den 24 μA liegt, die wir in der finalen Firmware erwarten könnten. Überprüfen wir den Basiswert:

Verbrauch zwischen den Broadcasts
Verbrauch zwischen den Broadcasts

Unser Leerlaufverbrauch beträgt jetzt 6,6 μA, was 0,2 μA mehr ist als die 6,4 μA in unserem vorherigen Beitrag. Vielleicht hat das Softdevice den Grundverbrauch irgendwie leicht erhöht, oder wir sehen jetzt nur eine Varianz in der Messung.

Fazit

In diesem Teil haben wir Bluetooth-Broadcasts und die Unterstützung für das Ruuvi-Datenformat 3 hinzugefügt. Wir haben auch ein Datenformat für die Fernkonfiguration und das Auslesen der Sensoren über jede Kommunikationsmethode, einschließlich Bluetooth Mesh, skizziert. Es gibt noch viel Feintuning und Erweiterungen an der Firmware vorzunehmen, aber wir nähern uns auch der Funktionalität der ursprünglichen Ruuvi Firmware.

Bleib dran und folge @ojousima und @ruuvicom auf Twitter für #FirmwareFriday-Beiträge!