Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,18 @@
Nanopb example "simple" using CMake
=======================
This example is the same as the simple nanopb example but built using CMake.
Example usage
-------------
On Linux, create a build directory and then call cmake:
nanopb/examples/cmake_simple$ mkdir build
nanopb/examples/cmake_simple$ cd build/
nanopb/examples/cmake_simple/build$ cmake ..
nanopb/examples/cmake_simple/build$ make
After that, you can run it with the command: ./simple
On other platforms supported by CMake, refer to CMake instructions.

View file

@ -0,0 +1,11 @@
// A very simple protocol definition, consisting of only
// one message.
syntax = "proto2";
import "sub/unlucky.proto";
message SimpleMessage {
required int32 lucky_number = 1;
required UnluckyNumber unlucky = 2;
}

View file

@ -0,0 +1,5 @@
syntax = "proto2";
message UnluckyNumber {
required uint32 number = 1;
}

View file

@ -0,0 +1,73 @@
#include <stdio.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "simple.pb.h"
int main()
{
/* This is the buffer where we will store our message. */
uint8_t buffer[128];
size_t message_length;
bool status;
/* Encode our message */
{
/* Allocate space on the stack to store the message data.
*
* Nanopb generates simple struct definitions for all the messages.
* - check out the contents of simple.pb.h!
* It is a good idea to always initialize your structures
* so that you do not have garbage data from RAM in there.
*/
SimpleMessage message = SimpleMessage_init_zero;
/* Create a stream that will write to our buffer. */
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
/* Fill in the lucky number */
message.lucky_number = 13;
message.unlucky.number = 42;
/* Now we are ready to encode the message! */
status = pb_encode(&stream, SimpleMessage_fields, &message);
message_length = stream.bytes_written;
/* Then just check for any errors.. */
if (!status)
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
}
/* Now we could transmit the message over network, store it in a file or
* wrap it to a pigeon's leg.
*/
/* But because we are lazy, we will just decode it immediately. */
{
/* Allocate space for the decoded message. */
SimpleMessage message = SimpleMessage_init_zero;
/* Create a stream that reads from the buffer. */
pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);
/* Now we are ready to decode the message. */
status = pb_decode(&stream, SimpleMessage_fields, &message);
/* Check for errors... */
if (!status)
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
/* Print the data contained in the message. */
printf("Your lucky number was %d!\n", message.lucky_number);
printf("Your unlucky number was %u!\n", message.unlucky.number);
}
return 0;
}

View file

@ -0,0 +1,18 @@
Nanopb example "simple" using CMake
=======================
This example is the same as the simple nanopb example but built using CMake.
Example usage
-------------
On Linux, create a build directory and then call cmake:
nanopb/examples/cmake_simple$ mkdir build
nanopb/examples/cmake_simple$ cd build/
nanopb/examples/cmake_simple/build$ cmake ..
nanopb/examples/cmake_simple/build$ make
After that, you can run it with the command: ./simple
On other platforms supported by CMake, refer to CMake instructions.

View file

@ -0,0 +1,71 @@
#include <stdio.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "simple.pb.h"
int main()
{
/* This is the buffer where we will store our message. */
uint8_t buffer[128];
size_t message_length;
bool status;
/* Encode our message */
{
/* Allocate space on the stack to store the message data.
*
* Nanopb generates simple struct definitions for all the messages.
* - check out the contents of simple.pb.h!
* It is a good idea to always initialize your structures
* so that you do not have garbage data from RAM in there.
*/
SimpleMessage message = SimpleMessage_init_zero;
/* Create a stream that will write to our buffer. */
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
/* Fill in the lucky number */
message.lucky_number = 13;
/* Now we are ready to encode the message! */
status = pb_encode(&stream, SimpleMessage_fields, &message);
message_length = stream.bytes_written;
/* Then just check for any errors.. */
if (!status)
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
}
/* Now we could transmit the message over network, store it in a file or
* wrap it to a pigeon's leg.
*/
/* But because we are lazy, we will just decode it immediately. */
{
/* Allocate space for the decoded message. */
SimpleMessage message = SimpleMessage_init_zero;
/* Create a stream that reads from the buffer. */
pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);
/* Now we are ready to decode the message. */
status = pb_decode(&stream, SimpleMessage_fields, &message);
/* Check for errors... */
if (!status)
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
/* Print the data contained in the message. */
printf("Your lucky number was %d!\n", message.lucky_number);
}
return 0;
}

