xaal.lib 0.7.6__py3-none-any.whl → 0.7.8__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.
xaal/lib/helpers.py CHANGED
@@ -1,4 +1,4 @@
1
- """
1
+ """
2
2
  This file contains some helpers functions. This functions aren't used in the lib itself
3
3
  but can be usefull for xaal packages developpers
4
4
  """
@@ -7,42 +7,50 @@ import logging
7
7
  import logging.handlers
8
8
  import os
9
9
  import time
10
+ from typing import Any, Optional
11
+
10
12
  import coloredlogs
11
13
  from decorator import decorator
12
14
 
13
- from . import config
15
+ from .config import config
16
+
14
17
 
15
18
  def singleton(class_):
16
- instances = {}
17
- def getinstance(*args, **kwargs):
18
- if class_ not in instances:
19
- instances[class_] = class_(*args, **kwargs)
20
- return instances[class_]
21
- return getinstance
19
+ instances = {}
20
+
21
+ def getinstance(*args, **kwargs):
22
+ if class_ not in instances:
23
+ instances[class_] = class_(*args, **kwargs)
24
+ return instances[class_]
25
+
26
+ return getinstance
22
27
 
23
28
 
24
29
  @decorator
25
- def timeit(method,*args,**kwargs):
30
+ def timeit(method, *args, **kwargs):
26
31
  logger = logging.getLogger(__name__)
27
32
  ts = time.time()
28
33
  result = method(*args, **kwargs)
29
34
  te = time.time()
30
- logger.debug('%r (%r, %r) %2.6f sec' % (method.__name__, args, kwargs, te-ts))
35
+ logger.debug("%r (%r, %r) %2.6f sec" % (method.__name__, args, kwargs, te - ts))
31
36
  return result
32
37
 
33
- def set_console_title(value):
38
+
39
+ def set_console_title(value: str):
34
40
  # set xterm title
35
- print("\x1B]0;xAAL => %s\x07" % value,end='\r')
41
+ print("\x1b]0;xAAL => %s\x07" % value, end="\r")
36
42
 
37
- def setup_console_logger(level=config.log_level):
38
- fmt = '%(asctime)s %(name)-25s %(funcName)-18s %(levelname)-8s %(message)s'
39
- #fmt = '[%(name)s] %(funcName)s %(levelname)s: %(message)s'
40
- coloredlogs.install(level=level,fmt=fmt)
41
43
 
42
- def setup_file_logger(name,level=config.log_level,filename = None):
43
- filename = filename or os.path.join(config.log_path,'%s.log' % name)
44
- formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
45
- handler = logging.handlers.RotatingFileHandler(filename, 'a', 10000, 1, 'utf8')
44
+ def setup_console_logger(level: str = config.log_level):
45
+ fmt = "%(asctime)s %(name)-25s %(funcName)-18s %(levelname)-8s %(message)s"
46
+ # fmt = '[%(name)s] %(funcName)s %(levelname)s: %(message)s'
47
+ coloredlogs.install(level=level, fmt=fmt)
48
+
49
+
50
+ def setup_file_logger(name: str, level: str = config.log_level, filename: Optional[str] = None):
51
+ filename = filename or os.path.join(config.log_path, "%s.log" % name)
52
+ formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(name)s - %(message)s")
53
+ handler = logging.handlers.RotatingFileHandler(filename, "a", 10000, 1, "utf8")
46
54
  handler.setLevel(level)
47
55
  handler.setFormatter(formatter)
48
56
  # register the new handler
@@ -50,26 +58,28 @@ def setup_file_logger(name,level=config.log_level,filename = None):
50
58
  logger.root.addHandler(handler)
51
59
  logger.root.setLevel('DEBUG')
52
60
 
61
+
53
62
  # ---------------------------------------------------------------------------
54
- # TBD: We should merge this stuffs, and add support for default config file
55
- # and commnand line parsing.
56
- #
63
+ # TBD: We should merge this stuffs, and add support for default config file
64
+ # and commnand line parsing.
65
+ #
57
66
  # Default arguments console_log and file_log are (and should) never be used.
58
67
  # ---------------------------------------------------------------------------
59
- def run_package(pkg_name,pkg_setup,console_log = True,file_log=False):
68
+ def run_package(pkg_name: str, pkg_setup: Any, console_log: bool = True, file_log: bool = False):
60
69
  if console_log:
61
70
  set_console_title(pkg_name)
62
71
  setup_console_logger()
63
72
  if file_log:
64
73
  setup_file_logger(pkg_name)
65
74
  logger = logging.getLogger(pkg_name)
66
- logger.info('starting xaal package: %s'% pkg_name )
75
+ logger.info("starting xaal package: %s" % pkg_name)
67
76
 
68
77
  from .engine import Engine
78
+
69
79
  eng = Engine()
70
80
  result = pkg_setup(eng)
71
81
 
72
- if result != True:
82
+ if result is not True:
73
83
  logger.critical("something goes wrong with package: %s" % pkg_name)
74
84
  try:
75
85
  eng.run()
@@ -77,4 +87,5 @@ def run_package(pkg_name,pkg_setup,console_log = True,file_log=False):
77
87
  eng.shutdown()
78
88
  logger.info("exit")
79
89
 
80
- __all__ = ['singleton','timeit','set_console_title','setup_console_logger','setup_file_logger','run_package']
90
+
91
+ __all__ = ['singleton', 'timeit', 'set_console_title', 'setup_console_logger', 'setup_file_logger', 'run_package']
xaal/lib/messages.py CHANGED
@@ -18,26 +18,139 @@
18
18
  # along with xAAL. If not, see <http://www.gnu.org/licenses/>.
19
19
  #
20
20
 
21
- from . import tools
22
- from . import config
21
+ import datetime
22
+ import logging
23
+ import pprint
24
+ import struct
25
+ import typing
26
+ from enum import Enum
27
+ from typing import Any, Optional
28
+
29
+ import pysodium
30
+ from tabulate import tabulate
31
+
32
+ from . import cbor, tools
23
33
  from .bindings import UUID
34
+ from .config import config
24
35
  from .exceptions import MessageError, MessageParserError
25
- from . import cbor
26
36
 
27
- from enum import Enum
28
- from tabulate import tabulate
29
- import pprint
30
- import datetime
31
- import pysodium
32
- import struct
33
- import sys
37
+ if typing.TYPE_CHECKING:
38
+ from .devices import Device
39
+
34
40
 
35
- import logging
36
41
  logger = logging.getLogger(__name__)
37
42
 
38
43
  ALIVE_ADDR = UUID("00000000-0000-0000-0000-000000000000")
39
44
 
40
45
 
46
+ class MessageType(Enum):
47
+ NOTIFY = 0
48
+ REQUEST = 1
49
+ REPLY = 2
50
+
51
+
52
+ class MessageAction(Enum):
53
+ ALIVE = 'alive'
54
+ IS_ALIVE = 'is_alive'
55
+ ATTRIBUTES_CHANGE = 'attributes_change'
56
+ GET_ATTRIBUTES = 'get_attributes'
57
+ GET_DESCRIPTION = 'get_description'
58
+
59
+
60
+ class Message(object):
61
+ """Message object used for incomming & outgoint message"""
62
+
63
+ __slots__ = ['version', 'timestamp', 'source', 'dev_type', 'msg_type', 'action', 'body', '__targets']
64
+
65
+ def __init__(self):
66
+ self.version = config.STACK_VERSION # message API version
67
+ self.__targets = [] # target property
68
+ self.timestamp: tuple = () # message timestamp
69
+ self.source: Optional[UUID] = None # message source
70
+ self.dev_type: Optional[str] = None # message dev_type
71
+ self.msg_type: Optional[MessageType] = None # message type
72
+ self.action: Optional[str] = None # message action
73
+ self.body = {} # message body
74
+
75
+ @property
76
+ def targets(self) -> list:
77
+ return self.__targets
78
+
79
+ @targets.setter
80
+ def targets(self, values: list):
81
+ if not isinstance(values, list):
82
+ raise MessageError("Expected a list for targetsList, got %s" % (type(values),))
83
+ for uid in values:
84
+ if not tools.is_valid_address(uid):
85
+ raise MessageError("Bad target addr: %s" % uid)
86
+ self.__targets = values
87
+
88
+ def targets_as_string(self) -> list:
89
+ return [str(k) for k in self.targets]
90
+
91
+ def dump(self):
92
+ r = []
93
+ r.append(['version', self.version])
94
+ r.append(['targets', str(self.targets)])
95
+ r.append(['timestamp', str(self.timestamp)])
96
+ r.append(['source', self.source])
97
+ r.append(['dev_type', self.dev_type])
98
+ r.append(['msg_type', MessageType(self.msg_type)])
99
+ r.append(['action', self.action])
100
+ if self.body:
101
+ tmp = ""
102
+ for k, v in self.body.items():
103
+ k = k + ":"
104
+ v = pprint.pformat(v, width=55)
105
+ tmp = tmp + "- %-12s %s\n" % (k, v)
106
+ # tmp = tmp.strip()
107
+ r.append(['body', tmp])
108
+ print(tabulate(r, headers=['Field', 'Value'], tablefmt='psql'))
109
+
110
+ def __repr__(self) -> str:
111
+ return f"<xaal.Message {id(self):x} {self.source} {self.dev_type} {self.msg_type} {self.action}>"
112
+
113
+ def is_request(self) -> bool:
114
+ if MessageType(self.msg_type) == MessageType.REQUEST:
115
+ return True
116
+ return False
117
+
118
+ def is_reply(self) -> bool:
119
+ if MessageType(self.msg_type) == MessageType.REPLY:
120
+ return True
121
+ return False
122
+
123
+ def is_notify(self) -> bool:
124
+ if MessageType(self.msg_type) == MessageType.NOTIFY:
125
+ return True
126
+ return False
127
+
128
+ def is_alive(self) -> bool:
129
+ if self.is_notify() and self.action == MessageAction.ALIVE.value:
130
+ return True
131
+ return False
132
+
133
+ def is_request_isalive(self) -> bool:
134
+ if self.is_request() and self.action == MessageAction.IS_ALIVE.value:
135
+ return True
136
+ return False
137
+
138
+ def is_attributes_change(self) -> bool:
139
+ if self.is_notify() and self.action == MessageAction.ATTRIBUTES_CHANGE.value:
140
+ return True
141
+ return False
142
+
143
+ def is_get_attribute_reply(self) -> bool:
144
+ if self.is_reply() and self.action == MessageAction.GET_ATTRIBUTES.value:
145
+ return True
146
+ return False
147
+
148
+ def is_get_description_reply(self) -> bool:
149
+ if self.is_reply() and self.action == MessageAction.GET_DESCRIPTION.value:
150
+ return True
151
+ return False
152
+
153
+
41
154
  class MessageFactory(object):
42
155
  """Message Factory:
