protobin 0.0.5__tar.gz → 0.0.7__tar.gz
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.
- {protobin-0.0.5 → protobin-0.0.7}/PKG-INFO +1 -1
- {protobin-0.0.5 → protobin-0.0.7}/protobin/fields.py +49 -29
- protobin-0.0.7/protobin/protocol.py +69 -0
- {protobin-0.0.5 → protobin-0.0.7}/pyproject.toml +1 -1
- protobin-0.0.7/tests/demo.json +30 -0
- protobin-0.0.7/tests/demo.yaml +624 -0
- protobin-0.0.7/tests/tests.py +358 -0
- protobin-0.0.5/protobin/protocol.py +0 -53
- protobin-0.0.5/tests/demo.json +0 -264
- protobin-0.0.5/tests/tests.py +0 -262
- {protobin-0.0.5 → protobin-0.0.7}/.gitignore +0 -0
- {protobin-0.0.5 → protobin-0.0.7}/LICENSE.txt +0 -0
- {protobin-0.0.5 → protobin-0.0.7}/README.md +0 -0
- {protobin-0.0.5 → protobin-0.0.7}/protobin/__init__.py +0 -0
- {protobin-0.0.5 → protobin-0.0.7}/protobin/errors.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: protobin
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: Python library for encode and decode data in protobin format
|
|
5
5
|
Project-URL: Homepage, https://github.com/drmelectronic/protobin-python
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/drmelectronic/protobin-python/issues
|
|
@@ -27,9 +27,9 @@ class FieldBase:
|
|
|
27
27
|
key: Union[str, List[str]]
|
|
28
28
|
type: FieldEnum
|
|
29
29
|
|
|
30
|
-
def __init__(self, js):
|
|
30
|
+
def __init__(self, k, js):
|
|
31
|
+
self.key = k
|
|
31
32
|
self.type = js['type']
|
|
32
|
-
self.key = js.get('key')
|
|
33
33
|
self.bytes = js.get('bytes')
|
|
34
34
|
|
|
35
35
|
def decode(self, binary):
|
|
@@ -88,8 +88,8 @@ class FieldBase:
|
|
|
88
88
|
class FieldArray(FieldBase):
|
|
89
89
|
length: int
|
|
90
90
|
|
|
91
|
-
def __init__(self, js):
|
|
92
|
-
super().__init__(js)
|
|
91
|
+
def __init__(self, k, js):
|
|
92
|
+
super().__init__(k, js)
|
|
93
93
|
self.array = js['array']
|
|
94
94
|
|
|
95
95
|
def decode(self, binary):
|
|
@@ -108,7 +108,7 @@ class FieldArray(FieldBase):
|
|
|
108
108
|
length = len(val)
|
|
109
109
|
binary += length.to_bytes(1, 'big')
|
|
110
110
|
for i in range(length):
|
|
111
|
-
fields = [FIELD_MAP[f['type']](f) for f in self.array]
|
|
111
|
+
fields = [FIELD_MAP[f['type']](k, f) for k, f in self.array.items()]
|
|
112
112
|
for f in fields:
|
|
113
113
|
binary += f.encode(val[i])
|
|
114
114
|
return binary
|
|
@@ -117,7 +117,7 @@ class FieldArray(FieldBase):
|
|
|
117
117
|
lista = []
|
|
118
118
|
for i in range(self.length):
|
|
119
119
|
data = {}
|
|
120
|
-
fields = [FIELD_MAP[f['type']](f) for f in self.array]
|
|
120
|
+
fields = [FIELD_MAP[f['type']](k, f) for k, f in self.array.items()]
|
|
121
121
|
for f in fields:
|
|
122
122
|
val, binary = f.decode(binary)
|
|
123
123
|
data[f.key] = val
|
|
@@ -128,8 +128,8 @@ class FieldArray(FieldBase):
|
|
|
128
128
|
class FieldBits(FieldBase):
|
|
129
129
|
length: int
|
|
130
130
|
|
|
131
|
-
def __init__(self, js):
|
|
132
|
-
super().__init__(js)
|
|
131
|
+
def __init__(self, k, js):
|
|
132
|
+
super().__init__(k, js)
|
|
133
133
|
self.length = js.get('length', 0)
|
|
134
134
|
self.bytes = math.ceil(self.length / 8)
|
|
135
135
|
|
|
@@ -177,10 +177,21 @@ class FieldBool(FieldBase):
|
|
|
177
177
|
bytes = 1
|
|
178
178
|
|
|
179
179
|
def to_binary(self, val):
|
|
180
|
+
if val is None:
|
|
181
|
+
val = 2
|
|
182
|
+
else:
|
|
183
|
+
val = bool(val)
|
|
184
|
+
|
|
180
185
|
return val.to_bytes(1, 'big', signed=False)
|
|
181
186
|
|
|
182
187
|
def from_binary(self, binary):
|
|
183
|
-
|
|
188
|
+
val = int.from_bytes(binary, 'big', signed=False)
|
|
189
|
+
if val == 0:
|
|
190
|
+
return False
|
|
191
|
+
elif val == 1:
|
|
192
|
+
return True
|
|
193
|
+
else:
|
|
194
|
+
return None
|
|
184
195
|
|
|
185
196
|
|
|
186
197
|
class FieldChar(FieldBase):
|
|
@@ -197,8 +208,8 @@ class FieldChar(FieldBase):
|
|
|
197
208
|
|
|
198
209
|
class FieldDate(FieldBase):
|
|
199
210
|
|
|
200
|
-
def __init__(self, js):
|
|
201
|
-
super().__init__(js)
|
|
211
|
+
def __init__(self, k, js):
|
|
212
|
+
super().__init__(k, js)
|
|
202
213
|
self.bytes = 3
|
|
203
214
|
|
|
204
215
|
def to_binary(self, val: datetime.datetime | str):
|
|
@@ -219,12 +230,16 @@ class FieldDate(FieldBase):
|
|
|
219
230
|
|
|
220
231
|
class FieldDateTime(FieldBase):
|
|
221
232
|
|
|
222
|
-
def __init__(self, js):
|
|
223
|
-
super().__init__(js)
|
|
233
|
+
def __init__(self, k, js):
|
|
234
|
+
super().__init__(k, js)
|
|
224
235
|
self.bytes = 6
|
|
225
236
|
|
|
226
237
|
def to_binary(self, val: datetime.datetime | str):
|
|
227
|
-
if
|
|
238
|
+
if val is None:
|
|
239
|
+
val = datetime.datetime(2000, 1, 1)
|
|
240
|
+
elif not isinstance(val, datetime.datetime):
|
|
241
|
+
raise ValueError(f'Error en el campo "{self.key}" se espera un datetime pero se recibe {val}')
|
|
242
|
+
elif isinstance(val, str):
|
|
228
243
|
val = datetime.datetime.strptime(val, '%Y-%m-%d %H:%M:%S').date()
|
|
229
244
|
return bytes([
|
|
230
245
|
self.to_nible(val.day),
|
|
@@ -246,12 +261,11 @@ class FieldDateTime(FieldBase):
|
|
|
246
261
|
|
|
247
262
|
|
|
248
263
|
class FieldFlags(FieldBase):
|
|
249
|
-
keys: List[str]
|
|
250
264
|
|
|
251
|
-
def __init__(self, js):
|
|
252
|
-
super().__init__(js)
|
|
253
|
-
self.
|
|
254
|
-
self.length = len(self.
|
|
265
|
+
def __init__(self, k, js):
|
|
266
|
+
super().__init__(k, js)
|
|
267
|
+
self.key = self.key.split(',')
|
|
268
|
+
self.length = len(self.key)
|
|
255
269
|
self.bytes = math.ceil(self.length / 8)
|
|
256
270
|
|
|
257
271
|
def encode(self, data):
|
|
@@ -264,23 +278,23 @@ class FieldFlags(FieldBase):
|
|
|
264
278
|
for b in bits:
|
|
265
279
|
flags.append(b == '1')
|
|
266
280
|
val = {}
|
|
267
|
-
order = -len(self.
|
|
268
|
-
for k in self.
|
|
281
|
+
order = -len(self.key)
|
|
282
|
+
for k in self.key:
|
|
269
283
|
val[k] = flags[order]
|
|
270
284
|
order += 1
|
|
271
285
|
return val
|
|
272
286
|
|
|
273
287
|
def to_binary(self, data):
|
|
274
|
-
bits = '0' * (8 - (len(self.
|
|
275
|
-
for k in self.
|
|
288
|
+
bits = '0' * (8 - (len(self.key) % 8))
|
|
289
|
+
for k in self.key:
|
|
276
290
|
bits += '1' if data[k] else '0'
|
|
277
291
|
return int(bits, 2).to_bytes(self.bytes, 'big', signed=False)
|
|
278
292
|
|
|
279
293
|
|
|
280
294
|
class FieldFloat(FieldBase):
|
|
281
295
|
|
|
282
|
-
def __init__(self, js):
|
|
283
|
-
super().__init__(js)
|
|
296
|
+
def __init__(self, k, js):
|
|
297
|
+
super().__init__(k, js)
|
|
284
298
|
self.decimals = js['decimals']
|
|
285
299
|
|
|
286
300
|
def from_binary(self, binary):
|
|
@@ -327,8 +341,12 @@ class FieldTime(FieldBase):
|
|
|
327
341
|
|
|
328
342
|
bytes = 2
|
|
329
343
|
|
|
330
|
-
def to_binary(self, val: datetime.datetime | str):
|
|
331
|
-
if
|
|
344
|
+
def to_binary(self, val: datetime.datetime | datetime.time | str):
|
|
345
|
+
if val is None:
|
|
346
|
+
val = datetime.time(0, 0)
|
|
347
|
+
elif not isinstance(val, datetime.datetime) and not isinstance(val, datetime.time):
|
|
348
|
+
raise ValueError(f'Error en el campo "{self.key}" se espera un datetime o time pero se recibe {val}')
|
|
349
|
+
elif isinstance(val, str):
|
|
332
350
|
val = datetime.datetime.strptime(val, '%H:%M').date()
|
|
333
351
|
return bytes([
|
|
334
352
|
self.to_nible(val.hour),
|
|
@@ -338,13 +356,15 @@ class FieldTime(FieldBase):
|
|
|
338
356
|
def from_binary(self, binary):
|
|
339
357
|
H, binary = self.get_nible(binary)
|
|
340
358
|
M, binary = self.get_nible(binary)
|
|
359
|
+
if H == 0 and M == 0:
|
|
360
|
+
return None
|
|
341
361
|
return datetime.time(H, M)
|
|
342
362
|
|
|
343
363
|
|
|
344
364
|
class FieldTimestamp(FieldBase):
|
|
345
365
|
|
|
346
|
-
def __init__(self, js):
|
|
347
|
-
super().__init__(js)
|
|
366
|
+
def __init__(self, k, js):
|
|
367
|
+
super().__init__(k, js)
|
|
348
368
|
self.bytes = 8
|
|
349
369
|
|
|
350
370
|
def to_binary(self, val: datetime.datetime | str):
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
|
|
5
|
+
from protobin.fields import FieldBase, FIELD_MAP
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Format:
|
|
9
|
+
|
|
10
|
+
fields: [FieldBase]
|
|
11
|
+
|
|
12
|
+
def __init__(self, format: [object]):
|
|
13
|
+
self.fields = []
|
|
14
|
+
self.header = format['header']
|
|
15
|
+
for k, f in format['fields'].items():
|
|
16
|
+
self.fields.append(FIELD_MAP[f['type']](k, f))
|
|
17
|
+
|
|
18
|
+
def encode(self, data):
|
|
19
|
+
binary = self.header.encode('utf') + b'='
|
|
20
|
+
for f in self.fields:
|
|
21
|
+
binary += f.encode(data)
|
|
22
|
+
return binary
|
|
23
|
+
|
|
24
|
+
def decode(self, binary):
|
|
25
|
+
data = {}
|
|
26
|
+
for f in self.fields:
|
|
27
|
+
val, binary = f.decode(binary)
|
|
28
|
+
if isinstance(f.key, list):
|
|
29
|
+
for k in f.key:
|
|
30
|
+
data[k] = val[k]
|
|
31
|
+
else:
|
|
32
|
+
data[f.key] = val
|
|
33
|
+
return data
|
|
34
|
+
|
|
35
|
+
def to_dict(self):
|
|
36
|
+
return [f.to_dict() for f in self.fields]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Protocol:
|
|
40
|
+
|
|
41
|
+
def __init__(self, file=None, js=None):
|
|
42
|
+
self.headers = {}
|
|
43
|
+
if file:
|
|
44
|
+
with open(file, 'r') as f:
|
|
45
|
+
if 'json' in file:
|
|
46
|
+
js = json.loads(f.read())
|
|
47
|
+
self.load_format(js)
|
|
48
|
+
elif 'yaml' in file:
|
|
49
|
+
js = yaml.full_load(f.read())
|
|
50
|
+
self.load_format(js)
|
|
51
|
+
else:
|
|
52
|
+
self.load_format(js)
|
|
53
|
+
|
|
54
|
+
def load_format(self, js):
|
|
55
|
+
self.formats = {}
|
|
56
|
+
for k in js.keys():
|
|
57
|
+
self.formats[k] = Format(js[k])
|
|
58
|
+
self.headers[js[k]['header']] = k
|
|
59
|
+
|
|
60
|
+
def get_format(self, h):
|
|
61
|
+
return self.formats[self.headers[h.decode('utf')]]
|
|
62
|
+
|
|
63
|
+
def encode(self, data, format):
|
|
64
|
+
return self.formats[format].encode(data)
|
|
65
|
+
|
|
66
|
+
def decode(self, binary):
|
|
67
|
+
h, binary = binary.split(b'=')
|
|
68
|
+
format = self.get_format(h)
|
|
69
|
+
return h.decode('utf'), format.decode(binary)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"login": {
|
|
3
|
+
"header": "T",
|
|
4
|
+
"fields": {
|
|
5
|
+
"serial": {"bytes": 0, "type": "string"}
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
"report": {
|
|
9
|
+
"header": "P",
|
|
10
|
+
"fields": {
|
|
11
|
+
"positions": {"bytes": 19, "type": "array", "array": {
|
|
12
|
+
"time": {"bytes": 6, "type": "datetime"},
|
|
13
|
+
"lng": {"bytes": 4, "type": "float", "decimals": 6},
|
|
14
|
+
"lat": {"bytes": 4, "type": "float", "decimals": 6},
|
|
15
|
+
"speed": {"bytes": 1, "type": "unsigned"},
|
|
16
|
+
"mark": {"bytes": 2, "type": "unsigned"},
|
|
17
|
+
"busstop": {"bytes": 2, "type": "unsigned"}}
|
|
18
|
+
},
|
|
19
|
+
"trip": {"bytes": 4, "type": "unsigned"},
|
|
20
|
+
"route": {"bytes": 1, "type": "unsigned"},
|
|
21
|
+
"direction": {"bytes": 1, "type": "bool"},
|
|
22
|
+
"state": {"bytes": 1, "type": "char"},
|
|
23
|
+
"sales": {"bytes": 3, "type": "unsigned"},
|
|
24
|
+
"events": {"bytes": 3, "type": "array", "array": {
|
|
25
|
+
"id": {"bytes": 2, "type": "unsigned"},
|
|
26
|
+
"value": {"bytes": 1, "type": "unsigned"}}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|