View file

@ -0,0 +1,9 @@
// A very simple protocol definition, consisting of only
// one message.
syntax = "proto2";
message SimpleMessage {
required int32 lucky_number = 1;
}

View file

@ -0,0 +1,17 @@
# Include the nanopb provided Makefile rules
include ../../extra/nanopb.mk
# Compiler flags to enable all warnings & debug info
CFLAGS = -ansi -Wall -Werror -g -O0
CFLAGS += -I$(NANOPB_DIR)
all: server client
.SUFFIXES:
clean:
rm -f server client fileproto.pb.c fileproto.pb.h
%: %.c common.c fileproto.pb.c
$(CC) $(CFLAGS) -o $@ $^ $(NANOPB_CORE)

View file

@ -0,0 +1,60 @@
Nanopb example "network_server"
===============================
This example demonstrates the use of nanopb to communicate over network
connections. It consists of a server that sends file listings, and of
a client that requests the file list from the server.
Example usage
-------------
user@host:~/nanopb/examples/network_server$ make # Build the example
protoc -ofileproto.pb fileproto.proto
python ../../generator/nanopb_generator.py fileproto.pb
Writing to fileproto.pb.h and fileproto.pb.c
cc -ansi -Wall -Werror -I .. -g -O0 -I../.. -o server server.c
../../pb_decode.c ../../pb_encode.c fileproto.pb.c common.c
cc -ansi -Wall -Werror -I .. -g -O0 -I../.. -o client client.c
../../pb_decode.c ../../pb_encode.c fileproto.pb.c common.c
user@host:~/nanopb/examples/network_server$ ./server & # Start the server on background
[1] 24462
petteri@oddish:~/nanopb/examples/network_server$ ./client /bin # Request the server to list /bin
Got connection.
Listing directory: /bin
1327119 bzdiff
1327126 bzless
1327147 ps
1327178 ntfsmove
1327271 mv
1327187 mount
1327259 false
1327266 tempfile
1327285 zfgrep
1327165 gzexe
1327204 nc.openbsd
1327260 uname
Details of implementation
-------------------------
fileproto.proto contains the portable Google Protocol Buffers protocol definition.
It could be used as-is to implement a server or a client in any other language, for
example Python or Java.
fileproto.options contains the nanopb-specific options for the protocol file. This
sets the amount of space allocated for file names when decoding messages.
common.c/h contains functions that allow nanopb to read and write directly from
network socket. This way there is no need to allocate a separate buffer to store
the message.
server.c contains the code to open a listening socket, to respond to clients and
to list directory contents.
client.c contains the code to connect to a server, to send a request and to print
the response message.
The code is implemented using the POSIX socket api, but it should be easy enough
to port into any other socket api, such as lwip.

View file

