6.8. Sending and Consuming Binary Data From EdgeX Device Services

EdgeX - Fuji Release

6.8.1. Overview

In this example, we will demonstrate how to send EdgeX Events and Readings that contain arbitrary binary data.

6.8.2. DeviceService Implementation

6.8.2.1. Device Profile

To indicate that a deviceResource represents a Binary type, the following format is used:

- name: "camera_snapshot"
  description: "snapshot from camera"
  properties:
  value:
      { type: "Binary", readWrite: "R" }
  units:
      { type: "Binary", readWrite: "R", defaultValue: "CameraSnapshot" }

6.8.2.2. Device Service

Here is a snippet from a hypothetical Device Service’s HandleReadCommands() method that produces an event that represents a JPEG image captured from a camera:

if req.DeviceResourceName == "camera_snapshot" {
  data, err := cameraClient.GetSnapshot() // returns ([]byte, error)
  check(err)

  cv, err := sdkModel.NewBinaryValue(reqs[i].DeviceResourceName, 0, data)
  check(err)

  responses[i] = cv
}

6.8.3. Calling Device Service Command

Querying core-metadata for the Device’s Commands and DeviceID provides the following as the URL to request a reading from the snapshot command: http://localhost:49990/api/v1/device/3469a658-c3b8-46f1-9098-7d19973af402/OnvifSnapshot

Unlike with non-binary Events, making a request to this URL will return an event in CBOR representation. CBOR is a representation of binary data loosely based off of the JSON data model. This Event will not be human-readable.

6.8.3.1. Parsing CBOR Encoded Events

To access the data enclosed in these Events and Readings, they will first need to be decoded from CBOR. The following is a simple Go program that reads in the CBOR response from a file containing the response from the previous HTTP request. The Go library recommended for parsing these events can be found at https://github.com/ugorji/go

package main

import (
      “io/ioutil”

      contracts “github.com/edgexfoundry/go-mod-core-contracts/models”
      “github.com/ugorji/go/codec”
)

func check(e error) {
      if e != nil {
              panic(e)
      }
}

func main() {
      // Read in our cbor data
      fileBytes, err := ioutil.ReadFile(“/Users/johndoe/Desktop/image.cbor”)
      check(err)

      // Create a cbor decoder from the cbor bytes and a cbor code handle
      var h codec.Handle = new(codec.CborHandle)
      var dec *codec.Decoder = codec.NewDecoderBytes(fileBytes, h)

      // Decode into an EdgeX Event
      var event contracts.Event
      err = dec.Decode(&event)
      check(err)

      // Grab binary data and write to a file
      imgBytes := event.Readings[0].BinaryValue
      ioutil.WriteFile(“/Users/johndoe/Desktop/image.jpeg”, imgBytes, 0644)
}

In the code above, the CBOR data is read into a buffer, a code.Decoder is created to decode the CBOR data, an EdgeX Event struct is created, and a pointer is passed into the decoder’s Decode() method to be filled in. Finally, the binary payload is written to a file from the BinaryValue field of the Reading.

This method would work as well for decoding Events off the EdgeX message bus.

6.8.3.2. Encoding Arbitrary Structures in Events

The Device SDK’s NewBinaryValue() function above only accepts a byte slice as binary data. Any arbitrary Go structure can be encoded in a binary reading by first encoding the structure into a byte slice using CBOR. The following illustrates this method:

// DeviceService HandleReadCommands() code:
foo := struct {
      X int
      Y int
      Z int
      Bar string
} {
      X: 7,
      Y: 3,
      Z: 100,
      Bar: "Hello world!",
}

buffer := new(bytes.Buffer)
ch := new(codec.CborHandle)
encoder := codec.NewEncoder(buffer, ch)

err = encoder.Encode(&foo)
check(err)

cv, err := sdkModel.NewBinaryValue(reqs[i].DeviceResourceName, 0, buffer.Bytes())
responses[i] = cv

This code takes the anonymous struct with fields X, Y, Z, and Bar (of different types) and serializes it into a byte slice using the same codec library, and passing the output to NewBinaryValue().

When consuming these events, another level of decoding will need to take place to get the structure out of the binary payload.

func main() {
  // Read in our cbor data
  fileBytes, err := ioutil.ReadFile(“/Users/johndoe/Desktop/image.cbor”)
  check(err)

  // Create a cbor decoder from the cbor bytes and a cbor code handle
  var h codec.Handle = new(codec.CborHandle)
  var dec *codec.Decoder = codec.NewDecoderBytes(fileBytes, h)

  // Decode into an EdgeX Event
  var event contracts.Event
  err = dec.Decode(&event)
  check(err)

  // Decode into arbitrary type
  foo := struct {
  X   int
  Y   int
  Z   int
  Bar string
  }{}

  dec = codec.NewDecoderBytes(event.Readings[0].BinaryValue, h)
  err = dec.Decode(&foo)
  check(err)

  fmt.Println(foo)
}

This code takes a command response in the same format as the previous example, but uses the codec library to decode the CBOR data inside the EdgeX Reading’s BinaryValue field.

Using this approach, an Event can be sent containing data containing an arbitrary, flexible structure. Use cases could be a Reading containing multiple images, a variable length list of integer read-outs, etc.