Această postare este originară din www.jaredwolff.com

Unul dintre cele mai confuze lucruri despre Bluetooth Low Energy este modul în care datele sunt mutate. În funcție de aplicație, starea dispozitivului dvs. poate fi destul de complexă. Asta înseamnă că a avea un punct final individual pentru fiecare informație este sinucidere prin Bluetooth.

Deci, ce soluție are?

Tampoane de protocol.

Un buffer de protocol este un mod programatic de a codifica / decoda date structurate optimizate. Ele pot fi partajate și manipulate pe aproape orice platformă. Nordic folosește de fapt o variantă a acestuia pentru serviciul DFU.

Au existat multe cuvinte buzz în primele câteva propoziții. Sperăm că până la sfârșitul acestui post veți înțelege exact despre ce vorbesc.

În acest tutorial, voi include un exemplu de cod complet eliminat pe care îl puteți clona și începe să îl utilizați imediat. Tot ce aveți nevoie este unul dintre acestea:

Kitul de dezvoltare NRF52

Deci, cum folosiți acest software magic?

Citește mai departe!

PS acest post este lung. Dacă doriți să descărcați ceva, faceți clic aici pentru un PDF frumos formatat. (Bonus adăugat, PDF-ul are toate cele trei părți ale acestei serii!)

Instalare

Prima parte a procesului este să vă asigurați că ați instalat toate utilitățile corecte. În funcție de ce limbaj de programare va determina ce instalați și utilizați. În acest caz, voi descrie utilitățile pe care le-am folosit pentru mai multe proiecte în trecut folosind C, Go și Javascript.

protoc este cel mai important utilitar pe care va trebui să îl instalați aici. Este „compilatorul” Protobuf care vă ia .proto și .options și le transformă în cod static.

  1. Pentru Mac, descărcați versiunea corespunzătoare aici.
  2. Dezarhivați folderul
  3. Alerga ./autogen.sh && ./configure && make în dosar
  4. Dacă apare o eroare autoreconf: failed to run aclocal: No such file or directory instalare autoconf folosind Homebrew:

brew install autoconf && brew install automake

Apoi, reluați pasul 3.

  1. Apoi rulați:
make check
sudo make install
which protoc

Considera protoc compilatorul pentru Protocol Buffers. Poate ieși direct fișiere brute sau biblioteci. Asta pentru că este încorporat suportul Go.

Datele brute pot fi folosite și pentru a genera biblioteci statice pentru alte limbi. Acest lucru necesită de obicei un utilitar suplimentar (sau utilități). Îi descriu pe cele două pe care le-a folosit proiectul Dondi Lib mai jos.

  1. nanopb este un script python folosit pentru a crea biblioteci C care codifică / decodează datele structurate.
    Poate fi instalat navigând la nanopb git repo și descărcarea fișierelor corespunzătoare. Cele mai importante piese care trebuie incluse:

  2. pb_encode.c, pb_decode.c și pb_common.c

  3. /generator/nanopb_generator.py

  4. Si /generator/nanopb/director co-localizat cu nanopb_generator.py

    nanopb este destinat implementării pe platforme încorporate. Este diferit de protoc-c (varianta C obișnuită), deoarece este optimizată pentru sistemele constrânse de resurse, cum ar fi procesoarele încorporate. Tampoanele au dimensiuni finite. Nu există alocare de memorie! În funcție de comunicarea bidirecțională, puteți importa și utiliza funcționalitatea de codificare sau decodare.

  5. pbjs folosește ieșirea din protoc pentru a genera o bibliotecă statică javascript. Acest lucru este puternic, deoarece îl puteți utiliza în orice aplicație javascript. Cel mai bun mod de instalare pbjs se execută:

    npm install -g protobufjs
    

Am simplificat puțin acest pas în exemplul de cod. Începeți prin clonarea repos-urilor aici.

Configurarea bufferului de protocol

Creați un fișier numit command.proto. Puteți face conținutul acelui fișier ce este mai jos:

syntax = "proto3";

message event {
  enum event_type {
    command = 0;
    response = 1;
  }
  event_type type = 1;
  string message = 2;
}

S-ar putea să pară străin la început, dar odată ce aruncați o privire mai profundă, nu este atât de diferit decât o structură standard C sau o masă hash.

Folosesc două tipuri de date în acest exemplu: a string și enum ca tip. De fapt, mai sunt câteva pe care le puteți citi la documentație. Când este compilat, structura c echivalentă arată ca:

/* Struct definitions */
typedef struct _event {
    event_event_type type;
    char message[64];
/* @@protoc_insertion_point(struct:event) */
} event;

Unde event_event_type este

/* Enum definitions */
typedef enum _event_event_type {
    event_event_type_command = 0,
    event_event_type_response = 1
} event_event_type;

Puteți să cuibăriți câte mesaje în interiorul celuilalt pe cât conțin inimile. De obicei, un mesaj este cât mai mic posibil, astfel încât transmiterea datelor este cât mai eficientă posibil. Acest lucru este deosebit de important pentru sistemele constrânse de resurse sau implementările LTE pentru care sunteți taxat fiecare megabyte folosit. Notă: atunci când elementele nu sunt utilizate sau definite, acestea sunt de obicei nu inclus în sarcina utilă a bufferului protocol codat.

În mod normal, atunci când creați un mesaj generic ca acesta, nu există nicio limită la dimensiunea șirului message. Această opțiune poate fi setată în .options fişier:

event.message	max_size:64

În acest fel, memoria poate fi alocată static în codul microprocesorului meu în timpul compilării. Dacă dimensiunea mesajului este mai mare de 64 de octeți, atunci acesta va fi tăiat în cod (sau pur și simplu veți primi o eroare în timpul decodării). Depinde de dvs., inginer software, să aflați cantitatea maximă absolută de octeți (sau caractere) de care aveți nevoie pentru acest tip de date.

Puteți privi mai multe nanopb caracteristici conexe la documentația lor.

Generarea bibliotecilor statice corespunzătoare

Pentru a face acest lucru cât mai ușor posibil, am pus toate următoarele coduri într-un Makefile. Când modificați protocolul tampon, se generează fiecare bibliotecă pentru fiecare limbă utilizată.

Dacă vrem să generăm un fișier Go static, comanda arată astfel:

protoc -I<directory with .proto> --go_out=<output directory> command.proto

Dacă ați instalat pluginul nanopb, puteți face ceva similar pentru a genera codul C:

protoc -I<directory with .proto> -ocommand.pb command.proto
<path>/<to>/protogen/nanopb_generator.py -I<directory with .proto> command

Primul fișier creează un fișier generic „obiect”. Al doilea creează de fapt biblioteca C statică.

Pentru javascript:

pbjs -t static-module -p<directory with .proto> command.proto > command.pb.js

Puteți testa fiecare dintre aceste comenzi cu .proto și .options exemple de fișiere de mai sus. De asemenea, am încorporat acest proces manual într-o singură comandă din depozitul de exemple. Obțineți acces aici.

Codificare și decodificare

Codificare

În exemplele de mai jos, vă arăt cum să utilizați codul static proaspăt compilat! Aici începe distracția.

Codificare folosind Javascript

Iată un flux tipic pe care îl puteți urmări atunci când utilizați o bibliotecă JavaScript generată static. Mai întâi, inițializați biblioteca.

// Import the config message
var protobuf  = require('./command.pb.js');

Apoi creați o instanță de event:

// setup command
var event = protobuf.event.create();
event.type = protobuf.event.event_type.command;
event.message = "This is";

Apoi, compilați sarcina utilă. adică transformă JSON lizibil de către om în binare frumos ambalate. Vezi mai jos.

// make sure it's valid
var err = protobuf.event.verify(event);
if( err != null ) {
   console.log("verify failed: " + err);
   return;
}