@ -0,0 +1,138 @@
/* This is a simple TCP client that connects to port 1234 and prints a list
* of files in a given directory.
*
* It directly deserializes and serializes messages from network, minimizing
* memory use.
*
* For flexibility, this example is implemented using posix api.
* In a real embedded system you would typically use some other kind of
* a communication and filesystem layer.
*/
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "fileproto.pb.h"
#include "common.h"
/* This callback function will be called once for each filename received
* from the server. The filenames will be printed out immediately, so that
* no memory has to be allocated for them.
*/
bool ListFilesResponse_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
{
PB_UNUSED(ostream);
if (istream != NULL && field->tag == ListFilesResponse_file_tag)
{
FileInfo fileinfo = {};
if (!pb_decode(istream, FileInfo_fields, &fileinfo))
return false;
printf("%-10lld %s\n", (long long)fileinfo.inode, fileinfo.name);
}
return true;
}
/* This function sends a request to socket 'fd' to list the files in
* directory given in 'path'. The results received from server will
* be printed to stdout.
*/
bool listdir(int fd, char *path)
{
/* Construct and send the request to server */
{
ListFilesRequest request = {};
pb_ostream_t output = pb_ostream_from_socket(fd);
/* In our protocol, path is optional. If it is not given,
* the server will list the root directory. */
if (path == NULL)
{
request.has_path = false;
}
else
{
request.has_path = true;
if (strlen(path) + 1 > sizeof(request.path))
{
fprintf(stderr, "Too long path.\n");
return false;
}
strcpy(request.path, path);
}
/* Encode the request. It is written to the socket immediately
* through our custom stream. */
if (!pb_encode_delimited(&output, ListFilesRequest_fields, &request))
{
fprintf(stderr, "Encoding failed: %s\n", PB_GET_ERROR(&output));
return false;
}
}
/* Read back the response from server */
{
ListFilesResponse response = {};
pb_istream_t input = pb_istream_from_socket(fd);
if (!pb_decode_delimited(&input, ListFilesResponse_fields, &response))
{
fprintf(stderr, "Decode failed: %s\n", PB_GET_ERROR(&input));
return false;
}
/* If the message from server decodes properly, but directory was
* not found on server side, we get path_error == true. */
if (response.path_error)
{
fprintf(stderr, "Server reported error.\n");
return false;
}
}
return true;
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
char *path = NULL;
if (argc > 1)
path = argv[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* Connect to server running on localhost:1234 */
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
servaddr.sin_port = htons(1234);
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
{
perror("connect");
return 1;
}
/* Send the directory listing request */
if (!listdir(sockfd, path))
return 2;
/* Close connection */
close(sockfd);
return 0;
}

View file

@ -0,0 +1,43 @@
/* Simple binding of nanopb streams to TCP sockets.
*/
#include <sys/socket.h>
#include <sys/types.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "common.h"
static bool write_callback(pb_ostream_t *stream, const uint8_t *buf, size_t count)
{
int fd = (intptr_t)stream->state;
return send(fd, buf, count, 0) == count;
}
static bool read_callback(pb_istream_t *stream, uint8_t *buf, size_t count)
{
int fd = (intptr_t)stream->state;
int result;
if (count == 0)
return true;
result = recv(fd, buf, count, MSG_WAITALL);
if (result == 0)
stream->bytes_left = 0; /* EOF */
return result == count;
}
pb_ostream_t pb_ostream_from_socket(int fd)
{
pb_ostream_t stream = {&write_callback, (void*)(intptr_t)fd, SIZE_MAX, 0};
return stream;
}
pb_istream_t pb_istream_from_socket(int fd)
{
pb_istream_t stream = {&read_callback, (void*)(intptr_t)fd, SIZE_MAX};
return stream;
}

View file

@ -0,0 +1,9 @@
#ifndef _PB_EXAMPLE_COMMON_H_
#define _PB_EXAMPLE_COMMON_H_
#include <pb.h>
pb_ostream_t pb_ostream_from_socket(int fd);
pb_istream_t pb_istream_from_socket(int fd);
#endif

View file

@ -0,0 +1,16 @@
# This file defines the nanopb-specific options for the messages defined
# in fileproto.proto.
#
# If you come from high-level programming background, the hardcoded
# maximum lengths may disgust you. However, if your microcontroller only
# has a few kB of ram to begin with, setting reasonable limits for
# filenames is ok.
#
# On the other hand, using the callback interface, it is not necessary
# to set a limit on the number of files in the response.
* include:"sys/types.h"
* include:"dirent.h"
ListFilesResponse.file type:FT_CALLBACK, callback_datatype:"DIR*"
ListFilesRequest.path max_size:128
FileInfo.name max_size:128

View file

@ -0,0 +1,20 @@
// This defines protocol for a simple server that lists files.
//
// See also the nanopb-specific options in fileproto.options.
syntax = "proto2";
message ListFilesRequest {
optional string path = 1 [default = "/"];
}
message FileInfo {
required uint64 inode = 1;
required string name = 2;
}
message ListFilesResponse {
optional bool path_error = 1 [default = false];
repeated FileInfo file = 2;
}

View file

@ -0,0 +1,164 @@
/* This is a simple TCP server that listens on port 1234 and provides lists
* of files to clients, using a protocol defined in file_server.proto.
*
* It directly deserializes and serializes messages from network, minimizing
* memory use.
*
* For flexibility, this example is implemented using posix api.
* In a real embedded system you would typically use some other kind of
* a communication and filesystem layer.
*/
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "fileproto.pb.h"
#include "common.h"
/* This callback function will be called during the encoding.
* It will write out any number of FileInfo entries, without consuming unnecessary memory.
* This is accomplished by fetching the filenames one at a time and encoding them
* immediately.
*/
bool ListFilesResponse_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
{
PB_UNUSED(istream);
if (ostream != NULL && field->tag == ListFilesResponse_file_tag)
{
DIR *dir = *(DIR**)field->pData;
struct dirent *file;
FileInfo fileinfo = {};
while ((file = readdir(dir)) != NULL)
{
fileinfo.inode = file->d_ino;
strncpy(fileinfo.name, file->d_name, sizeof(fileinfo.name));
fileinfo.name[sizeof(fileinfo.name) - 1] = '\0';
/* This encodes the header for the field, based on the constant info
* from pb_field_t. */
if (!pb_encode_tag_for_field(ostream, field))
return false;
/* This encodes the data for the field, based on our FileInfo structure. */
if (!pb_encode_submessage(ostream, FileInfo_fields, &fileinfo))
return false;
}
/* Because the main program uses pb_encode_delimited(), this callback will be
* called twice. Rewind the directory for the next call. */
rewinddir(dir);
}
return true;
}
/* Handle one arriving client connection.
* Clients are expected to send a ListFilesRequest, terminated by a '0'.
* Server will respond with a ListFilesResponse message.
*/
void handle_connection(int connfd)
{
DIR *directory = NULL;
/* Decode the message from the client and open the requested directory. */
{
ListFilesRequest request = {};
pb_istream_t input = pb_istream_from_socket(connfd);
if (!pb_decode_delimited(&input, ListFilesRequest_fields, &request))
{
printf("Decode failed: %s\n", PB_GET_ERROR(&input));
return;
}
directory = opendir(request.path);
printf("Listing directory: %s\n", request.path);
}
/* List the files in the directory and transmit the response to client */
{
ListFilesResponse response = {};
pb_ostream_t output = pb_ostream_from_socket(connfd);
if (directory == NULL)
{
perror("opendir");
/* Directory was not found, transmit error status */
response.has_path_error = true;
response.path_error = true;
}
else
{
/* Directory was found, transmit filenames */
response.has_path_error = false;
response.file = directory;
}
if (!pb_encode_delimited(&output, ListFilesResponse_fields, &response))
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&output));
}
}
if (directory != NULL)
closedir(directory);
}
int main(int argc, char **argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
int reuse = 1;
/* Listen on localhost:1234 for TCP connections */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
servaddr.sin_port = htons(1234);
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) != 0)
{
perror("bind");
return 1;
}
if (listen(listenfd, 5) != 0)
{
perror("listen");
return 1;
}
for(;;)
{
/* Wait for a client */
connfd = accept(listenfd, NULL, NULL);
if (connfd < 0)
{
perror("accept");
return 1;
}
printf("Got connection.\n");
handle_connection(connfd);
printf("Closing connection.\n");
close(connfd);
}
return 0;
}

