Python
The Python API is designed to be simple to use. It requires at least Python 3.8. It is not currently compatible with MicroPython.
Example
Code generation
$ bakelite gen -l python -i proto.bakelite -o proto.py
Use the generated code to implement a simple protocol.
from proto import Protocol, TestMessage, Ack # Generated by bakelite
import serial
# Open the serial port at 9600 baud
with serial.Serial("/dev/ttyusb0", 9600) as port:
# Create an instance of our protocol, passing the port
# we just opened as the stream.
proto = Protocol(stream=port)
# Send a test message
proto.send(
TestMessage(status=True, message="Ping!".encode('utf-8'))
)
# Wait for responses, and print them.
while True:
# Poll for new messages. By default, this function blocks
# untill there is data to read.
msg = proto.poll()
if msg:
if isinstance(msg, Ack): # if we got an ack, print it!
print(f"Ack code={msg.code} message=\"{msg.message.decode('utf-8')}\"")
else: # We got a message we didn't expect, print it too
print(msg)
String Handling
In Python 3, strings are Unicode by default. You'll need to encode the string as bytes before sending a message and decode them after receiving a message.
For example:
# send a message
msg TestMessage(
text: "This is a test string".encode('utf-8') # encode the string as utf-8
)
proto.send(msg)
# receive a message
msg = proto.poll()
print(msg.text.decode('utf-8'))
Any encoding that doesn't add null characters will work.
The two most common encoding types are utf-8
and ascii
.
If you're working with an embedded device that doesn't have native Unicode support, ascii
is recommended.
If you need to send utf-16
encoded text, use a bytes[] type instead.
API
Protocol
The Protocol class is generated by bakelite if you have a protocol
section in your protocol definition.
Pass any stream-like object to the constructor,
a serial port from PySerial, or a TCP socket, for example.
example definition:
protocol {
maxLength = 256
framing = COBS
crc = CRC8
messageIds {
...
}
}
__init__(self, stream: BufferedIOBase)
arguments:
- stream - Any stream like object that implements
read()
andwrite()
functions.
poll(self) -> Struct | None
Call this function to wail for a message. It will read any available data from the stream. If a message is available, the function will return the decoded struct for that message. Otherwise, it returns None.
returns:
Poll() will either return a valid struct or None if there is no message to decode.
send(self, message: Any) -> None
Sends a message. The message is serialized, encoded as a frame, and sent to the stream. You can pass any struct, as long as it was assigned a message ID in the protocol spec.
arguments:
- message - Any struct with an assigned message-id.
Struct
A struct class is generated for each struct in your protocol definition. Structs are dataclasses where each field in the struct becomes a member of the class.
For example, this struct definition:
struct TestMessage {
text: string[]
code: uint8
}
is equivalent to:
@dataclass
class TestMessage:
text: str
code: int
pack(self, stream) -> None:
...
unpack(stream) -> TestMessage:
...
The pack()
and unpack()
function are provided for serialization.
pack(self, stream: BufferedIOBase) -> None
Serialize the struct and write it to the stream.
arguments:
- stream - Any stream like object that implements
read()
andwrite()
functions.
unpack(stream: BufferedIOBase) -> Struct
Deserialize a struct from the stream.
unpack()
is a static method which returns an instance of the struct.
example:
msg = TestMessage.unpack(stream)
print(msg.text)
arguments:
- stream - Any stream like object that implements
read()
andwrite()
functions.
returns:
An instance of the struct class.
Enum
An enum class is generated for each enum in your protocol definition. Enums map to python's enum class.
For example:
enum MyColor: uint8 {
Red = 1
Green = 2
Blue = 3
}
Would map to:
class MyColor(enum):
Red = 1
Green = 2
Blue = 3
pack(self, stream) -> None:
...
unpack(stream) -> MyColor:
...
pack(self, stream: BufferedIOBase) -> None
Serialize the enum and write it to the stream.
arguments:
- stream - Any stream like object that implements
read()
andwrite()
functions.
unpack(stream: BufferedIOBase) -> Struct
Deserialize an enum from the stream.
unpack()
is a static method which returns an instance of the enum class.
example:
color = MyColor.unpack(stream)
print(color)
arguments:
- stream - Any stream like object that implements
read()
andwrite()
functions.
returns:
An instance of the enum class.