kkprotocol 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kkprotocol/__init__.py +105 -0
- kkprotocol/builder.py +1005 -0
- kkprotocol/codec.py +804 -0
- kkprotocol/compiler.py +182 -0
- kkprotocol/contrib/__init__.py +8 -0
- kkprotocol/contrib/artnet.py +55 -0
- kkprotocol/contrib/bacnet_mstp.py +110 -0
- kkprotocol/contrib/cctalk.py +53 -0
- kkprotocol/contrib/cjt188.py +57 -0
- kkprotocol/contrib/dlt645.py +81 -0
- kkprotocol/contrib/hdlc.py +47 -0
- kkprotocol/contrib/mbus.py +61 -0
- kkprotocol/contrib/modbus_ascii.py +59 -0
- kkprotocol/contrib/nmea.py +59 -0
- kkprotocol/contrib/slip.py +38 -0
- kkprotocol/contrib/xmodem.py +74 -0
- kkprotocol/crc.py +103 -0
- kkprotocol/errors.py +48 -0
- kkprotocol/example_device.py +186 -0
- kkprotocol/example_modbus_rtu.py +176 -0
- kkprotocol/example_modbus_rtu_multi.py +391 -0
- kkprotocol/fields.py +167 -0
- kkprotocol/frame.py +114 -0
- kkprotocol/frame_codec.py +50 -0
- kkprotocol/framing.py +190 -0
- kkprotocol/length.py +151 -0
- kkprotocol/pack.py +222 -0
- kkprotocol/py.typed +0 -0
- kkprotocol/spec.py +494 -0
- kkprotocol-0.1.0.dist-info/METADATA +1015 -0
- kkprotocol-0.1.0.dist-info/RECORD +34 -0
- kkprotocol-0.1.0.dist-info/WHEEL +5 -0
- kkprotocol-0.1.0.dist-info/licenses/LICENSE +21 -0
- kkprotocol-0.1.0.dist-info/top_level.txt +1 -0
kkprotocol/pack.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""把「一串有类型的值」直接拼成字节,不必先声明字段名。
|
|
3
|
+
|
|
4
|
+
``Field`` 回答的是「字段长什么样」,所以需要 key;本模块反过来——你已经知道值,
|
|
5
|
+
只想把它编码成字节塞进载荷::
|
|
6
|
+
|
|
7
|
+
from kkprotocol import Data, pack
|
|
8
|
+
|
|
9
|
+
body = pack(
|
|
10
|
+
Data.u8(0x00),
|
|
11
|
+
Data.u8(0x00),
|
|
12
|
+
Data.bool(True),
|
|
13
|
+
Data.str("test", count=24, fill=b"\\x00"),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
proto.build_raw(0x03, body, type=1, seq=1) # 也可以直接把列表传进去
|
|
17
|
+
|
|
18
|
+
``Data`` 的每个方法返回一个已编码的小对象(``_Encoded``),``pack()`` 把它们按顺序
|
|
19
|
+
拼成 ``bytes``。多字节整数默认大端,与 :class:`Field` 一致。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import struct
|
|
25
|
+
from typing import Any, Optional, Union
|
|
26
|
+
|
|
27
|
+
__all__ = ["Data", "pack"]
|
|
28
|
+
|
|
29
|
+
BytesLike = Union[bytes, bytearray, memoryview]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _Encoded:
|
|
33
|
+
"""一个已经编码好的值:只保留最终字节。"""
|
|
34
|
+
|
|
35
|
+
__slots__ = ("data",)
|
|
36
|
+
|
|
37
|
+
def __init__(self, data: BytesLike) -> None:
|
|
38
|
+
self.data = bytes(data)
|
|
39
|
+
|
|
40
|
+
# 方便直接当 bytes 用
|
|
41
|
+
def __bytes__(self) -> bytes:
|
|
42
|
+
return self.data
|
|
43
|
+
|
|
44
|
+
def __len__(self) -> int:
|
|
45
|
+
return len(self.data)
|
|
46
|
+
|
|
47
|
+
def __add__(self, other: Any) -> "_Encoded":
|
|
48
|
+
return _Encoded(self.data + _as_bytes(other))
|
|
49
|
+
|
|
50
|
+
def __radd__(self, other: Any) -> "_Encoded":
|
|
51
|
+
return _Encoded(_as_bytes(other) + self.data)
|
|
52
|
+
|
|
53
|
+
def __eq__(self, other: Any) -> bool:
|
|
54
|
+
if isinstance(other, _Encoded):
|
|
55
|
+
return self.data == other.data
|
|
56
|
+
if isinstance(other, (bytes, bytearray, memoryview)):
|
|
57
|
+
return self.data == bytes(other)
|
|
58
|
+
return NotImplemented
|
|
59
|
+
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
return "Data({})".format(self.data.hex(" "))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _as_bytes(item: Any) -> bytes:
|
|
65
|
+
"""把一条载荷项转成字节。"""
|
|
66
|
+
if isinstance(item, _Encoded):
|
|
67
|
+
return item.data
|
|
68
|
+
if isinstance(item, (bytes, bytearray, memoryview)):
|
|
69
|
+
return bytes(item)
|
|
70
|
+
raise TypeError(
|
|
71
|
+
"载荷项必须是 Data.xxx(...) 或 bytes,实际 {}".format(type(item).__name__)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _fill_byte(fill: Union[int, bytes, bytearray, str]) -> bytes:
|
|
76
|
+
"""把 fill 归一成 1 字节。"""
|
|
77
|
+
if isinstance(fill, int):
|
|
78
|
+
if not 0 <= fill <= 0xFF:
|
|
79
|
+
raise ValueError("fill 必须在 0..255,实际 {}".format(fill))
|
|
80
|
+
return bytes([fill])
|
|
81
|
+
if isinstance(fill, str):
|
|
82
|
+
raw = fill.encode("latin-1")
|
|
83
|
+
else:
|
|
84
|
+
raw = bytes(fill)
|
|
85
|
+
if len(raw) != 1:
|
|
86
|
+
raise ValueError("fill 必须是 1 个字节,实际 {!r}".format(fill))
|
|
87
|
+
return raw
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Data:
|
|
91
|
+
"""值构造器:给值,拿到字节。与 :class:`Field` 的相对应工厂一一对应。
|
|
92
|
+
|
|
93
|
+
用法::
|
|
94
|
+
|
|
95
|
+
Data.u8(0x12) # 1 字节
|
|
96
|
+
Data.u16(0x1234) # 2 字节,大端
|
|
97
|
+
Data.u16(0x1234, little_endian=True)
|
|
98
|
+
Data.bool(True) # b"\\x01"
|
|
99
|
+
Data.str("test", count=24, fill=b"\\x00") # 定长 24,不足补 \\x00
|
|
100
|
+
Data.raw(b"\\x01\\x02") # 原样字节
|
|
101
|
+
Data.zeros(4) # 4 个 \\x00
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------------ 整数
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _pack(fmt: str, value: Any, label: str) -> _Encoded:
|
|
107
|
+
try:
|
|
108
|
+
return _Encoded(struct.pack(fmt, value))
|
|
109
|
+
except struct.error as exc:
|
|
110
|
+
raise ValueError("{}: {!r} 无法按 {} 打包({})".format(label, value, fmt, exc))
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def u8(cls, value: int) -> _Encoded:
|
|
114
|
+
"""1 字节无符号整数。"""
|
|
115
|
+
return cls._pack("B", value, "Data.u8")
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def i8(cls, value: int) -> _Encoded:
|
|
119
|
+
"""1 字节有符号整数。"""
|
|
120
|
+
return cls._pack("b", value, "Data.i8")
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def u16(cls, value: int, *, little_endian: bool = False) -> _Encoded:
|
|
124
|
+
"""2 字节无符号整数,默认大端。"""
|
|
125
|
+
return cls._pack("<H" if little_endian else "!H", value, "Data.u16")
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def i16(cls, value: int, *, little_endian: bool = False) -> _Encoded:
|
|
129
|
+
"""2 字节有符号整数,默认大端。"""
|
|
130
|
+
return cls._pack("<h" if little_endian else "!h", value, "Data.i16")
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def u32(cls, value: int, *, little_endian: bool = False) -> _Encoded:
|
|
134
|
+
"""4 字节无符号整数,默认大端。"""
|
|
135
|
+
return cls._pack("<I" if little_endian else "!I", value, "Data.u32")
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def i32(cls, value: int, *, little_endian: bool = False) -> _Encoded:
|
|
139
|
+
"""4 字节有符号整数,默认大端。"""
|
|
140
|
+
return cls._pack("<i" if little_endian else "!i", value, "Data.i32")
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def u64(cls, value: int, *, little_endian: bool = False) -> _Encoded:
|
|
144
|
+
"""8 字节无符号整数,默认大端。"""
|
|
145
|
+
return cls._pack("<Q" if little_endian else "!Q", value, "Data.u64")
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def i64(cls, value: int, *, little_endian: bool = False) -> _Encoded:
|
|
149
|
+
"""8 字节有符号整数,默认大端。"""
|
|
150
|
+
return cls._pack("<q" if little_endian else "!q", value, "Data.i64")
|
|
151
|
+
|
|
152
|
+
# ------------------------------------------------------------------ 浮点
|
|
153
|
+
@classmethod
|
|
154
|
+
def f32(cls, value: float, *, little_endian: bool = False) -> _Encoded:
|
|
155
|
+
"""4 字节单精度浮点,默认大端。"""
|
|
156
|
+
return cls._pack("<f" if little_endian else "!f", value, "Data.f32")
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def f64(cls, value: float, *, little_endian: bool = False) -> _Encoded:
|
|
160
|
+
"""8 字节双精度浮点,默认大端。"""
|
|
161
|
+
return cls._pack("<d" if little_endian else "!d", value, "Data.f64")
|
|
162
|
+
|
|
163
|
+
# ------------------------------------------------------------------ 其它
|
|
164
|
+
@classmethod
|
|
165
|
+
def bool(cls, value: Any) -> _Encoded:
|
|
166
|
+
"""1 字节布尔(真 = 0x01,假 = 0x00)。"""
|
|
167
|
+
return _Encoded(b"\x01" if value else b"\x00")
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def raw(cls, data: BytesLike) -> _Encoded:
|
|
171
|
+
"""原样字节,不做任何解释。"""
|
|
172
|
+
return _Encoded(data)
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def zeros(cls, count: int) -> _Encoded:
|
|
176
|
+
"""``count`` 个 0x00。"""
|
|
177
|
+
if count < 0:
|
|
178
|
+
raise ValueError("count 不能为负,实际 {}".format(count))
|
|
179
|
+
return _Encoded(b"\x00" * count)
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def str(
|
|
183
|
+
cls,
|
|
184
|
+
text: str,
|
|
185
|
+
*,
|
|
186
|
+
count: Optional[int] = None,
|
|
187
|
+
fill: Union[int, bytes, bytearray, str] = b"\x00",
|
|
188
|
+
encoding: str = "utf-8",
|
|
189
|
+
) -> _Encoded:
|
|
190
|
+
"""字符串。
|
|
191
|
+
|
|
192
|
+
``count`` 给定时按定长处理:超长截断,不足用 ``fill`` 补齐(C 字符串风格)。
|
|
193
|
+
不给 ``count`` 就按实际长度输出。
|
|
194
|
+
"""
|
|
195
|
+
raw = text.encode(encoding)
|
|
196
|
+
if count is None:
|
|
197
|
+
return _Encoded(raw)
|
|
198
|
+
if count < 0:
|
|
199
|
+
raise ValueError("count 不能为负,实际 {}".format(count))
|
|
200
|
+
if len(raw) >= count:
|
|
201
|
+
return _Encoded(raw[:count])
|
|
202
|
+
return _Encoded(raw + _fill_byte(fill) * (count - len(raw)))
|
|
203
|
+
|
|
204
|
+
# ``Data.char`` 是 ``Data.str`` 的别名,方便从「字符数组」的写法迁移过来
|
|
205
|
+
char = str
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def pack(*items: Any) -> bytes:
|
|
209
|
+
"""把若干 ``Data.xxx(...)``(或 bytes)按顺序拼成 ``bytes``。
|
|
210
|
+
|
|
211
|
+
也接受单个列表/元组,便于写成 ``pack([Data.u8(1), Data.u8(2)])``::
|
|
212
|
+
|
|
213
|
+
pack(Data.u8(0), Data.u8(0), Data.bool(True), Data.str("test", count=24))
|
|
214
|
+
pack([Data.u8(0), Data.u8(0), Data.bool(True)])
|
|
215
|
+
b"\\x00" + pack(Data.u8(1)) # 也能和 bytes 直接相加
|
|
216
|
+
"""
|
|
217
|
+
if len(items) == 1 and isinstance(items[0], (list, tuple)):
|
|
218
|
+
items = tuple(items[0])
|
|
219
|
+
out = bytearray()
|
|
220
|
+
for item in items:
|
|
221
|
+
out += _as_bytes(item)
|
|
222
|
+
return bytes(out)
|
kkprotocol/py.typed
ADDED
|
File without changes
|