View file

@ -0,0 +1,5 @@
.pio/
.idea/
cmake-build-*/
/CMakeLists.txt
CMakeListsPrivate.txt

View file

@ -0,0 +1,48 @@
;
; You can setup `custom_nanopb_protos` `nanopb_options` vars to generate code from proto files
;
; Generator will use next folders:
;
; `$BUILD_DIR/nanopb/generated-src` - `*.pb.h` and `*.pb.c` files
; `$BUILD_DIR/nanopb/md5` - MD5 files to track changes in source .proto/.options
;
; Compiled `.pb.o` files will be located under `$BUILD_DIR/nanopb/generated-build`
;
; Example:
[env:pio_with_options]
platform = native
lib_deps = Nanopb
src_filter =
+<pio_with_options.c>
; All path are relative to the `$PROJECT_DIR`
custom_nanopb_protos =
+<proto/pio_with_options.proto>
custom_nanopb_options =
--error-on-unmatched
[env:pio_without_options]
platform = native
lib_deps = Nanopb
src_filter =
+<pio_without_options.c>
; All path are relative to the `$PROJECT_DIR`
custom_nanopb_protos =
+<proto/pio_without_options.proto>
[env:pio_esp32_idf]
platform = espressif32
board = firebeetle32
framework = espidf
lib_deps = Nanopb
; Warning: the 'src_filter' option cannot be used with ESP-IDF. Select source files to build in the project CMakeLists.txt file.
; So, we specified source files in src/CMakeLists.txt
custom_nanopb_protos =
+<proto/pio_without_options.proto>