Veți primi erori în acest pas dacă obiectul dvs. este malformat sau dacă lipsește required elemente. Nu recomand utilizarea required prefix la definirea .proto fişier. Orice verificare a elementelor necesare poate fi efectuată cu ușurință în codul aplicației.

În cele din urmă, ultimul pas este codificarea și transformarea acestuia în octeți bruti:

// encode into raw bytes
var payload = protobuf.event.encode(event).finish();

Apoi puteți utiliza încărcătura utilă și o puteți trimite prin BLE, HTTP sau orice altceva. Dacă există un protocol de comunicare, puteți trimite acest buffer peste el!

Decodare în C

Odată ce datele sunt primite, acestea sunt decodate pe capătul încorporat. nanopb este confuz. Dar, din fericire, am aici un cod care vă va funcționa:

// Setitng up protocol buffer data
event evt;

// Read in buffer
pb_istream_t istream = pb_istream_from_buffer((pb_byte_t *)data, data_len);

if (!pb_decode(&istream, event_fields, &evt)) {
   NRF_LOG_ERROR("Unable to decode: %s", PB_GET_ERROR(&istream));
   return;
}

// Validate code & type
if( evt.type != event_event_type_command ) {
   return;
}

În primul rând, creați un flux de intrare bazat pe datele brute și dimensiunea datelor.

Apoi, utilizați pb_decode funcţie. Indicați primul argument către fluxul de intrare. Al doilea la definiția protocolului tampon cu care am lucrat. Este situat în command.pb.h fişier.

/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define event_fields &event_msg

Ultimul argument este un indicator către structura în care să introducă datele decodate. (În acest caz este evt definit chiar înainte pb_istream_from_buffer de mai sus).

Codificare în C

Să spunem acum că vom răspunde la mesajul care tocmai a fost decodat mai sus. Deci, acum trebuie să creăm date, să le codăm și să le trimitem înapoi. Iată procesul:

// Encode value
pb_byte_t output[event_size];

// Output buffer
pb_ostream_t ostream = pb_ostream_from_buffer(output,sizeof(output));

if (!pb_encode(&ostream, event_fields, &evt)) {
   NRF_LOG_ERROR("Unable to encode: %s", PB_GET_ERROR(&ostream));
   return;
}

Mai întâi creați un buffer care conține cantitatea maximă de octeți pe care buffer-ul dvs. Protocol îl ocupă. Acest lucru este definit și în command.pb.h. În acest caz event_size este setat sa 67. Apoi, în mod similar cu comanda de decodare, creați un flux și îl conectați la memoria tampon. Apoi, în cele din urmă, codificați datele arătând cu evt struct împreună cu fluxul și event_fields.

Atata timp cat pb_encode revine fără erori, datele codificate au fost scrise în output! Structura poate avea o lungime variabilă, astfel încât cel mai bun mod de a gestiona atunci când o trimiteți este să obțineți bytes_written din ostream:

NRF_LOG_INFO("bytes written %d",ostream.bytes_written);

Concluzie

Frumos că ai reușit! Sper că începi să înțelegi puterea bufferelor de protocol. Nu vă faceți griji, mi-a luat puțin timp să dau totul seama. Și tu poți fi un maestru al bufferului de protocol! ?

Dacă nu sunteți prea încântați de Protocol Buffers, există și alte alternative. Am folosit MessagePack cu ceva succes la produsele anterioare. Este simplu și are o mulțime de suport pentru majoritatea limbajelor de programare.

Dacă sunteți interesat cum să realizați acest lucru într-un proiect Bluetooth Low Energy, rămâneți la curent cu partea a doua. În partea a doua, vă voi arăta cum să configurați un serviciu și o caracteristică Bluetooth foarte simple, care vor fi folosite pentru a transfera datele noastre proaspăt codificate înainte și înapoi.

De asemenea, dacă doriți să vedeți tot codul în acțiune, puteți descărca totul de aici.