During the development of various devices, the question arises about the data transmission format.

For me, basically - almost all IoT and IIoT transfers data via HTTP.

Over the years, I have developed a very simple protocol that fulfills some requirements:

  • Simple to implement
  • Simple to parse
  • Simple to debug
  • Transfer protocol independent
  • Support meta information (timestamp, etc..)
  • Passing many values in one message

Many devices have no idea what time it is, so the message format does not require this field.

In order to at least somehow identify the device, the device field is required. Authentication is carried out to the transport level.

Devices that can buffer messages (for example, try to send later), a timestamp field is added to the metadata in which a unix timestamp is written.

Message format

Simple plain:

<device-id>|<key>=<value>|<key2>=<value2>

Simple plain with meta:

<device-id>;<meta-key>=<meta-value>,<meta-key2>=<meta-value2>|<key>=<value>|<key2>=<value2>

In JSON:

{
    "device": "<device-id>",
    "meta": {
        "<meta-key>": "<meta-value>"
    },
    "data": {
        "<key>": "<value>"
    }
}

Example

Plain:

esp8266-<mac-address>;model=esp8266-ws001,sw-version=11,wifi-ssid=<WIFI Name>|temperature__c=25.5|humidity=50|pressure__hpa=760|altitude=100

JSON:

{
    "device": "rpi-<mac-address>",
    "meta": {
        "model": "rpi-ws001",
        "sw-version": "22",
        "wifi-ssid": "<WIFI Name>"
    },
    "data": {
        "temperature__c": "25.5",
        "humidity": "50",
        "pressure__hpa": "760",
        "altitude": "100"
    }
}

Server code

Example server code for parse telemetry message:

type Message struct {
    Device string            `json:"device"`
    Meta   map[string]string `json:"meta,omitempty"`
    Data   map[string]string `json:"data,omitempty"`
}

func parseMessageKV(text, delimiter string) map[string]string {
    data := make(map[string]string)

    for _, kv := range strings.Split(text, delimiter) {
        if strings.Contains(kv, "=") {
            kvmap := strings.SplitN(kv, "=", 2)
            data[kvmap[0]] = kvmap[1]
        }
    }

    return data
}

func ParseMessage(payload []byte) (*Message, error) {
    msg := &Message{}

    if text := string(payload); strings.Contains(text, "|") {
        data := strings.SplitN(text, "|", 2)

        deviceInfo := data[0]

        if strings.Contains(deviceInfo, ";") && strings.Contains(deviceInfo, ",") && strings.Contains(deviceInfo, "=") {
            deviceInfoMeta := strings.Split(deviceInfo, ";")
            msg.Device = deviceInfoMeta[0]

            msg.Meta = parseMessageKV(deviceInfoMeta[1], ",")

        } else {
            msg.Device = deviceInfo
        }

        msg.Data = parseMessageKV(data[1], "|")
    } else {
        if err := json.Unmarshal(payload, &msg); err != nil {
            return nil, err
        }
    }

    return msg, nil
}