Init Commit: Moved bproto to seperate repo
This commit is contained in:
67
template/python/static/bproto_base.py
Normal file
67
template/python/static/bproto_base.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import abc
|
||||
|
||||
|
||||
class bproto_Package(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def to_bytes(self) -> bytes:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def from_bytes(self, data: bytes):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class bproto_Bitfield(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def to_bytes(self) -> bytes:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def from_bytes(self, data: bytes):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
def crc16_calc_inital(data: bytearray) -> int:
|
||||
"""CRC-16/CITT-FALSE
|
||||
from https://stackoverflow.com/questions/35205702/calculating-crc16-in-python
|
||||
|
||||
Args:
|
||||
data (bytearray): protocol hash
|
||||
|
||||
Returns:
|
||||
bytes: initals for crc16_calc_with_initial
|
||||
"""
|
||||
crc = 0xFFFF
|
||||
for i in range(0, len(data)):
|
||||
crc ^= data[i] << 8
|
||||
for j in range(0, 8):
|
||||
if (crc & 0x8000) > 0:
|
||||
crc = (crc << 1) ^ 0x1021
|
||||
else:
|
||||
crc = crc << 1
|
||||
return crc
|
||||
|
||||
|
||||
def calc_crc_sum(data: bytearray, inital: int) -> bytes:
|
||||
"""CRC-16/CITT-FALSE
|
||||
form https://stackoverflow.com/questions/35205702/calculating-crc16-in-python
|
||||
|
||||
Args:
|
||||
data (bytearray): protocol data
|
||||
inital (int): Inital calculated from protocol hash
|
||||
|
||||
Returns:
|
||||
bytes: 2 byte CRC Checksum
|
||||
"""
|
||||
crc = inital
|
||||
for i in range(0, len(data)):
|
||||
crc ^= data[i] << 8
|
||||
for j in range(0, 8):
|
||||
if (crc & 0x8000) > 0:
|
||||
crc = (crc << 1) ^ 0x1021
|
||||
else:
|
||||
crc = crc << 1
|
||||
return crc & 0xFFFF
|
||||
19
template/python/static/bproto_error.py
Normal file
19
template/python/static/bproto_error.py
Normal file
@@ -0,0 +1,19 @@
|
||||
class bproto_Error(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class bproto_PackageError(bproto_Error):
|
||||
pass
|
||||
|
||||
|
||||
class bproto_PackageErrorInvalidSize(bproto_PackageError):
|
||||
pass
|
||||
|
||||
|
||||
class bproto_PackageErrorInvalidMessageID(bproto_PackageError):
|
||||
pass
|
||||
|
||||
|
||||
class bproto_PackageErrorInvalidCRC(bproto_PackageError):
|
||||
pass
|
||||
38
template/python/template/bproto_protocol_bitfield.py.jinja2
Normal file
38
template/python/template/bproto_protocol_bitfield.py.jinja2
Normal file
@@ -0,0 +1,38 @@
|
||||
from bproto_base import bproto_Bitfield
|
||||
|
||||
{% for bitfield in bitfields %}
|
||||
class {{bitfield.name}}(bproto_Bitfield):
|
||||
BYTE_SIZE = {{bitfield.size}}
|
||||
|
||||
def __init__(self,
|
||||
{%- for bit in bitfield.bits %}
|
||||
{{bit.name}}=False,
|
||||
{%- endfor %}
|
||||
):
|
||||
{%- for bit in bitfield.bits %}
|
||||
self.{{bit.name}} = {{bit.name}}
|
||||
{%- endfor %}
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
res = 0
|
||||
{%- for bit in bitfield.bits %}
|
||||
res |= (1 << {{bit.pos}}) if self.{{bit.name}} else 0
|
||||
{%- endfor %}
|
||||
return int.to_bytes(res, self.BYTE_SIZE, '{{protocol.endian_str}}')
|
||||
|
||||
def from_bytes(self, data: bytes):
|
||||
bits = int.from_bytes(data[:self.BYTE_SIZE], '{{protocol.endian_str}}')
|
||||
{%- for bit in bitfield.bits %}
|
||||
self.{{bit.name}} = (bits & (1 << {{bit.pos}})) != 0
|
||||
{%- endfor %}
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
value_str = ", ".join([
|
||||
{%- for bit in bitfield.bits %}
|
||||
"{{bit.name}}=" + str(self.{{bit.name}}),
|
||||
{%- endfor %}
|
||||
])
|
||||
return "{{bitfield.name}}(" + value_str + ")"
|
||||
|
||||
{% endfor %}
|
||||
19
template/python/template/bproto_protocol_packets.py.jinja2
Normal file
19
template/python/template/bproto_protocol_packets.py.jinja2
Normal file
@@ -0,0 +1,19 @@
|
||||
from bproto_base import bproto_Package, calc_crc_sum
|
||||
from bproto_error import bproto_Error, bproto_PackageErrorInvalidSize, bproto_PackageErrorInvalidMessageID, bproto_PackageErrorInvalidCRC
|
||||
|
||||
from {{ import_name }}_protocol_enum import {% for e in enums %}{{e.name}}{% if not loop.last %}, {% endif %}{% endfor %}
|
||||
from {{ import_name }}_protocol_bitfield import {% for b in bitfields %}{{b.name}}{% if not loop.last %}, {% endif %}{% endfor %}
|
||||
from {{ import_name }}_protocol_message_ids import MessageIds_{{ protocol.name }}, MESSAGE_SIZE_MAP_{{ protocol.name }}
|
||||
|
||||
from typing import Annotated, List
|
||||
import struct
|
||||
|
||||
CRC_INITAL = {{ protocol.crc_initial }}
|
||||
|
||||
|
||||
{% for m in messages %}
|
||||
{%- include 'python/template/message/class.jinja2' -%}
|
||||
{%- if not loop.last %}
|
||||
|
||||
{% else %}{%- endif -%}{%- endfor %}
|
||||
{% include 'python/template/message/parse_generic.jinja2' -%}
|
||||
@@ -0,0 +1,15 @@
|
||||
import enum
|
||||
|
||||
|
||||
class MessageIds_{{ protocol.name }}(enum.Enum):
|
||||
{%- for msg in messages %}
|
||||
{{ msg.id_name }} = {{ msg.id }}
|
||||
{%- endfor %}
|
||||
|
||||
|
||||
MESSAGE_SIZE_MAP_{{ protocol.name }} = {
|
||||
{%- for msg in messages %}
|
||||
MessageIds_{{ protocol.name }}.{{ msg.id_name }}: {{ msg.size }},
|
||||
{%- endfor %}
|
||||
}
|
||||
|
||||
8
template/python/template/enum.py.jinja2
Normal file
8
template/python/template/enum.py.jinja2
Normal file
@@ -0,0 +1,8 @@
|
||||
import enum
|
||||
{% for e in enums %}
|
||||
|
||||
class {{ e.name }}(enum.Enum):
|
||||
{%- for k,v in e.consts %}
|
||||
{{ k }} = {{ v }}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
20
template/python/template/message/class.jinja2
Normal file
20
template/python/template/message/class.jinja2
Normal file
@@ -0,0 +1,20 @@
|
||||
class {{m.name}}(bproto_Package):
|
||||
ID = MessageIds_{{ protocol.name }}.{{m.id_name}}
|
||||
SIZE = {{ m.size }}
|
||||
{% for f in m.fields %}
|
||||
{{f.name}}: {{f.type}}
|
||||
{%- endfor %}
|
||||
|
||||
def __init__(self,
|
||||
{% for f in m.fields -%}
|
||||
{{f.name}}: {{f.type}} = {{f.default_value}},
|
||||
{% endfor -%}
|
||||
):
|
||||
{% for f in m.fields -%}
|
||||
self.{{f.name}} = {{f.name}}
|
||||
{% endfor -%}
|
||||
|
||||
|
||||
{% include 'python/template/message/toBytes.jinja2' %}
|
||||
{% include 'python/template/message/fromBytes.jinja2' %}
|
||||
|
||||
21
template/python/template/message/fromBytes.jinja2
Normal file
21
template/python/template/message/fromBytes.jinja2
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
def from_bytes(self, data: bytes):
|
||||
|
||||
if len(data) < self.SIZE:
|
||||
raise bproto_PackageErrorInvalidSize(f"A size of {len(data)} is to small for {{ protocol.name }} (min. {self.SIZE})")
|
||||
|
||||
msg_id = struct.unpack('{{protocol.endian_format}}B', data[:1])[0]
|
||||
crc_data = struct.unpack('{{protocol.endian_format}}H', data[-{{ protocol.crc_size }}:])[0]
|
||||
|
||||
if calc_crc_sum(data[:-{{ protocol.crc_size }}], CRC_INITAL) != crc_data:
|
||||
raise bproto_PackageErrorInvalidCRC(f"CRC {crc_data:04x} did not match calculated")
|
||||
|
||||
if msg_id != self.ID.value:
|
||||
raise bproto_PackageErrorInvalidMessageID("Message ID {msd_id} does not match {{ protocol.name }}'s id {self.ID.value}")
|
||||
|
||||
data = data[1:]
|
||||
{% for f in m.fields -%}
|
||||
self.{{ f.name }} = {{ f.from_bytes_conversion }}
|
||||
data = data[{{ f.size }}:]
|
||||
{% endfor -%}
|
||||
return self
|
||||
31
template/python/template/message/parse_generic.jinja2
Normal file
31
template/python/template/message/parse_generic.jinja2
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
def parse_package(data: bytes) -> tuple[bproto_Package, int]:
|
||||
if len(data) < 1:
|
||||
raise bproto_PackageErrorInvalidSize("Package has no data")
|
||||
|
||||
msg_id = struct.unpack('{{protocol.endian_format}}B', data[:1])[0]
|
||||
|
||||
if msg_id not in MessageIds_{{ protocol.name }}:
|
||||
raise bproto_PackageErrorInvalidMessageID(f"Message ID '{msg_id}' is invaild")
|
||||
|
||||
msg_id = MessageIds_{{ protocol.name }}(msg_id)
|
||||
expected_size = MESSAGE_SIZE_MAP_{{ protocol.name }}.get(msg_id) or 0
|
||||
|
||||
if expected_size > len(data):
|
||||
raise bproto_PackageErrorInvalidSize(f"Package is to short for '{msg_id}' ({expected_size} > {len(data)})")
|
||||
|
||||
pkg_data = data[:expected_size]
|
||||
|
||||
crc_data = struct.unpack('{{protocol.endian_format}}H', pkg_data[-2:])[0]
|
||||
|
||||
if calc_crc_sum(pkg_data[:-2], CRC_INITAL) != crc_data:
|
||||
raise bproto_PackageErrorInvalidCRC(f"CRC {crc_data:04x} did not match calculated")
|
||||
|
||||
match(msg_id):
|
||||
{%- for msg in messages %}
|
||||
case MessageIds_{{ protocol.name }}.{{ msg.id_name }}:
|
||||
return {{msg.name}}().from_bytes(pkg_data), expected_size
|
||||
{%- endfor %}
|
||||
case _:
|
||||
raise bproto_Error("Message ID could not be interpreted; this should not have happen; its a developer error! Create a issue")
|
||||
|
||||
8
template/python/template/message/toBytes.jinja2
Normal file
8
template/python/template/message/toBytes.jinja2
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
res = struct.pack("{{protocol.endian_format}}B", self.ID.value)
|
||||
{% for f in m.fields -%}
|
||||
res += {{ f.to_bytes_conversion }}
|
||||
{% endfor -%}
|
||||
res += struct.pack("{{protocol.endian_format}}H", calc_crc_sum(res, CRC_INITAL))
|
||||
return res
|
||||
Reference in New Issue
Block a user