43
156
  - Build xAAL message
@@ -45,9 +158,10 @@ class MessageFactory(object):
45
158
  - Serialize/Deserialize data in CBOR"""
46
159
 
47
160
  def __init__(self, cipher_key):
48
- self.cipher_key = cipher_key # key encode / decode message built from passphrase
161
+ # key encode / decode message built from passphrase
162
+ self.cipher_key = cipher_key
49
163
 
50
- def encode_msg(self, msg):
164
+ def encode_msg(self, msg: Message) -> bytes:
51
165
  """Apply security layer and return encode MSG in CBOR
52
166
  :param msg: xAAL msg instance
53
167
  :type msg: Message
@@ -65,6 +179,10 @@ class MessageFactory(object):
65
179
 
66
180
  # Format payload & ciphering
67
181
  buf = []
182
+ if not msg.source:
183
+ raise MessageError("No source address in message")
184
+ if not msg.msg_type:
185
+ raise MessageError("No msg_type in message")
68
186
  buf.append(msg.source.bytes)
69
187
  buf.append(msg.dev_type)
70
188
  buf.append(msg.msg_type.value)
@@ -73,8 +191,8 @@ class MessageFactory(object):
73
191
  buf.append(msg.body)
74
192
  clear = cbor.dumps(buf)
75
193
  # Additionnal Data == cbor serialization of the targets array
