Tutorial: Serialize and Deserialize Data


This tutorial walks you through sample code that serializes (encodes) and deserializes (decodes) directives and events for each Alexa Gadgets Toolkit interface. This tutorial uses a C language implementation, but you can extend the concepts to other languages such as Python or Java.

Steps to serialize and deserialize data for each interface

Complete the following steps to serialize and deserialize directives and events for each Alexa Gadgets Toolkit interface.

  1. Get the resources, including the nanopb .proto compiler and the Alexa Gadgets sample code repository.
  2. Copy the utility files from your nanopb directory into the /examples directory of the Alexa Gadgets repository.
  3. Compile the .proto files into classes that you can reference from your gadget code.
  4. Compile the sample from the /examples directory.
  5. Run the sample, which encodes and decodes Alexa Gadgets directives and events to and from binary, and prints out the results.
  6. (Optional) Modify the statically allocated data size to optimize memory usage.

Step 1: Get the resources

The first step is to get the nanopb .proto compiler and the Alexa Gadgets sample code repository.

To get the resources

  1. Download the nanopb .proto compiler – To use protocol buffers with C, we recommend that you use nanopb. Nanopb is a small code-size protocol buffer implementation in ANSI C. It's especially suitable for use in microcontrollers, but fits any memory-restricted system. To install nanopb, complete the following steps:
    1. Download nanopb 3.x at Nanopb Downloads. This sample code doesn't work with nanopb 4.x.
    2. Extract the contents of the nanopb download. The extracted nanopb directory contains an executable to compile .proto files (that is, get *.c and *.h files). It also contains helper files that contain functions and data structures to encode and decode any protobuf-encoded data.
  2. Download the Alexa Gadgets sample code repository – Get the Alexa Gadgets Sample Code GitHub repository. This repository contains Alexa Gadget .proto files and sample code.

Step 2: Copy the utility files

  • Copy the following files from your nanopb directory into the /examples directory of the Alexa Gadgets repository that you downloaded in the previous step.
    These files are the utility files that define the methods and the data structures required to encode and decode the protobuf-encoded messages:
    • pb.h
    • pb_common.c
    • pb_common.h
    • pb_decode.c
    • pb_decode.h
    • pb_encode.c
    • pb_encode.h

Step 3: Compile the .proto files

You now must compile the Alexa Gadgets .proto files into classes that you can reference from your gadget code.

To compile the .proto files

  1. Navigate to the /examples directory of the Alexa Gadgets sample code repository that you downloaded.
  2. Open compile_nanos.sh (on Mac or Linux) or compile_nanos.bat (on Windows), and then make sure that your nanopb .proto compiler is located at the specified PROTO_COMPILE_PATH.
  3. From the /examples directory, run compile_nanos.sh (on Mac or Linux) or compile_nanos.bat (on Windows).
    This action compiles all of the .proto files in the Alexa Gadgets sample code repository, and then puts the resulting C and header source files into the /examples directory.

Step 4: Compile the sample

  • From the /examples directory, compile proto_sample.c by using the following command:

Copied to clipboard.

gcc -I. -DPB_FIELD_16BIT notificationsSetIndicatorDirectivePayload.pb.c notificationsSetIndicatorDirective.pb.c alexaDiscoveryDiscoverResponseEventPayload.pb.c alexaDiscoveryDiscoverResponseEvent.pb.c alexaDiscoveryDiscoverDirective.pb.c alexaDiscoveryDiscoverDirectivePayload.pb.c alexaGadgetStateListenerStateUpdateDirective.pb.c alexaGadgetStateListenerStateUpdateDirectivePayload.pb.c alexaGadgetSpeechDataSpeechmarksDirective.pb.c alexaGadgetSpeechDataSpeechmarksDirectivePayload.pb.c pb_common.c pb_decode.c pb_encode.c directiveHeader.pb.c eventHeader.pb.c directiveParser.pb.c eventParser.pb.c proto_sample.c -o proto_sample

Step 5: Run the sample

  • Execute ./proto_sample, which encodes and decodes Alexa Gadgets directives and events to and from binary, and prints out the results.

