diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4a60754 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,94 @@ +# Bakelite + +Code generator for reliable embedded communication protocols. Generates serialization, framing, and CRC code from protocol definitions. + +## Purpose + +Bakelite makes it easier to build reliable communication protocols for embedded systems. While desktop/mobile systems have HTTP/REST, gRPC, etc., these aren't suitable for embedded systems with constrained memory and CPU. Bakelite targets tiny systems without a heap or memory allocator. + +**Wire format philosophy**: Predictable from the definition—a developer should be able to guess the wire format after reading the spec once. Bakelite is unopinionated beyond message encoding and framing. No concept of client/server—works for mesh networks, DMA, IPC, etc. + +**Versioning** (planned): Will be minimal and developer-controlled. + +## Target Platforms + +**Embedded** (no heap, static allocation): +- `cpptiny` - C++ +- `ctiny` - C99 + +**Application**: +- `python` - Full runtime + +**Planned**: SystemVerilog, MicroPython, embedded Rust, C++ with stdlib, C#, Java, Rust, TypeScript. New features should consider future language implementations. + +## Project Structure + +- `bakelite/generator/` - Code generation (parser, templates, CLI) + - `protodef.lark` - Lark grammar for `.bakelite` IDL + - `templates/` - Jinja2 templates (one per language) + - `runtimes/` - Static runtime support files (`common/`, `cpptiny/`, `ctiny/`) +- `bakelite/proto/` - Python runtime (serialization, COBS framing, CRC) +- `bakelite/tests/` - Test suite +- `examples/` - Arduino (Python↔embedded) and TCP chat (C/C++) +- `docs/` - mkdocs documentation + +## Architecture + +- **Generator**: Parses `.bakelite` IDL → generates language-specific code via Jinja2 templates +- **Runtime**: Static helper code (framing, CRC) that doesn't change per-protocol +- **Zero-copy design**: Embedded targets use single-buffer approach, no dynamic allocation +- **Template mapping**: Each language has `{lang}.py` generator + `{lang}*.j2` templates + +## Design Constraints + +When adding features: +- Embedded targets: No heap allocation, static buffers only +- Wire format must remain predictable from protocol definition +- Features must be implementable across all planned target languages +- Keep protocol-level opinions minimal (except versioning) + +## Development + +Uses **Pixi** for dependency management and task running. + +```bash +pixi run test # Run Python tests +pixi run test-cpp # Run C++ tests +pixi run test-all # Run all tests +pixi run lint # Run ruff and mypy +pixi run format # Auto-format with ruff +pixi run build # Build package +pixi run docs # Build documentation +``` + +## Key Technologies + +- **Lark** - Parser generator for protocol DSL (`protodef.lark`) +- **Jinja2** - Template engine for code generation +- **Ruff** - Linting and formatting +- **mypy** - Static type checking +- **pytest** - Testing with pytest-describe for BDD-style tests +- **hatchling** - Build backend for packaging + +## CLI + +```bash +bakelite gen -l python -i proto.bakelite -o output.py +bakelite gen -l cpptiny -i proto.bakelite -o output.h +bakelite gen -l ctiny -i proto.bakelite -o output.h +bakelite runtime -l cpptiny -o bakelite.h +``` + +## Code Style + +- Python 3.13+ +- Type hints enforced via mypy (strict mode) +- 4-space indentation, 100 char line length +- Modern type syntax (`X | None`, `list[X]`, etc.) +- Priorities: devex, ergonomic API (even for generated code), small memory footprint, correctness, performance +- Prefer improving type hints over `# type: ignore` comments +- The bakelite tool is invoked with `bakelite` command. User docs should assume bakelite is in the path. +- PRs, commits and documentation should be to the point. Only be verbose when the details matter. +- Don't leave comments about what you've changed unless it's a commit/PR or changelog. +- Make sure lint is clean and all tests pass after each change. +- All target languages must have tests with at least 80% code coverage. diff --git a/docs/protocol.md b/docs/protocol.md index 218bf7b..6141c14 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -1,101 +1,130 @@ -# Protocol Definition +# Protocol Specification + +## Overview + +Bakelite's protocol definition language lets you describe message structures for embedded communication. + +**Design goals:** +- Simple, easily understood on-wire layout +- Suitable for resource-constrained embedded systems +- Low boilerplate + +**Non-goals:** +- Not intended as a file interchange format +- Not useful when one side doesn't know the schema + +## Types + +### Quick Reference + +| Type | Size | Description | +|------|------|-------------| +| `int8` | 1 byte | Signed integer | +| `int16` | 2 bytes | Signed integer | +| `int32` | 4 bytes | Signed integer | +| `int64` | 8 bytes | Signed integer | +| `uint8` | 1 byte | Unsigned integer | +| `uint16` | 2 bytes | Unsigned integer | +| `uint32` | 4 bytes | Unsigned integer | +| `uint64` | 8 bytes | Unsigned integer | +| `float32` | 4 bytes | IEEE 754 single precision | +| `float64` | 8 bytes | IEEE 754 double precision | +| `bool` | 1 byte | `true` (1) or `false` (0) | +| `bytes[N]` | N bytes | Fixed-length byte array | +| `bytes[]` | 1 + data | Variable-length bytes (length-prefixed, max 255) | +| `string[N]` | N bytes | Fixed-length null-terminated string | +| `string[]` | data + 1 | Variable-length null-terminated string | +| `T[N]` | N × sizeof(T) | Fixed-length array | +| `T[]` | 1 + elements | Variable-length array (length-prefixed, max 255) | -## Design Goals -* Simple -* Easily understood on-wire layout -* Composable -* Suitable for use on resource constrained embedded systems -* Low boilerplate +### Numeric Types -## Design Non-Goals -* Not intended for use as a file interchange format -* Not useful in situations where one side doesn't know the schema +All numeric types are little-endian. -## Primitive Types -### Numeric Types -|Name |Size (Bytes) |Description | -|------------------------------|--------------|-----------------------| -|int8, int16, int32, int64 |8, 16, 32, 64 | Signed integer | -|uint8, uint16, uint32, uint64 |8, 16, 32, 64 | Unsigned integer | -|float32, float64 |32, 64 | Floating point number | -|bool |1 | true/false value | - - -### Variable Length -|Name |Size (Bytes)|Description| -|---------|------------|-----------| -|bytes |Fixes, N+1 | A list of uint8s | -|string |Fixed, N+1 | A null-terminated list of uint8s, with optional encoding (ascii or utf8) | - -#### Bytes (Fixed Length) `bytes[n]` -Fixed length byte types contain a defined number of bytes. -Each byte can have any value, including NULL. - -For Example: - - - - -
Typebytes[4]
Byte1234
Value0x050x000xAF0xDE
- -#### Bytes (Variable Length) `bytes[]` -Variable length byte arrays may be any size up to 255 bytes. -When one of these fields is serialized, the first byte indicates the size, -while the remaining bytes contain the data. - -For example: - - - - - -
Typebytes[]
Byte1234
Value0x030xAA0x000xDE
PurposeSizeData
- -#### String (Fixed Length) `string[n]` -Strings are always null-terminated. -If a string has a fixed length, any characters after the null-terminator may have any value. - -For example: - - - - -
Typestring[5]
Byte12345
ValueHey0x000x00
- -Strings must always contain a null byte. -The below example is invalid, because there is no room to store the null byte. - - - - -
Typestring[5]
Byte12345
ValueHello
- -#### String (Variable Length) `string[]` -Unlike `bytes[]` variable lengths strings do not contain a size byte. -Instead, the serializer will read until it encounters the first null byte. - -For example: - - - - -
Typestring[]
Byte1234
ValueHey0x00
- -### Bitfield Types -__Not for V1__ - -|Name |Size (Bits)|Description | -|-----------|-----------|----------------------------| -|uint{N} |N bits | Unsigned integer | -|int{N} |N bits | Signed integer | -|flag |1 bit | True/False value | -|unused{N} |N bit | reserved or padding bits | - -All types have a maximum length of 64 bits. +``` +uint16 = 0x1234: +┌──────┬──────┐ +│ 34 │ 12 │ +└──────┴──────┘ + LSB MSB +``` + +``` +int32 = -1 (0xFFFFFFFF): +┌──────┬──────┬──────┬──────┐ +│ FF │ FF │ FF │ FF │ +└──────┴──────┴──────┴──────┘ +``` + +### Bool + +A boolean is a single byte: `0x00` for false, `0x01` for true. + +``` +bool = true: +┌──────┐ +│ 01 │ +└──────┘ +``` + +### Bytes + +#### Fixed Length `bytes[N]` + +Contains exactly N bytes. Any byte value is valid, including null. + +``` +bytes[4] = [0x05, 0x00, 0xAF, 0xDE]: +┌──────┬──────┬──────┬──────┐ +│ 05 │ 00 │ AF │ DE │ +└──────┴──────┴──────┴──────┘ +``` + +#### Variable Length `bytes[]` + +Prefixed with a 1-byte length (max 255 bytes). + +``` +bytes[] = [0xAA, 0x00, 0xDE] (3 bytes): +┌──────┬──────┬──────┬──────┐ +│ 03 │ AA │ 00 │ DE │ +└──────┴──────┴──────┴──────┘ + len ─── data ─── +``` + +### Strings + +All strings are null-terminated. + +#### Fixed Length `string[N]` + +A null-terminated string in an N-byte buffer. Characters after the null terminator are padding (undefined values). + +``` +string[5] = "Hey": +┌──────┬──────┬──────┬──────┬──────┐ +│ H │ e │ y │ 00 │ ?? │ +└──────┴──────┴──────┴──────┴──────┘ + ─── string ─── null pad +``` + +A `string[N]` can hold at most N-1 characters (one byte reserved for null). + +#### Variable Length `string[]` + +A null-terminated string with no length prefix. The serializer reads until the first null byte. + +``` +string[] = "Hey": +┌──────┬──────┬──────┬──────┐ +│ H │ e │ y │ 00 │ +└──────┴──────┴──────┴──────┘ +``` ### Enums -Enums are a set of named constants that help make a protocol definition easier to understand. -For example: + +Enums are named integer constants with a specified underlying type. + ``` enum PinDirection: uint8 { Input = 0 @@ -103,171 +132,119 @@ enum PinDirection: uint8 { Floating = 2 } ``` -Enums are always numerical, and you must specify the underlying type when declaring the enum. -The enum above is stored using a `uint8`. -### Struct -A struct is a composite data type made of named members of other data types. -For example, this struct contains three members, each with a different type. +On the wire, enums are encoded as their underlying type: + ``` -struct MyTestStruct { - a: int32, - b: boolean, - c: uint16 -} +PinDirection.Output: +┌──────┐ +│ 01 │ +└──────┘ ``` -Structs can contain any primitive type, bytes, string, arrays, enums, bitfield structs and, other structs. -They cannot contain bitfield types (unless using an embedded bitfield). -The members of a struct are encoded in order. -There is no type or size information encoded with the struct. - -For example: - - - - -
TypeMyTestStruct
Byte1234567
Memberabc
-### Bitfield Structs -__Not for V1__ +### Structs -A bitfield struct lets you store data that is not byte aligned. -These can be used in situations where memory or network bandwidth is very tightly constrained or when communicating with register-based hardware. -I2C devices, for example. +A struct is a composite type with named members, encoded in declaration order with no padding. -Below is an example of a bitfield definition for the control register of a TMP1075 temperature sensor. ``` -enum ConversionRate: uint{2} { - Rate_27_5 = 0 # 27.5ms - Rate_55 = 1 # 55ms - Rate_110 = 2 # 110ms - Rate_220 = 3 # 220ms +struct Sensor { + id: uint8 + temperature: int16 + active: bool } +``` -bitfield struct ControlRegister { - oneShot: flag - conversionRate: ConversionRate - conversionFaults: uint{2} - outputPriority: flag - alertFunction: flag - shutdownMode: flag - reserved: unused{8} -} +``` +Sensor { id=1, temperature=256, active=true }: +┌──────┬──────┬──────┬──────┐ +│ 01 │ 00 │ 01 │ 01 │ +└──────┴──────┴──────┴──────┘ + id temperature active + (little-endian) ``` -#### Embedded bitfields -Bitfield types are not allowed in structs. -You can include bitfield structs as members of a standard struct (see limitations below). -Sometimes though, this can become verbose. -In cases where you only need a few non-byte aligned fields in an otherwise normal struct, -embedded bitfields may be useful. - -For example, this struct uses an embedded bitfield to represent the states of 8 pins in a port. -``` -struct WritePort { - portNum: uint8 - bitfield { - pin1: flag - pin2: flag - pin3: flag - pin4: flag - pin5: flag - pin6: flag - pin7: flag - pin8: flag - } +Structs can contain any type including other structs: + +``` +struct Reading { + sensor: Sensor + timestamp: uint32 } ``` -#### Limitations -Due to their flexibility, bitfield structs may have a size that's not an even multiple of 8bit. -This causes many non-byte aligned reads. -Non-byte aligned reads are size efficient but are expensive to serialize. -To use a bitfield in a normal struct (embedded or otherwise), it must be a multiple of 8 bits. -This limitation also applies when directly serializing a bitfield struct. -If your bitfield struct is not naturally a multiple of 8 bits, the `unused{n}` data type can be used to pad the data to the correct size. - ### Arrays -Arrays are created by appending square brackets to the end of a type. -`[n]` for fixed length arrays, `[]` for variable-length arrays. -There is some ambiguity when using arrays of strings or bytes. -In such cases, the array indicator is placed after the type size. -For example, a list of 5 variable-length strings would look like this `string[][5]`. -#### Fixed Length -Fixed-length arrays always contain all their elements. -For example, an array of type `uint16[3]` will be encoded like this: +#### Fixed Length `T[N]` - - - - -
Typeuint16[3]
Byte123456
Element123
+N contiguous elements with no length prefix. -#### Variable Length -Variable-length arrays are encoded with a length byte, followed by N elements. -For example: +``` +uint16[3] = [1, 2, 3]: +┌──────┬──────┬──────┬──────┬──────┬──────┐ +│ 01 │ 00 │ 02 │ 00 │ 03 │ 00 │ +└──────┴──────┴──────┴──────┴──────┴──────┘ + ─ [0] ─ ─ [1] ─ ─ [2] ─ +``` - - - - -
Typeuint16[]
Byte1234567
ElementSize12...
+#### Variable Length `T[]` +A 1-byte length prefix followed by elements (max 255 elements). -### Choice Types -__Not for V1__ +``` +uint16[] = [1, 2] (2 elements): +┌──────┬──────┬──────┬──────┬──────┐ +│ 02 │ 01 │ 00 │ 02 │ 00 │ +└──────┴──────┴──────┴──────┴──────┘ + len ─ [0] ─ ─ [1] ─ +``` -### Union Types -__Not for V1__ +#### Arrays of Variable-Length Types -## Endianness -__Not for V1__ +For arrays of strings or bytes, the array brackets follow the type size: -This will wait for a future version, but a default endianness will be assumed. -It can then be changed on a protocol, struct, or field level. +- `string[32][5]` — 5 fixed-length strings of 32 bytes each +- `string[][5]` — 5 variable-length strings -## Framing -A few different framing types will be supported. For the first version, only COBS will be implemented. +## Wire Format -### Fixed -__Not for V1__ +### Byte Order -Frames messages into fixed-length packets. -Use this when your underlying hardware can reliably deliver corruption-free fixed-length messages. +All multi-byte values are little-endian (LSB first). -This is not suitable for serial or TCP links. +### Message Frame Layout -### Length Based -__Not for V1__ +With COBS framing and CRC, a message is transmitted as: -A simple framing method where a 4-byte length prefix is added to the data before it is transmitted. -It's a lightweight protocol suitable for situations where the underlying stack provides error correction. +``` +┌──────┬──────────┬─────────────────┬───────┬──────┐ +│ COBS │ Msg ID │ Message Payload │ CRC │ 0x00 │ +│ byte │ (1 byte) │ │ │ │ +└──────┴──────────┴─────────────────┴───────┴──────┘ +``` -This would be suitable for use with TCP, or with serial protocols that have well-defined frame boundaries. -It is not suitable for Serial since you may start reading in the middle of a packet. +Example: 6-byte message with COBS and CRC16: -### COBS -COBS is a robust framing algorithm with low, fixed overhead. -It is also computationally efficient. -Combined with CRC error checking, it is robust against corruption and synchronization loss. See the [Wikipedia article](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing) for more details. +``` +┌──────┬──────┬──────────────────────────────┬──────┬──────┬──────┐ +│ COBS │ ID │ Message (6 B) │ CRC │ CRC │ 0x00 │ +└──────┴──────┴──────────────────────────────┴──────┴──────┴──────┘ + 1B 1B 6B 2B (CRC16) 1B + Total: 11 bytes on wire +``` -COBS is suitable for serial protocols. +## Protocol Block -#### Error Checking -CRC8, 16, and 32 are supported. -CRC checks can be disabled if the link is reliable. -Future versions may implement other error detection/correction schemes. +The protocol block configures framing, error checking, and message IDs. -## Protocol -The protocol wraps structs, framing, CRC, and other settings into an object the code generator can use to generate helpers. -The protocol works by sending and receiving messages. -Messages are simply structs that have been assigned a message ID. -When sent, each messages is wrapped in a frame, and proceeded by a message ID byte. -See the [layout](#layout) section below for more details. +``` +struct TestMessage { + value: uint16 +} + +struct Ack { + code: uint8 +} -The below protocol would generate an implementation that uses COBS framing, CRC8, and supported up to 256 byte messages. It supports two messages `TestMessage` and `Ack`. -```proto protocol { maxLength = 256 framing = COBS @@ -281,59 +258,66 @@ protocol { ``` ### Message IDs -To send a struct, it must be assigned a message ID. -Message IDs are used by the receiver to determine which struct to decode. -If a struct is nested inside another struct, only the root struct needs to be assigned an ID. -In the example below, we only intend to send TestMessage, so it is assigned the ID of one. -SubMessage is never sent directly, so no ID is assigned. -```proto +Each message type needs a unique ID (1–255; 0 is reserved). Only top-level messages need IDs—nested structs don't. + +``` struct SubMessage { code: uint8 - text: string[] } -TestMessage { - data: SubMessage - success: bool +struct TestMessage { + sub: SubMessage } protocol { - ... messageIds { - TestMessage = 1 + TestMessage = 1 # SubMessage doesn't need an ID } } ``` -### Max Length -The `maxLength` parameter describes the maximum size, in bytes, of a message. -This does not include the framing overhead, CRC, or message ID byte. -For example, a maxLength of 64 means that any struct with a size of up to 64 bytes can be sent. - -The length of the frame that is actually sent will be longer. -For the above example, if we sent a struct that was 64 bytes in size using COBS framing and CRC16 error detection, then the on-wire frame size would be 69 bytes. - -### Layout -Here's an example frame with an 6 byte message, COBS framing and CRC16 error checking. - - - - - - - - - - - - - - - - - - - - -
RoleFramingMessageFraming
Byte1234567891011
ElementCOBS OverheadMessage IdMessageCRC16Null Byte
\ No newline at end of file +### Options + +| Option | Values | Description | +|--------|--------|-------------| +| `maxLength` | integer | Maximum message payload size in bytes (excludes framing/CRC overhead) | +| `framing` | `COBS`, `None` | Framing algorithm | +| `crc` | `CRC8`, `CRC16`, `CRC32`, `None` | Error detection | + +#### COBS Framing + +COBS (Consistent Overhead Byte Stuffing) provides reliable framing with low, fixed overhead. Messages are delimited by null bytes. See [Wikipedia](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing) for details. + +#### CRC + +CRC error detection catches transmission errors. The CRC is computed over the message ID and payload, appended before COBS encoding. + +## Grammar Reference + +```ebnf +file = (enum | struct | protocol | comment)+ + +comment = "#" + +enum = [annotations] "enum" NAME ":" type "{" enum_value+ "}" +enum_value = [annotations] NAME "=" NUMBER + +struct = [annotations] "struct" NAME "{" struct_member+ "}" +struct_member = [annotations] NAME ":" type [array] ["=" default] + +protocol = "protocol" "{" (option | messageIds)+ "}" +option = NAME "=" VALUE +messageIds = "messageIds" "{" message_id+ "}" +message_id = NAME "=" NUMBER + +type = primitive | NAME +primitive = "int8" | "int16" | "int32" | "int64" + | "uint8" | "uint16" | "uint32" | "uint64" + | "float32" | "float64" | "bool" + | ("bytes" | "string") "[" [NUMBER] "]" + +array = "[" [NUMBER] "]" +annotations = annotation+ +annotation = "@" NAME ["(" arguments ")"] +``` diff --git a/docs/python.md b/docs/python.md index 900447a..eaff3b3 100644 --- a/docs/python.md +++ b/docs/python.md @@ -1,40 +1,48 @@ # Python The Python API is designed to be simple to use. -It requires at least Python 3.8. +It requires Python 3.13+. It is not currently compatible with MicroPython. -## Example -Code generation +## Code Generation + +Generate the runtime library and protocol code: ```sh -$ bakelite gen -l python -i proto.bakelite -o proto.py +bakelite runtime -l python # Creates bakelite_runtime/ folder +bakelite gen -l python -i proto.bakelite -o proto.py ``` -Use the generated code to implement a simple protocol. +## Example (Sync) + ```python -from proto import Protocol, TestMessage, Ack # Generated by bakelite +from proto import Protocol, TestMessage, Ack 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) + proto = Protocol(stream=port) + proto.send(TestMessage(status=True, message="Ping!")) + + for msg in proto.messages(): + if isinstance(msg, Ack): + print(f"Ack code={msg.code}") +``` + +## Example (Async) + +```python +import asyncio +from proto import Protocol, TestMessage, Ack + +async def main(): + reader, writer = await asyncio.open_connection('localhost', 8888) + proto = Protocol(stream=(reader, writer)) + + await proto.send(TestMessage(status=True, message="Ping!"), async_=True) + + async for msg in proto.messages(async_=True): + if isinstance(msg, Ack): + print(f"Ack code={msg.code}") + +asyncio.run(main()) ``` ### String Handling @@ -62,43 +70,42 @@ 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:__ -```proto -protocol { - maxLength = 256 - framing = COBS - crc = CRC8 +Pass a stream-like object to the constructor: +- For sync: `BufferedIOBase` (serial port, TCP socket, etc.) +- For async: `tuple[StreamReader, StreamWriter]` - messageIds { - ... - } -} -``` - -##### \_\_init\_\_(self, stream: BufferedIOBase) +##### \_\_init\_\_(self, stream) __arguments:__ -* __stream__ - Any stream like object that implements `read()` and `write()` functions. +* __stream__ - `BufferedIOBase` for sync, or `(StreamReader, StreamWriter)` for async. -##### 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. +##### messages(self, async_=False) -> Iterator | AsyncIterator +The primary interface for consuming messages. Returns an iterator that yields messages as they arrive. -__returns:__
-Poll() will either return a valid struct or None if there is no message to decode. +```python +# Sync +for msg in proto.messages(): + handle(msg) -##### send(self, message: Any) -> None +# Async +async for msg in proto.messages(async_=True): + await handle(msg) +``` + +##### send(self, message, async_=False) -> None | Coroutine 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. +* __async___ - If True, returns a coroutine for async sending. + +##### poll(self, async_=False) -> Struct | None | Coroutine +Reads available data and returns a message if one is complete, otherwise None. + +__arguments:__ + +* __async___ - If True, returns a coroutine for async polling. ### Struct A struct class is generated for each struct in your protocol definition. diff --git a/docs/quickstart.md b/docs/quickstart.md index 41ac387..4be6d6d 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -2,7 +2,7 @@ ## Install Bakelite -Bakelite requires Python 3.8 or above. +Bakelite requires Python 3.13+. Install it via pip. ```bash @@ -43,5 +43,6 @@ $ bakelite runtime -l cpptiny -o bakelite.h $ bakelite gen -l cpptiny -i my_proto.bakelite -o my_proto.h # Generate Python code +$ bakelite runtime -l python $ bakelite gen -l python -i my_proto.bakelite -o my_proto.py ``` \ No newline at end of file