76
- ad = result[3]
77
- nonce = build_nonce(msg.timestamp)
194
+ ad = result[3]
195
+ nonce = build_nonce(msg.timestamp)
78
196
  payload = pysodium.crypto_aead_chacha20poly1305_ietf_encrypt(clear, ad, nonce, self.cipher_key)
79
197
 
80
198
  # Final CBOR serialization
@@ -82,7 +200,7 @@ class MessageFactory(object):
82
200
  pkt = cbor.dumps(result)
83
201
  return pkt
84
202
 
85
- def decode_msg(self, data, filter_func=None):
203
+ def decode_msg(self, data: bytes, filter_func: Any = None) -> Optional[Message]:
86
204
  """Decode incoming CBOR data and De-Ciphering
87
205
  :param data: data received from the multicast bus
88
206
  :type data: cbor
@@ -94,17 +212,17 @@ class MessageFactory(object):
94
212
  # Decode cbor incoming data
95
213
  try:
96
214
  data_rx = cbor.loads(data)
97
- except:
215
+ except Exception:
98
216
  raise MessageParserError("Unable to parse CBOR data")
99
217
 
100
218
  # Instanciate Message, parse the security layer
101
219
  msg = Message()
102
220
  try:
103
- msg.version = data_rx[0]
104
- msg_time = data_rx[1]
105
- targets = cbor.loads(data_rx[3])
106
- msg.targets = [UUID(bytes=t) for t in targets]
107
- msg.timestamp = [data_rx[1], data_rx[2]]
221
+ msg.version = data_rx[0]
222
+ msg_time = data_rx[1]
223
+ targets = cbor.loads(data_rx[3])
224
+ msg.targets = [UUID(bytes=t) for t in targets]
225
+ msg.timestamp = (data_rx[1], data_rx[2])
108
226
  except IndexError:
109
227
  raise MessageParserError("Bad Message, wrong fields")
110
228
 
@@ -116,7 +234,7 @@ class MessageFactory(object):
116
234
  # Replay attack, window fixed to CIPHER_WINDOW in seconds
117
235
  now = build_timestamp()[0] # test done only on seconds ...
118
236
  if msg_time < (now - config.cipher_window):
119
- raise MessageParserError("Potential replay attack, message too old: %d sec" % round(now - msg_time) )
237
+ raise MessageParserError("Potential replay attack, message too old: %d sec" % round(now - msg_time))
120
238
 
121
239
  if msg_time > (now + config.cipher_window):
122
240
  raise MessageParserError("Potential replay attack, message too young: %d sec" % round(now - msg_time))
@@ -132,19 +250,19 @@ class MessageFactory(object):
132
250
  nonce = build_nonce(msg.timestamp)
133
251
  try:
134
252
  clear = pysodium.crypto_aead_chacha20poly1305_ietf_decrypt(ciph, ad, nonce, self.cipher_key)
135
- except :
253
+ except Exception:
136
254
  raise MessageParserError("Unable to decrypt msg")
137
255
 
138
256
  # Decode application layer (payload)
139
257
  try:
140
258
  payload = cbor.loads(clear)
141
- except:
259
+ except Exception:
142
260
  raise MessageParserError("Unable to parse CBOR data in payload after decrypt")
143
261
  try:
144
- msg.source = UUID(bytes=payload[0])
262
+ msg.source = UUID(bytes=payload[0])
145
263
  msg.dev_type = payload[1]
146
264
  msg.msg_type = payload[2]
147
- msg.action = payload[3]
265
+ msg.action = payload[3]
148
266
  except IndexError:
149
267
  raise MessageParserError("Unable to parse payload headers")
150
268
  if len(payload) == 5:
@@ -160,7 +278,14 @@ class MessageFactory(object):
160
278
  #####################################################
161
279
  # MSG builder
162
280
  #####################################################
163
- def build_msg(self, dev=None, targets=[], msg_type=None, action=None, body=None):
281
+ def build_msg(
282
+ self,
283
+ dev: Optional['Device'] = None,
284
+ targets: list = [],
285
+ msg_type: Optional[MessageType] = None,
286
+ action: Optional[str] = None,
287
+ body: Optional[dict] = None,
288
+ ):
164
289
  """the build method takes in parameters :
