Skip to content

Basic Usage

dogtopus edited this page Nov 16, 2023 · 9 revisions

Creating schema

MiniPB supports 3 different flavors of schema declaration methods: Message classes (object serialization), Key-value schema (dict serialization), format string (tuple serialization). The expected Input/Output formats and Pros/Cons listed below each approach

Message class

import minipb

### Encode/Decode a Message with schema defined via Fields
@minipb.process_message_fields
class HelloWorldMessage(minipb.Message):
    msg = minipb.Field(1, minipb.TYPE_STRING)

# Creating a Message instance
#   Method 1: init with kwargs work!
msg_obj = HelloWorldMessage(msg='Hello world!')

#   Method 2: from_dict, iterates over all Field's declared in order on the class
msg_obj = HelloWorldMessage.from_dict({'msg': 'Hello world!'})

# Encode a message
encoded_msg = msg_obj.encode()
# encoded_message == b'\n\x0cHello world!'

# Decode a message
decoded_msg_obj = HelloWorldMessage.decode(encoded_msg)
# decoded_msg == HelloWorldMessage(msg='Hello world!')

decoded_dict = decoded_msg_obj.to_dict()
# decoded_dict == {'msg': 'Hello world!'}

Usage

  • Define a schema by subclassing Message and use Fields and the @process_message_fields decorator
  • instance.encode() outputs a bytes object
  • MessageClass.decode() takes a bytes object and returns an instance of the MessageClass

Pros

  • Most Pythonic way to work with dataclass like objects
  • Best IDE support (for completion, typing, etc.)
  • Explicitly allows for the specification of non-consecutive field-numbers (useful if you're mirroring an existing .proto definition)

Cons

  • For microcontrollers, introduces Object management overhead. May not be suitable for memory constrained applications

Format string

import minipb

### Encode/Decode a message with the Wire object and Format String
hello_world_msg = minipb.Wire('U')

# Encode a message
encoded_msg = hello_world_msg.encode('Hello world!')
# encoded_message == b'\n\x0cHello world!'

# Decode a message
decoded_msg = hello_world_msg.decode(encoded_msg)
# decoded_msg == ('Hello world!',)

Usage

  • Define a schema by passing a format-string to the Wire object
  • wire_instance.encode() takes a tuple of objects and outputs a bytes object
  • Wire.decode() takes a bytes object and outputs a tuple of objects

Pros

  • Least memory overhead of all 3 approaches, no dicts required for input/output

Cons

  • Format-string may be harder to work with compared to Message class approach
  • Does not directly support the specification of non-consecutive field-numbers (need to use field seek to emulate such behavior)

Key-value schema

import minipb

### Encode/Decode a message with the Wire object and Key-Value Schema
# Create the Wire object with schema
hello_world_msg = minipb.Wire([
    ('msg', 'U') # 'U' means UTF-8 string.
])

# Encode a message
encoded_msg = hello_world_msg.encode({
    'msg': 'Hello world!'
})
# encoded_message == b'\n\x0cHello world!'

# Decode a message
decoded_msg = hello_world_msg.decode(encoded_msg)
# decoded_msg == {'msg': 'Hello world!'}

Usage

  • Define a schema by passing a key-value-format-list to the Wire object
  • wire_instance.encode() takes a single dictionary with keys corresponding to the schema and outputs a bytes object
  • Wire.decode() takes a bytes objective and outputs a dictionary

Pros

  • Less Object management overhead vs Message class approach

Cons

  • Key-value-format-list may be harder to work with compared to Message class approach
  • Does not directly support non-consecutive field-numbers (need to use field seek to emulate such behavior)

Serialize/Deserialize

To serialize some objects, use:

encoded = schema.encode('hello', 1) # if using format string
encoded = schema.encode({'some_string': 'hello', 'some_int': 1}) # if using key-value format list

# Alternatively, use this to do one-off serialization without explicitly create a `Wire` object.
encoded = minipb.encode('UV', 'hello', 1)
# Same style also works for key-value format list, but looks less pretty.

Similarly, to deserialize some objects, use:

decoded = schema.decode(encoded)
# Returns ('hello', 1) if using format string, or {'some_string': 'hello', 'some_int': 1} if using key-value format list

# Alternatively, use this to do one-off deserialization without explicitly create a `Wire` object.
decoded = minipb.decode('UV', encoded)

Schema-less encoding/decoding

MiniPB offers the RawWire class that encodes and decodes data without a schema. For decoding it simply splits all the fields in the serialized bytes into a list of dictionaries that contains all the information included in the original serialized data. Similarly the encoding assembles the output from the decoder back to bytes.

TODO: add examples