The following table shows the functions that the sample code contains.

Function Description
decode_directive() Decodes all Alexa Gadgets directives. If this function doesn't recognize the interface namespace and directive name, it prints an error. You can add your own directive parsing code to this function.
decode_event() Decodes all Alexa Gadgets events. If this function doesn't recognize the interface namespace and event name, it prints an error. You can add your own event parsing code to this function.
encode_set_indicator_directive()

Puts the following Notifications.SetIndicator directive into binary form.

 {
   "directive": {
      "header": {
         "namespace": "Notifications",
         "name": "SetIndicator",
         "messageId": "messageID1"
      },
      "payload": {
         "persistVisualIndicator": true,
         "playAudioIndicator": true,
         "asset": {
            "assetId": "assetID1",
            "url": "url1"
         }
      }
   }
}
encode_sample_discover_response_event()

Puts the following Alexa.Discovery.Discover.Response event into binary form.

{
   "event": {
      "header": {
         "namespace": "Alexa.Discovery",
         "name": "Discover.Response",
         "messageId": ""
      },
      "payload": {
         "endpoints": [{
            "endpointId" : " test id",
            "friendlyName": " friendly name",
            "capabilities": [
               {
                  "type": "test type 1",
                  "interface": " Test interface 1",
                  "version":  "1.0"
               },
               {
                  "type": "test type 2",
                  "interface": " Test interface 2",
                  "version":  "1.0"
               },
               {
                  "type": "test type 3",
                  "interface": " Test interface 3",
                  "version":  "1.1"
               }],   
               "additionalIdentification": {
                  "firmwareVersion": "19",
                  "deviceToken": "xxxxxxxxx",
                  "deviceTokenEncryptionType": "yyy",
                  "amazonDeviceType": "aabbccd",
                  "modelName": "mock model name",
                  "radioAddress": "1234567890"
               }
         }]
      }
   }
}
encode_sample_discover_directive()

Puts the following Alexa.Discovery.Discover directive into binary form.

{
   "directive": {
      "header": {
         "namespace": "Alexa.Discovery",
         "name": "Discover",
         "messageId": ""
      },
      "payload": {
         "scope": {
            "type": "",
            "token": ""
         }
      }
   }
}
encode_sample_state_update_directive()

Puts the following StateListener.StateUpdate directive, which notifies the gadget that a timer was set, into binary form.

{
   "directive": {
      "header": {
         "namespace": "Alexa.Gadget.StateListener",
         "name": "StateUpdate",
         "messageId": ""
      },
      "payload": {
         "states": [{
            "name": "timers",
            "value": "active"
         }]
      }
   }
}
encode_sample_speechmarks_directive()

Puts the following SpeechData.Speechmarks directive into binary form.

{
   "directive": {
      "header": {
         "namespace": "Alexa.Gadget.SpeechData",
         "name": "Speechmarks",
         "messageId": ""
      },
      "payload": {
         "playerOffsetInMilliSeconds": 0,
         "speechmarksData": [{
            "type": "viseme",
            "value": "s",
            "startOffsetInMilliSeconds": 130
         }]
      }
   }
}
main() Calls the functions listed previously.

Output

When you run the sample, you get the following output.

(Optional) Step 6: Modify the statically allocated data size

In the Alexa Gadgets sample code repository, there's an options file that corresponds to the .proto file of each directive and event that contains one or more fields in its payload.

The options files specify the maximum size for each field in the payload. This instructs the .proto compiler to statically allocate memory for the fields. For example, the options file for the Discover.Response event defines the size of the capabilities array as follows:

alexaDiscovery.DiscoverResponseEventPayloadProto.Endpoints.capabilities    max_count:32

When the compiler generates the code, it therefore allocates memory for 32 capabilities.

To optimize memory usage

  1. Open the options file that corresponds to the .proto file you want modify.
  2. Decrease the max_count (or max_size, where that is specified instead).
    These decreases cause the compiler to statically allocate less memory to the .proto messages that you create.

Was this page helpful?

Last updated: Feb 14, 2022