165
290
  -A device
166
291
  -The list of targets of the message
@@ -187,150 +312,47 @@ class MessageFactory(object):
187
312
  data = self.encode_msg(message)
188
313
  return data
189
314
 
190
- def build_alive_for(self, dev, timeout=0):
315
+ def build_alive_for(self, dev: 'Device', timeout: int = 0) -> bytes:
191
316
  """Build Alive message for a given device
192
317
  timeout = 0 is the minimum value
193
318
  """
194
319
  body = {}
195
320
  body['timeout'] = timeout
196
- message = self.build_msg(dev=dev, targets=[], msg_type=MessageType.NOTIFY, action=MessageAction.ALIVE.value, body=body)
321
+ message = self.build_msg(
322
+ dev=dev, targets=[], msg_type=MessageType.NOTIFY, action=MessageAction.ALIVE.value, body=body
323
+ )
197
324
  return message
198
325
 
199
- def build_error_msg(self, dev, errcode, description=None):
326
+ def build_error_msg(self, dev: 'Device', errcode: int, description: Optional[str] = None):
200
327
  """Build a Error message"""
201
328
  message = Message()
202
329
  body = {}
203
330
  body['code'] = errcode
204
331
  if description:
205
332
  body['description'] = description
206
- message = self.build_msg(dev, [], MessageType.NOTIFY, "error", body)
333
+ message = self.build_msg(dev, [], MessageType.NOTIFY, 'error', body)
207
334
  return message