View file

@ -0,0 +1 @@
TestMessageWithOptions.str max_size:16

View file

@ -0,0 +1,5 @@
syntax = "proto3";
message TestMessageWithOptions {
string str = 1;
}

View file

@ -0,0 +1,5 @@
syntax = "proto3";
message TestMessageWithoutOptions {
int32 number = 1;
}

View file

@ -0,0 +1,34 @@
#include "pb_encode.h"
#include "pb_decode.h"
#include "test.h"
#include "pio_without_options.pb.h"
void app_main() {
int status = 0;
uint8_t buffer[256];
pb_ostream_t ostream;
pb_istream_t istream;
size_t written;
TestMessageWithoutOptions original = TestMessageWithoutOptions_init_zero;
original.number = 45;
ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
TEST(pb_encode(&ostream, &TestMessageWithoutOptions_msg, &original));
written = ostream.bytes_written;
istream = pb_istream_from_buffer(buffer, written);
TestMessageWithoutOptions decoded = TestMessageWithoutOptions_init_zero;
TEST(pb_decode(&istream, &TestMessageWithoutOptions_msg, &decoded));
TEST(decoded.number == 45);
return status;
}

View file

@ -0,0 +1,35 @@
#include "pb_encode.h"
#include "pb_decode.h"
#include "test.h"
#include "pio_with_options.pb.h"
int main(int argc, char *argv[]) {
int status = 0;
uint8_t buffer[256];
pb_ostream_t ostream;
pb_istream_t istream;
size_t written;
TestMessageWithOptions original = TestMessageWithOptions_init_zero;
strcpy(original.str,"Hello");
ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
TEST(pb_encode(&ostream, &TestMessageWithOptions_msg, &original));
written = ostream.bytes_written;
istream = pb_istream_from_buffer(buffer, written);
TestMessageWithOptions decoded = TestMessageWithOptions_init_zero;
TEST(pb_decode(&istream, &TestMessageWithOptions_msg, &decoded));
TEST(strcmp(decoded.str,"Hello") == 0);
return status;
}

View file

