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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: protobin
3
- Version: 0.0.5
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
- return bool(int.from_bytes(binary, 'big', signed=False))
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 isinstance(val, str):
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.keys = js.get('keys')
254
- self.length = len(self.keys)
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.keys)
268
- for k in self.keys:
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.keys) % 8))
275
- for k in self.keys:
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 isinstance(val, str):
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)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "protobin"
7
- version = "0.0.5"
7
+ version = "0.0.7"
8
8
  authors = [
9
9
  { name="Daniel Robles", email="drm.electronic@gmail.com" },
10
10
  ]
@@ -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
+ }