208
335
 
209
336
 
210
- class MessageType(Enum):
211
- NOTIFY = 0
212
- REQUEST = 1
213
- REPLY = 2
214
-
215
-
216
- class MessageAction(Enum):
217
- ALIVE = "alive"
218
- IS_ALIVE = "is_alive"
219
- ATTRIBUTES_CHANGE = "attributes_change"
220
- GET_ATTRIBUTES = "get_attributes"
221
- GET_DESCRIPTION = "get_description"
222
-
223
-
224
- class Message(object):
225
- """Message object used for incomming & outgoint message"""
226
-
227
- __slots__ = ['version', 'timestamp', 'source', 'dev_type', 'msg_type', 'action', 'body', '__targets']
228
-
229
- def __init__(self):
230
- self.version = config.STACK_VERSION # message API version
231
- self.__targets = [] # target property
232
- self.timestamp = None # message timestamp
233
- self.source = None # message source
234
- self.dev_type = None # message dev_type
235
- self.msg_type = None # message type
236
- self.action = None # message action
237
- self.body = {} # message body
238
-
239
- @property
240
- def targets(self):
241
- return self.__targets
242
-
243
- @targets.setter
244
- def targets(self, values):
245
- if not isinstance(values, list):
246
- raise MessageError("Expected a list for targetsList, got %s" % (type(values),))
247
- for uid in values:
248
- if not tools.is_valid_address(uid):
249
- raise MessageError("Bad target addr: %s" % uid)
250
- self.__targets = values
251
-
252
- def targets_as_string(self):
253
- return [str(k) for k in self.targets]
254
-
255
- def dump(self):
256
- r = []
257
- r.append(["version", self.version])
258
- r.append(["targets", str(self.targets)])
259
- r.append(["timestamp", str(self.timestamp)])
260
- r.append(["source", self.source])
261
- r.append(["dev_type", self.dev_type])
262
- r.append(["msg_type", MessageType(self.msg_type)])
263
- r.append(["action", self.action])
264
- if self.body:
265
- tmp=""
266
- for k,v in self.body.items():
267
- k = k + ':'
268
- v = pprint.pformat(v,width=55)
269
- tmp = tmp+"- %-12s %s\n" % (k,v)
270
- #tmp = tmp.strip()
271
- r.append(["body", tmp])
272
- print(tabulate(r, headers=["Fied", "Value"], tablefmt="psql"))
273
-
274
- def __repr__(self):
275
- return f"<xaal.Message {id(self):x} {self.source} {self.dev_type} {self.msg_type} {self.action}>"
276
-
277
- def is_request(self):
278
- if MessageType(self.msg_type) == MessageType.REQUEST:
279
- return True
280
- return False
281
-
282
- def is_reply(self):
283
- if MessageType(self.msg_type) == MessageType.REPLY:
284
- return True
285
- return False
286
-
287
- def is_notify(self):
288
- if MessageType(self.msg_type) == MessageType.NOTIFY:
289
- return True
290
- return False
291
-
292
- def is_alive(self):
293
- if self.is_notify() and self.action == MessageAction.ALIVE.value:
294
- return True
295
- return False
296
-
297
- def is_request_isalive(self):
298
- if self.is_request() and self.action == MessageAction.IS_ALIVE.value:
299
- return True
300
- return False
301
-
302
- def is_attributes_change(self):
303
- if self.is_notify() and self.action == MessageAction.ATTRIBUTES_CHANGE.value:
304
- return True
305
- return False
306
-
307
- def is_get_attribute_reply(self):
308
- if self.is_reply() and self.action == MessageAction.GET_ATTRIBUTES.value:
309
- return True
310
- return False
311
-
312
- def is_get_description_reply(self):
313
- if self.is_reply() and self.action == MessageAction.GET_DESCRIPTION.value:
314
- return True
315
- return False
316
-
317
-
318
- def build_nonce(data):
319
- """ Big-Endian, time in seconds and time in microseconds """
320
- nonce = struct.pack('>QL', data[0], data[1])
337
+ def build_nonce(data: tuple) -> bytes:
338
+ """Big-Endian, time in seconds and time in microseconds"""
339
+ nonce = struct.pack(">QL", data[0], data[1])
321
340
  return nonce