@ -0,0 +1,35 @@
#include "pb_encode.h"
#include "pb_decode.h"
#include "test.h"
#include "pio_without_options.pb.h"
int main(int argc, char *argv[]) {
int status = 0;
uint8_t buffer[256];
pb_ostream_t ostream;
pb_istream_t istream;
size_t written;
TestMessageWithoutOptions original = TestMessageWithoutOptions_init_zero;
original.number = 45;
ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
TEST(pb_encode(&ostream, &TestMessageWithoutOptions_msg, &original));
written = ostream.bytes_written;
istream = pb_istream_from_buffer(buffer, written);
TestMessageWithoutOptions decoded = TestMessageWithoutOptions_init_zero;
TEST(pb_decode(&istream, &TestMessageWithoutOptions_msg, &decoded));
TEST(decoded.number == 45);
return status;
}

View file

@ -0,0 +1,9 @@
#include <stdio.h>
#define TEST(x) \
if (!(x)) { \
fprintf(stderr, "\033[31;1mFAILED:\033[22;39m %s:%d %s\n", __FILE__, __LINE__, #x); \
status = 1; \
} else { \
printf("\033[32;1mOK:\033[22;39m %s\n", #x); \
}

View file

@ -0,0 +1,22 @@
# Include the nanopb provided Makefile rules
include ../../extra/nanopb.mk
# Compiler flags to enable all warnings & debug info
CFLAGS = -Wall -Werror -g -O0
CFLAGS += "-I$(NANOPB_DIR)"
# C source code files that are required
CSRC = simple.c # The main program
CSRC += simple.pb.c # The compiled protocol definition
CSRC += $(NANOPB_DIR)/pb_encode.c # The nanopb encoder
CSRC += $(NANOPB_DIR)/pb_decode.c # The nanopb decoder
CSRC += $(NANOPB_DIR)/pb_common.c # The nanopb common parts
# Build rule for the main program
simple: $(CSRC)
$(CC) $(CFLAGS) -osimple $(CSRC)
# Build rule for the protocol
simple.pb.c: simple.proto
$(PROTOC) $(PROTOC_OPTS) --nanopb_out=. simple.proto

View file

@ -0,0 +1,29 @@
Nanopb example "simple"
=======================
This example demonstrates the very basic use of nanopb. It encodes and
decodes a simple message.
The code uses four different API functions:
* pb_ostream_from_buffer() to declare the output buffer that is to be used
* pb_encode() to encode a message
* pb_istream_from_buffer() to declare the input buffer that is to be used
* pb_decode() to decode a message
Example usage
-------------
On Linux, simply type "make" to build the example. After that, you can
run it with the command: ./simple
On other platforms, you first have to compile the protocol definition using
the following command::
../../generator-bin/protoc --nanopb_out=. simple.proto
After that, add the following five files to your project and compile:
simple.c simple.pb.c pb_encode.c pb_decode.c pb_common.c

View file

@ -0,0 +1,71 @@
#include <stdio.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "simple.pb.h"
int main()
{
/* This is the buffer where we will store our message. */
uint8_t buffer[128];
size_t message_length;
bool status;
/* Encode our message */
{
/* Allocate space on the stack to store the message data.
*
* Nanopb generates simple struct definitions for all the messages.
* - check out the contents of simple.pb.h!
* It is a good idea to always initialize your structures
* so that you do not have garbage data from RAM in there.
*/
SimpleMessage message = SimpleMessage_init_zero;
/* Create a stream that will write to our buffer. */
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
/* Fill in the lucky number */
message.lucky_number = 13;
/* Now we are ready to encode the message! */
status = pb_encode(&stream, SimpleMessage_fields, &message);
message_length = stream.bytes_written;
/* Then just check for any errors.. */
if (!status)
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
}
/* Now we could transmit the message over network, store it in a file or
* wrap it to a pigeon's leg.
*/
/* But because we are lazy, we will just decode it immediately. */
{
/* Allocate space for the decoded message. */
SimpleMessage message = SimpleMessage_init_zero;
/* Create a stream that reads from the buffer. */
pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);
/* Now we are ready to decode the message. */
status = pb_decode(&stream, SimpleMessage_fields, &message);
/* Check for errors... */
if (!status)
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
/* Print the data contained in the message. */
printf("Your lucky number was %d!\n", (int)message.lucky_number);
}
return 0;
}

View file

@ -0,0 +1,9 @@
// A very simple protocol definition, consisting of only
// one message.
syntax = "proto2";
message SimpleMessage {
required int32 lucky_number = 1;
}

View file

@ -0,0 +1,20 @@
# Include the nanopb provided Makefile rules
include ../../extra/nanopb.mk
# Compiler flags to enable all warnings & debug info
CFLAGS = -ansi -Wall -Werror -g -O0
CFLAGS += -I$(NANOPB_DIR)
all: encode decode
./encode 1 | ./decode
./encode 2 | ./decode
./encode 3 | ./decode
.SUFFIXES:
clean:
rm -f encode unionproto.pb.h unionproto.pb.c
%: %.c unionproto.pb.c
$(CC) $(CFLAGS) -o $@ $^ $(NANOPB_CORE)

View file

@ -0,0 +1,55 @@
Nanopb example "using_union_messages"
=====================================
Union messages is a common technique in Google Protocol Buffers used to
represent a group of messages, only one of which is passed at a time.
It is described in Google's documentation:
https://developers.google.com/protocol-buffers/docs/techniques#union
This directory contains an example on how to encode and decode union messages
with minimal memory usage. Usually, nanopb would allocate space to store
all of the possible messages at the same time, even though at most one of
them will be used at a time.
By using some of the lower level nanopb APIs, we can manually generate the
top level message, so that we only need to allocate the one submessage that
we actually want. Similarly when decoding, we can manually read the tag of
the top level message, and only then allocate the memory for the submessage
after we already know its type.
NOTE: There is a newer protobuf feature called `oneof` that is also supported
by nanopb. It might be a better option for new code.
Example usage
-------------
Type `make` to run the example. It will build it and run commands like
following:
./encode 1 | ./decode
Got MsgType1: 42
./encode 2 | ./decode
Got MsgType2: true
./encode 3 | ./decode
Got MsgType3: 3 1415
This simply demonstrates that the "decode" program has correctly identified
the type of the received message, and managed to decode it.
Details of implementation
-------------------------
unionproto.proto contains the protocol used in the example. It consists of
three messages: MsgType1, MsgType2 and MsgType3, which are collected together
into UnionMessage.
encode.c takes one command line argument, which should be a number 1-3. It
then fills in and encodes the corresponding message, and writes it to stdout.
decode.c reads a UnionMessage from stdin. Then it calls the function
decode_unionmessage_type() to determine the type of the message. After that,
the corresponding message is decoded and the contents of it printed to the
screen.

View file

@ -0,0 +1,95 @@
/* This program reads a message from stdin, detects its type and decodes it.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pb_decode.h>
#include <pb_common.h>
#include "unionproto.pb.h"
/* This function reads manually the first tag from the stream and finds the
* corresponding message type. It doesn't yet decode the actual message.
*
* Returns a pointer to the MsgType_fields array, as an identifier for the
* message type. Returns null if the tag is of unknown type or an error occurs.
*/
const pb_msgdesc_t* decode_unionmessage_type(pb_istream_t *stream)
{
pb_wire_type_t wire_type;
uint32_t tag;
bool eof;
while (pb_decode_tag(stream, &wire_type, &tag, &eof))
{
if (wire_type == PB_WT_STRING)
{
pb_field_iter_t iter;
if (pb_field_iter_begin(&iter, UnionMessage_fields, NULL) &&
pb_field_iter_find(&iter, tag))
{
/* Found our field. */
return iter.submsg_desc;
}
}
/* Wasn't our field.. */
pb_skip_field(stream, wire_type);
}
return NULL;
}
bool decode_unionmessage_contents(pb_istream_t *stream, const pb_msgdesc_t *messagetype, void *dest_struct)
{
pb_istream_t substream;
bool status;
if (!pb_make_string_substream(stream, &substream))
return false;
status = pb_decode(&substream, messagetype, dest_struct);
pb_close_string_substream(stream, &substream);
return status;
}
int main()
{
/* Read the data into buffer */
uint8_t buffer[512];
size_t count = fread(buffer, 1, sizeof(buffer), stdin);
pb_istream_t stream = pb_istream_from_buffer(buffer, count);
const pb_msgdesc_t *type = decode_unionmessage_type(&stream);
bool status = false;
if (type == MsgType1_fields)
{
MsgType1 msg = {};
status = decode_unionmessage_contents(&stream, MsgType1_fields, &msg);
printf("Got MsgType1: %d\n", msg.value);
}
else if (type == MsgType2_fields)
{
MsgType2 msg = {};
status = decode_unionmessage_contents(&stream, MsgType2_fields, &msg);
printf("Got MsgType2: %s\n", msg.value ? "true" : "false");
}
else if (type == MsgType3_fields)
{
MsgType3 msg = {};
status = decode_unionmessage_contents(&stream, MsgType3_fields, &msg);
printf("Got MsgType3: %d %d\n", msg.value1, msg.value2);
}
if (!status)
{
printf("Decode failed: %s\n", PB_GET_ERROR(&stream));
return 1;
}
return 0;
}

View file

@ -0,0 +1,90 @@
/* This program takes a command line argument and encodes a message in
* one of MsgType1, MsgType2 or MsgType3.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pb_encode.h>
#include <pb_common.h>
#include "unionproto.pb.h"
/* This function is the core of the union encoding process. It handles
* the top-level pb_field_t array manually, in order to encode a correct
* field tag before the message. The pointer to MsgType_fields array is
* used as an unique identifier for the message type.
*/
bool encode_unionmessage(pb_ostream_t *stream, const pb_msgdesc_t *messagetype, void *message)
{
pb_field_iter_t iter;
if (!pb_field_iter_begin(&iter, UnionMessage_fields, message))
return false;
do
{
if (iter.submsg_desc == messagetype)
{
/* This is our field, encode the message using it. */
if (!pb_encode_tag_for_field(stream, &iter))
return false;
return pb_encode_submessage(stream, messagetype, message);
}
} while (pb_field_iter_next(&iter));
/* Didn't find the field for messagetype */
return false;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s (1|2|3)\n", argv[0]);
return 1;
}
uint8_t buffer[512];
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
bool status = false;
int msgtype = atoi(argv[1]);
if (msgtype == 1)
{
/* Send message of type 1 */
MsgType1 msg = {42};
status = encode_unionmessage(&stream, MsgType1_fields, &msg);
}
else if (msgtype == 2)
{
/* Send message of type 2 */
MsgType2 msg = {true};
status = encode_unionmessage(&stream, MsgType2_fields, &msg);
}
else if (msgtype == 3)
{
/* Send message of type 3 */
MsgType3 msg = {3, 1415};
status = encode_unionmessage(&stream, MsgType3_fields, &msg);
}
else
{
fprintf(stderr, "Unknown message type: %d\n", msgtype);
return 2;
}
if (!status)
{
fprintf(stderr, "Encoding failed!\n");
return 3;
}
else
{
fwrite(buffer, 1, stream.bytes_written, stdout);
return 0; /* Success */
}
}

View file

@ -0,0 +1,32 @@
// This is an example of how to handle 'union' style messages
// with nanopb, without allocating memory for all the message types.
//
// There is no official type in Protocol Buffers for describing unions,
// but they are commonly implemented by filling out exactly one of
// several optional fields.
syntax = "proto2";
message MsgType1
{
required int32 value = 1;
}
message MsgType2
{
required bool value = 1;
}
message MsgType3
{
required int32 value1 = 1;
required int32 value2 = 2;
}
message UnionMessage
{
optional MsgType1 msg1 = 1;
optional MsgType2 msg2 = 2;
optional MsgType3 msg3 = 3;
}