protobin 0.0.7__tar.gz → 0.0.8__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.7 → protobin-0.0.8}/PKG-INFO +2 -1
- {protobin-0.0.7 → protobin-0.0.8}/protobin/fields.py +21 -17
- protobin-0.0.8/protobin/interfaces.py +0 -0
- {protobin-0.0.7 → protobin-0.0.8}/protobin/protocol.py +22 -15
- {protobin-0.0.7 → protobin-0.0.8}/pyproject.toml +4 -1
- {protobin-0.0.7 → protobin-0.0.8}/tests/demo.json +18 -5
- {protobin-0.0.7 → protobin-0.0.8}/tests/tests.py +29 -12
- {protobin-0.0.7 → protobin-0.0.8}/.gitignore +0 -0
- {protobin-0.0.7 → protobin-0.0.8}/LICENSE.txt +0 -0
- {protobin-0.0.7 → protobin-0.0.8}/README.md +0 -0
- {protobin-0.0.7 → protobin-0.0.8}/protobin/__init__.py +0 -0
- {protobin-0.0.7 → protobin-0.0.8}/protobin/errors.py +0 -0
- {protobin-0.0.7 → protobin-0.0.8}/tests/demo.yaml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: protobin
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
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
|
|
@@ -33,6 +33,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
33
33
|
Classifier: Operating System :: OS Independent
|
|
34
34
|
Classifier: Programming Language :: Python :: 3
|
|
35
35
|
Requires-Python: >=3.9
|
|
36
|
+
Requires-Dist: pyyaml~=6.0
|
|
36
37
|
Description-Content-Type: text/markdown
|
|
37
38
|
|
|
38
39
|
# protobin
|
|
@@ -13,6 +13,7 @@ class FieldEnum(enum.StrEnum):
|
|
|
13
13
|
CHAR = 'char'
|
|
14
14
|
DATE = 'date'
|
|
15
15
|
DATETIME = 'datetime'
|
|
16
|
+
ID = 'id'
|
|
16
17
|
FLAGS = 'flags'
|
|
17
18
|
FLOAT = 'float'
|
|
18
19
|
SIGNED = 'signed'
|
|
@@ -77,12 +78,6 @@ class FieldBase:
|
|
|
77
78
|
return binary[:self.bytes], binary[self.bytes:]
|
|
78
79
|
return binary[1:binary[0] + 1], binary[binary[0] + 1:]
|
|
79
80
|
|
|
80
|
-
def to_dict(self):
|
|
81
|
-
return {
|
|
82
|
-
'key': self.key,
|
|
83
|
-
'bytes': self.bytes,
|
|
84
|
-
'type': self.type
|
|
85
|
-
}
|
|
86
81
|
|
|
87
82
|
|
|
88
83
|
class FieldArray(FieldBase):
|
|
@@ -98,11 +93,6 @@ class FieldArray(FieldBase):
|
|
|
98
93
|
val, binary = self.from_binary(binary)
|
|
99
94
|
return val, binary
|
|
100
95
|
|
|
101
|
-
def to_dict(self):
|
|
102
|
-
d = super().to_dict()
|
|
103
|
-
d['array'] = self.array
|
|
104
|
-
return d
|
|
105
|
-
|
|
106
96
|
def to_binary(self, val):
|
|
107
97
|
binary = b''
|
|
108
98
|
length = len(val)
|
|
@@ -304,10 +294,23 @@ class FieldFloat(FieldBase):
|
|
|
304
294
|
def to_binary(self, val):
|
|
305
295
|
return int(round(val * (10 ** self.decimals))).to_bytes(self.bytes, 'big', signed=True)
|
|
306
296
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
297
|
+
|
|
298
|
+
class FieldId(FieldBase):
|
|
299
|
+
|
|
300
|
+
def to_binary(self, val):
|
|
301
|
+
if val is None:
|
|
302
|
+
val = 0
|
|
303
|
+
max_value = 256 ** self.bytes - 1
|
|
304
|
+
val = min(val, max_value)
|
|
305
|
+
if val < 0:
|
|
306
|
+
raise ValueError('Negative values not allowed')
|
|
307
|
+
return int(val).to_bytes(self.bytes, 'big', signed=False)
|
|
308
|
+
|
|
309
|
+
def from_binary(self, binary):
|
|
310
|
+
val = int.from_bytes(binary[:self.bytes], 'big', signed=False)
|
|
311
|
+
if val == 0:
|
|
312
|
+
val = None
|
|
313
|
+
return val
|
|
311
314
|
|
|
312
315
|
|
|
313
316
|
class FieldSigned(FieldBase):
|
|
@@ -380,11 +383,11 @@ class FieldUnsigned(FieldBase):
|
|
|
380
383
|
|
|
381
384
|
def to_binary(self, val):
|
|
382
385
|
if val is None:
|
|
383
|
-
|
|
386
|
+
raise ValueError(f'Error en el campo "{self.key}" no se permite None')
|
|
384
387
|
max_value = 256 ** self.bytes - 1
|
|
385
388
|
val = min(val, max_value)
|
|
386
389
|
if val < 0:
|
|
387
|
-
raise ValueError('
|
|
390
|
+
raise ValueError(f'Error en el campo "{self.key}" se espera enteros positivos y se recibió {val}')
|
|
388
391
|
return int(val).to_bytes(self.bytes, 'big', signed=False)
|
|
389
392
|
|
|
390
393
|
def from_binary(self, binary):
|
|
@@ -400,6 +403,7 @@ FIELD_MAP = {
|
|
|
400
403
|
'datetime': FieldDateTime,
|
|
401
404
|
'flags': FieldFlags,
|
|
402
405
|
'float': FieldFloat,
|
|
406
|
+
'id': FieldId,
|
|
403
407
|
'signed': FieldSigned,
|
|
404
408
|
'string': FieldString,
|
|
405
409
|
'time': FieldTime,
|
|
File without changes
|
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
import json
|
|
2
|
-
|
|
3
2
|
import yaml
|
|
4
|
-
|
|
5
3
|
from protobin.fields import FieldBase, FIELD_MAP
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
class Format:
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
input_fields: [FieldBase]
|
|
9
|
+
output_fields: [FieldBase]
|
|
11
10
|
|
|
12
|
-
def __init__(self, format:
|
|
13
|
-
self.
|
|
11
|
+
def __init__(self, name: str, format, server: bool):
|
|
12
|
+
self.name = name
|
|
13
|
+
self.input_fields = []
|
|
14
|
+
self.output_fields = []
|
|
14
15
|
self.header = format['header']
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
if server is None:
|
|
17
|
+
input_mode = 'fields'
|
|
18
|
+
output_mode = 'fields'
|
|
19
|
+
else:
|
|
20
|
+
input_mode = 'device' if server else 'server'
|
|
21
|
+
output_mode = 'server' if server else 'device'
|
|
22
|
+
for k, f in format[input_mode].items():
|
|
23
|
+
self.input_fields.append(FIELD_MAP[f['type']](k, f))
|
|
24
|
+
for k, f in format[output_mode].items():
|
|
25
|
+
self.output_fields.append(FIELD_MAP[f['type']](k, f))
|
|
17
26
|
|
|
18
27
|
def encode(self, data):
|
|
19
28
|
binary = self.header.encode('utf') + b'='
|
|
20
|
-
for f in self.
|
|
29
|
+
for f in self.output_fields:
|
|
21
30
|
binary += f.encode(data)
|
|
22
31
|
return binary
|
|
23
32
|
|
|
24
33
|
def decode(self, binary):
|
|
25
34
|
data = {}
|
|
26
|
-
for f in self.
|
|
35
|
+
for f in self.input_fields:
|
|
27
36
|
val, binary = f.decode(binary)
|
|
28
37
|
if isinstance(f.key, list):
|
|
29
38
|
for k in f.key:
|
|
@@ -32,13 +41,11 @@ class Format:
|
|
|
32
41
|
data[f.key] = val
|
|
33
42
|
return data
|
|
34
43
|
|
|
35
|
-
def to_dict(self):
|
|
36
|
-
return [f.to_dict() for f in self.fields]
|
|
37
|
-
|
|
38
44
|
|
|
39
45
|
class Protocol:
|
|
40
46
|
|
|
41
|
-
def __init__(self, file=None, js=None):
|
|
47
|
+
def __init__(self, server: bool = None, file=None, js=None):
|
|
48
|
+
self.server = server
|
|
42
49
|
self.headers = {}
|
|
43
50
|
if file:
|
|
44
51
|
with open(file, 'r') as f:
|
|
@@ -54,7 +61,7 @@ class Protocol:
|
|
|
54
61
|
def load_format(self, js):
|
|
55
62
|
self.formats = {}
|
|
56
63
|
for k in js.keys():
|
|
57
|
-
self.formats[k] = Format(js[k])
|
|
64
|
+
self.formats[k] = Format(name=k, format=js[k], server=self.server)
|
|
58
65
|
self.headers[js[k]['header']] = k
|
|
59
66
|
|
|
60
67
|
def get_format(self, h):
|
|
@@ -66,4 +73,4 @@ class Protocol:
|
|
|
66
73
|
def decode(self, binary):
|
|
67
74
|
h, binary = binary.split(b'=')
|
|
68
75
|
format = self.get_format(h)
|
|
69
|
-
return
|
|
76
|
+
return format.name, format.decode(binary)
|
|
@@ -4,10 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "protobin"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.8"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Daniel Robles", email="drm.electronic@gmail.com" },
|
|
10
10
|
]
|
|
11
|
+
dependencies = [
|
|
12
|
+
'PyYAML ~= 6.0'
|
|
13
|
+
]
|
|
11
14
|
description = "Python library for encode and decode data in protobin format"
|
|
12
15
|
readme = "README.md"
|
|
13
16
|
license = { file="LICENSE.txt" }
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"login": {
|
|
3
3
|
"header": "T",
|
|
4
|
-
"
|
|
4
|
+
"device": {
|
|
5
5
|
"serial": {"bytes": 0, "type": "string"}
|
|
6
|
-
}
|
|
6
|
+
},
|
|
7
|
+
"server": {
|
|
8
|
+
"id": {"bytes": 2, "type": "id"},
|
|
9
|
+
"bus_number": {"bytes": 2, "type": "id"},
|
|
10
|
+
"route": {"bytes": 1, "type": "id"},
|
|
11
|
+
"direction": {"bytes": 1, "type": "bool"},
|
|
12
|
+
"geofence": {"bytes": 2, "type": "id"},
|
|
13
|
+
"state": {"bytes": 1, "type": "char"},
|
|
14
|
+
"trip": {"bytes": 4, "type": "id"},
|
|
15
|
+
"driver": {"bytes": 4, "type": "id"}
|
|
16
|
+
}
|
|
7
17
|
},
|
|
8
18
|
"report": {
|
|
9
19
|
"header": "P",
|
|
10
|
-
"
|
|
20
|
+
"device": {
|
|
11
21
|
"positions": {"bytes": 19, "type": "array", "array": {
|
|
12
22
|
"time": {"bytes": 6, "type": "datetime"},
|
|
13
23
|
"lng": {"bytes": 4, "type": "float", "decimals": 6},
|
|
14
24
|
"lat": {"bytes": 4, "type": "float", "decimals": 6},
|
|
15
25
|
"speed": {"bytes": 1, "type": "unsigned"},
|
|
16
|
-
"mark": {"bytes": 2, "type": "
|
|
17
|
-
"busstop": {"bytes": 2, "type": "
|
|
26
|
+
"mark": {"bytes": 2, "type": "id"},
|
|
27
|
+
"busstop": {"bytes": 2, "type": "id"}}
|
|
18
28
|
},
|
|
19
29
|
"trip": {"bytes": 4, "type": "unsigned"},
|
|
20
30
|
"route": {"bytes": 1, "type": "unsigned"},
|
|
@@ -25,6 +35,9 @@
|
|
|
25
35
|
"id": {"bytes": 2, "type": "unsigned"},
|
|
26
36
|
"value": {"bytes": 1, "type": "unsigned"}}
|
|
27
37
|
}
|
|
38
|
+
},
|
|
39
|
+
"server": {
|
|
40
|
+
"positions": {"bytes": 1, "type": "unsigned"}
|
|
28
41
|
}
|
|
29
42
|
}
|
|
30
43
|
}
|
|
@@ -12,7 +12,7 @@ DATA = {
|
|
|
12
12
|
'lng': -11.485014,
|
|
13
13
|
'lat': -77.621845,
|
|
14
14
|
'speed': 20,
|
|
15
|
-
'mark':
|
|
15
|
+
'mark': None,
|
|
16
16
|
'busstop': 16
|
|
17
17
|
},
|
|
18
18
|
{
|
|
@@ -20,7 +20,7 @@ DATA = {
|
|
|
20
20
|
'lng': -11.485414,
|
|
21
21
|
'lat': -77.622245,
|
|
22
22
|
'speed': 25,
|
|
23
|
-
'mark':
|
|
23
|
+
'mark': None,
|
|
24
24
|
'busstop': 16
|
|
25
25
|
},
|
|
26
26
|
{
|
|
@@ -28,7 +28,7 @@ DATA = {
|
|
|
28
28
|
'lng': -11.485514,
|
|
29
29
|
'lat': -77.621945,
|
|
30
30
|
'speed': 18,
|
|
31
|
-
'mark':
|
|
31
|
+
'mark': None,
|
|
32
32
|
'busstop': 16
|
|
33
33
|
},
|
|
34
34
|
{
|
|
@@ -228,6 +228,7 @@ class BasicTest(unittest.TestCase):
|
|
|
228
228
|
def test_float(self):
|
|
229
229
|
data = {'test': -11.659812}
|
|
230
230
|
binary = self.protocol.encode(data, 'float')
|
|
231
|
+
print(binary)
|
|
231
232
|
h, recv = self.protocol.decode(binary)
|
|
232
233
|
self.assertEqual(data, recv)
|
|
233
234
|
|
|
@@ -263,11 +264,12 @@ class BasicTest(unittest.TestCase):
|
|
|
263
264
|
class AdvancedTest(unittest.TestCase):
|
|
264
265
|
|
|
265
266
|
def test_file_json(self):
|
|
266
|
-
|
|
267
|
-
binary =
|
|
268
|
-
|
|
267
|
+
device = Protocol(file='demo.json', server=False)
|
|
268
|
+
binary = device.encode(DATA, 'report')
|
|
269
|
+
server = Protocol(file='demo.json', server=True)
|
|
270
|
+
name, recv = server.decode(binary)
|
|
269
271
|
self.assertEqual(DATA, recv)
|
|
270
|
-
self.assertEqual(
|
|
272
|
+
self.assertEqual(name, 'report')
|
|
271
273
|
|
|
272
274
|
# def test_file_yaml(self):
|
|
273
275
|
# protocol = Protocol(file='demo.yaml')
|
|
@@ -347,12 +349,27 @@ class AdvancedTest(unittest.TestCase):
|
|
|
347
349
|
js = json.dumps(ym, indent=2)
|
|
348
350
|
|
|
349
351
|
def test_convert_json_to_yaml(self):
|
|
350
|
-
protocol = Protocol(file='demo.json')
|
|
352
|
+
protocol = Protocol(file='demo.json', server=True)
|
|
351
353
|
|
|
352
354
|
def test_login(self):
|
|
353
|
-
|
|
355
|
+
device = Protocol(file='demo.json', server=False)
|
|
356
|
+
server = Protocol(file='demo.json', server=True)
|
|
354
357
|
data = {'serial': '15984316545'}
|
|
355
|
-
binary =
|
|
356
|
-
header, recv =
|
|
358
|
+
binary = device.encode(data, 'login')
|
|
359
|
+
header, recv = server.decode(binary)
|
|
360
|
+
self.assertEqual(header, 'login')
|
|
361
|
+
self.assertEqual(data, recv)
|
|
362
|
+
data = {
|
|
363
|
+
'id': 43,
|
|
364
|
+
'bus_number': 18,
|
|
365
|
+
'route': 2,
|
|
366
|
+
'direction': True,
|
|
367
|
+
'geofence': None,
|
|
368
|
+
'state': 'R',
|
|
369
|
+
'trip': 5681121,
|
|
370
|
+
'driver': 1432
|
|
371
|
+
}
|
|
372
|
+
binary = server.encode(data, 'login')
|
|
373
|
+
header, recv = device.decode(binary)
|
|
374
|
+
self.assertEqual(header, 'login')
|
|
357
375
|
self.assertEqual(data, recv)
|
|
358
|
-
self.assertEqual(header, 'T')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|