322
341
 
323
342
 
324
- def build_timestamp():
343
+ def build_timestamp() -> tuple:
325
344
  """Return array [seconds since epoch, microseconds since last seconds] Time = UTC+0000"""
326
345
  epoch = datetime.datetime.fromtimestamp(0, datetime.UTC)
327
346
  timestamp = datetime.datetime.now(datetime.UTC) - epoch
328
- return _packtimestamp(timestamp.total_seconds(), timestamp.microseconds)
347
+ return (int(timestamp.total_seconds()), int(timestamp.microseconds))
348
+
329
349
 
350
+ ## This stuff below is for Py2/Py3 compatibility. In the current state of xAAL, we only use
351
+ # Py3. This code is here for archive purpose and could be removed in the future.
330
352
 
331
353
  # for better performance, I choose to use this trick to fix the change in size for Py3.
332
354
  # only test once.
333
- if sys.version_info.major == 2:
334
- _packtimestamp = lambda t1,t2: [long(t1),int(t2)]
335
- else:
336
- _packtimestamp = lambda t1,t2: [int(t1),int(t2)]
355
+ # if sys.version_info.major == 2:
356
+ # _packtimestamp = lambda t1, t2: (long(t1), int(t2)) # pyright: ignore
357
+ # else:
358
+ # _packtimestamp = lambda t1, t2: (int(t1), int(t2))
xaal/lib/network.py CHANGED
@@ -18,25 +18,26 @@
18
18
  # along with xAAL. If not, see <http://www.gnu.org/licenses/>.
19
19
  #
20
20
 
21
+ import logging
22
+ import select
21
23
  import socket
22
24
  import struct
23
- import select
24
- import logging
25
+ import time
26
+ from enum import Enum
27
+ from typing import Optional
25
28
 
26
29
  logger = logging.getLogger(__name__)
27
30
 
28
- import time
29
- from enum import Enum
30
31
 
31
32
  class NetworkState(Enum):
32
33
  disconnected = 0
33
- connected = 1
34
+ connected = 1
34
35
 
35
36
 
36
37
  class NetworkConnector(object):
37
38
  UDP_MAX_SIZE = 65507
38
39
 
39
- def __init__(self, addr, port, hops,bind_addr='0.0.0.0'):
40
+ def __init__(self, addr: str, port: int, hops: int, bind_addr="0.0.0.0"):
40
41
  self.addr = addr
41
42
  self.port = port
42
43
  self.hops = hops
@@ -52,14 +53,14 @@ class NetworkConnector(object):
52
53
  def __connect(self):
53
54
  logger.info("Connecting to %s:%s" % (self.addr, self.port))
54
55
 
55
- self.__sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP)
56
+ self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
56
57
  self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
57
58
  # #formac os ???
58
- #self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
59
+ # self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
59
60
  self.__sock.bind((self.bind_addr, self.port))
60
- mreq = struct.pack("=4s4s",socket.inet_aton(self.addr),socket.inet_aton(self.bind_addr))
61
- self.__sock.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,mreq)
62
- self.__sock.setsockopt(socket.IPPROTO_IP,socket.IP_MULTICAST_TTL,self.hops)
61
+ mreq = struct.pack("=4s4s", socket.inet_aton(self.addr), socket.inet_aton(self.bind_addr))
62
+ self.__sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
63
+ self.__sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, self.hops)
63
64
  self.state = NetworkState.connected
64
65
 
65
66
  def disconnect(self):
@@ -70,31 +71,33 @@ class NetworkConnector(object):
70
71
  def is_connected(self):
71
72
  return self.state == NetworkState.connected
72
73
 
73
- def receive(self):
74
+ def receive(self) -> bytes:
74
75
  packt = self.__sock.recv(self.UDP_MAX_SIZE)
75
76
  return packt
76
77
 
77
- def __get_data(self):
78
- r = select.select([self.__sock, ], [], [], 0.02)
78
+ def __get_data(self) -> Optional[bytes]:
79
+ r = select.select([self.__sock,], [], [], 0.02)
79
80
  if r[0]:
80
81
  return self.receive()
81
82
  return None
82
83
 
83
- def get_data(self):
84
- if not self.is_connected(): self.connect()
84
+ def get_data(self) -> Optional[bytes]:
85
+ if not self.is_connected():
86
+ self.connect()
85
87
  try:
86
88
  return self.__get_data()
87
89
  except Exception as e:
88
90
  self.network_error(e)
89
91
 
90
- def send(self,data):
91
- if not self.is_connected(): self.connect()
92
+ def send(self, data: bytes):
93
+ if not self.is_connected():
94
+ self.connect()
92
95
  try:
93
96
  self.__sock.sendto(data, (self.addr, self.port))
94
97
  except Exception as e:
95
98
  self.network_error(e)
96
99
 
97
- def network_error(self, msg):
100
+ def network_error(self, ex: Exception):
98
101
  self.disconnect()
99
- logger.info("Network error, reconnect..%s" % msg)
102
+ logger.info("Network error, reconnect..%s" % ex.__str__())
100
103
  time.sleep(5)