p3lib 1.1.108__py2.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.
- p3lib/__init__.py +0 -0
- p3lib/ate.py +108 -0
- p3lib/bokeh_auth.py +363 -0
- p3lib/bokeh_gui.py +845 -0
- p3lib/boot_manager.py +420 -0
- p3lib/conduit.py +145 -0
- p3lib/database_if.py +289 -0
- p3lib/file_io.py +154 -0
- p3lib/gnome_desktop_app.py +146 -0
- p3lib/helper.py +420 -0
- p3lib/json_networking.py +239 -0
- p3lib/login.html +98 -0
- p3lib/mqtt_rpc.py +240 -0
- p3lib/netif.py +226 -0
- p3lib/netplotly.py +223 -0
- p3lib/ngt.py +841 -0
- p3lib/pconfig.py +874 -0
- p3lib/ssh.py +935 -0
- p3lib/table_plot.py +675 -0
- p3lib/uio.py +574 -0
- p3lib-1.1.108.dist-info/LICENSE +21 -0
- p3lib-1.1.108.dist-info/METADATA +34 -0
- p3lib-1.1.108.dist-info/RECORD +24 -0
- p3lib-1.1.108.dist-info/WHEEL +4 -0
p3lib/helper.py
ADDED
@@ -0,0 +1,420 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
"""This file is responsible for providing general helper functionality not
|
4
|
+
associated with particular objects"""
|
5
|
+
|
6
|
+
import sys
|
7
|
+
import os
|
8
|
+
import platform
|
9
|
+
import json
|
10
|
+
import traceback
|
11
|
+
import socket
|
12
|
+
|
13
|
+
def initArgs(parser, lastCmdLineArg=None, checkHostArg=True):
|
14
|
+
"""This method is responsible for
|
15
|
+
- Ensure that the host argument has been defined by the user on the command line
|
16
|
+
- Set the debug level as defined on the command line.
|
17
|
+
- If lastCmdLineArg is defined then ensure that this is the last arguemnt on the
|
18
|
+
command line. If we don't do this then the user may define some arguments on
|
19
|
+
the command line after the action (callback) command line option but as
|
20
|
+
the arguments are processed in sequence the args following the action
|
21
|
+
arg will not be used.
|
22
|
+
"""
|
23
|
+
|
24
|
+
if checkHostArg:
|
25
|
+
if parser.values.host == None or len(parser.values.host) == 0:
|
26
|
+
raise Exception("Please define the RFeye host on the command line.")
|
27
|
+
|
28
|
+
parser.uio.debugLevel = parser.values.debug
|
29
|
+
|
30
|
+
# Check that the argument that invoked tha action (callback) is the last on the command line
|
31
|
+
argOk = False
|
32
|
+
if len(sys.argv) > 0:
|
33
|
+
lastArg = sys.argv[len(sys.argv) - 1]
|
34
|
+
if lastArg == lastCmdLineArg:
|
35
|
+
argOk = True
|
36
|
+
|
37
|
+
if not argOk:
|
38
|
+
raise Exception("Please ensure %s (if used) is the last argument on the command line." % (lastCmdLineArg))
|
39
|
+
|
40
|
+
|
41
|
+
def ensureBoolInt(value, arg):
|
42
|
+
"""We expect value to be a boolean (0 or 1)
|
43
|
+
raise an error if not
|
44
|
+
"""
|
45
|
+
if value not in [0, 1]:
|
46
|
+
raise Exception("The %s arg should be followed by 0 or 1. Was %s." % (str(arg), str(value)))
|
47
|
+
|
48
|
+
|
49
|
+
def getLines(text):
|
50
|
+
"""Split the text into lines"""
|
51
|
+
lines = []
|
52
|
+
if len(text) > 0:
|
53
|
+
elems = text.split("\n")
|
54
|
+
lines = stripEOL(elems)
|
55
|
+
return lines
|
56
|
+
|
57
|
+
|
58
|
+
def stripEOL(lines):
|
59
|
+
"""Strip the end of line characters from the list of lines of text"""
|
60
|
+
noEOLLines = []
|
61
|
+
for l in lines:
|
62
|
+
l = l.rstrip("\n")
|
63
|
+
l = l.rstrip("\r")
|
64
|
+
noEOLLines.append(l)
|
65
|
+
return noEOLLines
|
66
|
+
|
67
|
+
|
68
|
+
def getLinesFromFile(f):
|
69
|
+
"""Get Lines from file"""
|
70
|
+
fd = open(f, "r")
|
71
|
+
lines = fd.readlines()
|
72
|
+
fd.close()
|
73
|
+
return stripEOL(lines)
|
74
|
+
|
75
|
+
|
76
|
+
def _removeInvalidChars(line):
|
77
|
+
"""Return a copy of line with each ASCII control character (0-31),
|
78
|
+
and each double quote, removed."""
|
79
|
+
output = ''
|
80
|
+
for c in line:
|
81
|
+
if c >= ' ' and c != '"':
|
82
|
+
output = output + c
|
83
|
+
return output
|
84
|
+
|
85
|
+
|
86
|
+
def _addEntry(line, dict):
|
87
|
+
"""Parse line into a key and value, adding the result to dict, as in
|
88
|
+
getDict."""
|
89
|
+
# check for a parameter
|
90
|
+
fields = line.split('=')
|
91
|
+
# if at least 2 fields exist
|
92
|
+
if len(fields) > 1:
|
93
|
+
# add the key,value pair to the dictionary
|
94
|
+
key = _removeInvalidChars(fields[0])
|
95
|
+
value = _removeInvalidChars(fields[1])
|
96
|
+
dict[key] = value
|
97
|
+
|
98
|
+
|
99
|
+
def getDict(filename, jsonFmt=False):
|
100
|
+
"""@brief Load dict from file
|
101
|
+
@param jsonFmt If True then we expect the file to be in json format.
|
102
|
+
|
103
|
+
if json is True we expect the file to be in json format
|
104
|
+
|
105
|
+
if json is False
|
106
|
+
We key=value pairs (= is the separate character).
|
107
|
+
Lines containing a hash sign as the first non-whitespace character are
|
108
|
+
ignored. Leading and trailing whitespace is ignored.
|
109
|
+
|
110
|
+
Lines not containing an equals sign are also silently ignored.
|
111
|
+
|
112
|
+
Lines not ignored are assumed to be in the form key=value, where key
|
113
|
+
does not contain an equals sign;
|
114
|
+
Control characters and double quotes in both key and value are silently
|
115
|
+
discarded. value is also truncated just before the first whitespace or
|
116
|
+
equals sign it contains.
|
117
|
+
|
118
|
+
@return Return the dict loaded from the file.
|
119
|
+
|
120
|
+
"""
|
121
|
+
dictLoaded = {}
|
122
|
+
|
123
|
+
if jsonFmt:
|
124
|
+
fp = open(filename, 'r')
|
125
|
+
dictLoaded = json.load(fp)
|
126
|
+
fp.close()
|
127
|
+
|
128
|
+
else:
|
129
|
+
|
130
|
+
lines = getLinesFromFile(filename)
|
131
|
+
for line in lines:
|
132
|
+
# strip leading and trailing whitespaces
|
133
|
+
line = line.strip()
|
134
|
+
# if a comment line then ignore
|
135
|
+
if line.find('#') == 0:
|
136
|
+
continue
|
137
|
+
# add an entry to the dict
|
138
|
+
_addEntry(line, dictLoaded)
|
139
|
+
|
140
|
+
return dictLoaded
|
141
|
+
|
142
|
+
|
143
|
+
def saveDict(dictToSave, filename, jsonFmt=False):
|
144
|
+
"""@brief Save dict to a file.
|
145
|
+
@param jsonFmt If True then we expect the file to be in json format.
|
146
|
+
|
147
|
+
if json is True we expect the file to be in json format
|
148
|
+
|
149
|
+
if json is False the file is saved as key = value pairs
|
150
|
+
Each key in dict produces a line of the form key=value. Output will be
|
151
|
+
ambiguous if any keys contain equals signs or if any values contain
|
152
|
+
newlines.
|
153
|
+
|
154
|
+
"""
|
155
|
+
|
156
|
+
if jsonFmt:
|
157
|
+
try:
|
158
|
+
|
159
|
+
with open(filename, "w") as write_file:
|
160
|
+
json.dump(dictToSave, write_file)
|
161
|
+
|
162
|
+
except IOError as i:
|
163
|
+
raise IOError(i.errno, 'Failed to write file \'%s\': %s'
|
164
|
+
% (filename, i.strerror), i.filename).with_traceback(sys.exc_info()[2])
|
165
|
+
else:
|
166
|
+
lines = []
|
167
|
+
# build config file lines
|
168
|
+
for key in list(dictToSave.keys()):
|
169
|
+
lines.append(str(key) + '=' + str(dictToSave.get(key)) + '\n')
|
170
|
+
try:
|
171
|
+
f = open(filename, 'w')
|
172
|
+
f.writelines(lines)
|
173
|
+
f.close()
|
174
|
+
except IOError as i:
|
175
|
+
raise IOError(i.errno, 'Failed to write file \'%s\': %s'
|
176
|
+
% (filename, i.strerror), i.filename).with_traceback(sys.exc_info()[2])
|
177
|
+
|
178
|
+
|
179
|
+
def getAddrPort(host):
|
180
|
+
"""The host address may be entered in the format <address>:<port>
|
181
|
+
Return a tuple with host and port"""
|
182
|
+
elems = host.split(":")
|
183
|
+
|
184
|
+
if len(elems) > 1:
|
185
|
+
host = elems[0]
|
186
|
+
port = int(elems[1])
|
187
|
+
else:
|
188
|
+
port = 22
|
189
|
+
|
190
|
+
return [host, port]
|
191
|
+
|
192
|
+
|
193
|
+
def getProgramName():
|
194
|
+
"""Get the name of the currently running program."""
|
195
|
+
progName = sys.argv[0].strip()
|
196
|
+
if progName.startswith('./'):
|
197
|
+
progName = progName[2:]
|
198
|
+
if progName.endswith('.py'):
|
199
|
+
progName = progName[:-3]
|
200
|
+
|
201
|
+
# Only return the name of the program not the path
|
202
|
+
pName = os.path.split(progName)[-1]
|
203
|
+
if pName.endswith('.exe'):
|
204
|
+
pName = pName[:-4]
|
205
|
+
return pName
|
206
|
+
|
207
|
+
|
208
|
+
def getBoolUserResponse(uio, prompt, allowQuit=True):
|
209
|
+
"""Get boolean (Y/N) repsonse from user.
|
210
|
+
If allowQuit is True and the user enters q then the program will exit."""
|
211
|
+
while True:
|
212
|
+
response = uio.getInput(prompt=prompt)
|
213
|
+
if response.lower() == 'y':
|
214
|
+
return True
|
215
|
+
elif response.lower() == 'n':
|
216
|
+
return False
|
217
|
+
elif allowQuit and response.lower() == 'q':
|
218
|
+
sys.exit(0)
|
219
|
+
|
220
|
+
|
221
|
+
def getIntUserResponse(uio, prompt, allowQuit=True):
|
222
|
+
"""Get int repsonse from user.
|
223
|
+
If allowQuit is True and the user enters q then None is returned to
|
224
|
+
indicate that the user selected quit."""
|
225
|
+
while True:
|
226
|
+
response = uio.getInput(prompt=prompt)
|
227
|
+
|
228
|
+
try:
|
229
|
+
|
230
|
+
return int(response)
|
231
|
+
|
232
|
+
except ValueError:
|
233
|
+
|
234
|
+
uio.info("%s is not a valid integer value." % (response))
|
235
|
+
|
236
|
+
if allowQuit and response.lower() == 'q':
|
237
|
+
return None
|
238
|
+
|
239
|
+
|
240
|
+
def getIntListUserResponse(uio, prompt, minValue=None, maxValue=None, allowQuit=True):
|
241
|
+
"""Get int repsonse from user as a list of int's
|
242
|
+
If allowQuit is True and the user enters q then the program will exit."""
|
243
|
+
while True:
|
244
|
+
response = uio.getInput(prompt=prompt)
|
245
|
+
try:
|
246
|
+
elems = response.split(",")
|
247
|
+
if len(elems) > 0:
|
248
|
+
intList = []
|
249
|
+
errorStr = None
|
250
|
+
for vStr in elems:
|
251
|
+
v = int(vStr)
|
252
|
+
if minValue != None and v < minValue:
|
253
|
+
errorStr = "The min value that may be entered is %d." % (minValue)
|
254
|
+
break
|
255
|
+
elif maxValue != None and v > maxValue:
|
256
|
+
errorStr = "The max value that may be entered is %d." % (maxValue)
|
257
|
+
break
|
258
|
+
else:
|
259
|
+
intList.append(v)
|
260
|
+
|
261
|
+
if errorStr != None:
|
262
|
+
uio.error(errorStr)
|
263
|
+
|
264
|
+
return intList
|
265
|
+
except ValueError:
|
266
|
+
pass
|
267
|
+
if allowQuit and response.lower() == 'q':
|
268
|
+
sys.exit(0)
|
269
|
+
|
270
|
+
|
271
|
+
def getHomePath():
|
272
|
+
"""Get the user home path as this will be used to store config files"""
|
273
|
+
if platform.system() == 'Linux' and os.geteuid() == 0:
|
274
|
+
# Fix for os.environ["HOME"] returning /home/root sometimes.
|
275
|
+
return '/root/'
|
276
|
+
|
277
|
+
elif "HOME" in os.environ:
|
278
|
+
return os.environ["HOME"]
|
279
|
+
|
280
|
+
elif "HOMEDRIVE" in os.environ and "HOMEPATH" in os.environ:
|
281
|
+
return os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]
|
282
|
+
|
283
|
+
elif "USERPROFILE" in os.environ:
|
284
|
+
return os.environ["USERPROFILE"]
|
285
|
+
|
286
|
+
return None
|
287
|
+
|
288
|
+
|
289
|
+
def setHomePath(homePath):
|
290
|
+
"""Seth the env variable HOME"""
|
291
|
+
# Do some sanity/defensive stuff
|
292
|
+
if homePath == None:
|
293
|
+
raise Exception("homePath=None.")
|
294
|
+
elif len(homePath) == 0:
|
295
|
+
raise Exception("len(homePath)=0.")
|
296
|
+
|
297
|
+
# We do some special stuff on windows
|
298
|
+
if platform.system() == "Windows":
|
299
|
+
|
300
|
+
os.environ["HOME"] = homePath
|
301
|
+
|
302
|
+
if "USERPROFILE" in os.environ:
|
303
|
+
os.environ["USERPROFILE"] = os.environ["HOME"]
|
304
|
+
|
305
|
+
if "HOMEPATH" in os.environ:
|
306
|
+
os.environ["HOMEPATH"] = os.environ["HOME"]
|
307
|
+
|
308
|
+
if not os.path.isdir(os.environ["HOME"]):
|
309
|
+
raise Exception(os.environ["HOME"] + " path not found.")
|
310
|
+
|
311
|
+
else:
|
312
|
+
# Not windows set HOME env var
|
313
|
+
os.environ["HOME"] = homePath
|
314
|
+
|
315
|
+
if not os.path.isdir(os.environ["HOME"]):
|
316
|
+
raise Exception(os.environ["HOME"] + " path not found.")
|
317
|
+
|
318
|
+
def printDict(uio, theDict, indent=0):
|
319
|
+
"""@brief Show the details of a dictionary contents
|
320
|
+
@param theDict The dictionary
|
321
|
+
@param indent Number of tab indents
|
322
|
+
@return None"""
|
323
|
+
for key in theDict:
|
324
|
+
uio.info('\t' * indent + str(key))
|
325
|
+
value = theDict[key]
|
326
|
+
if isinstance(value, dict):
|
327
|
+
printDict(uio, value, indent + 1)
|
328
|
+
else:
|
329
|
+
uio.info('\t' * (indent + 1) + str(value))
|
330
|
+
|
331
|
+
def logTraceBack(uio):
|
332
|
+
"""@brief Log a traceback using the uio instance to the debug file.
|
333
|
+
@param uio A UIO instance
|
334
|
+
@return None"""
|
335
|
+
# Always store the exception traceback in the logfile as this makes
|
336
|
+
# it easier to diagnose problems with the testing
|
337
|
+
lines = traceback.format_exc().split("\n")
|
338
|
+
for line in lines:
|
339
|
+
uio.storeToDebugLog(line)
|
340
|
+
|
341
|
+
def GetFreeTCPPort():
|
342
|
+
"""@brief Get a free port and return to the client. If no port is available
|
343
|
+
then -1 is returned.
|
344
|
+
@return the free TCP port number or -1 if no port is available."""
|
345
|
+
tcpPort=-1
|
346
|
+
try:
|
347
|
+
#Bind to a local port to find a free TTCP port
|
348
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
349
|
+
sock.bind(('', 0))
|
350
|
+
tcpPort = sock.getsockname()[1]
|
351
|
+
sock.close()
|
352
|
+
except socket.error:
|
353
|
+
pass
|
354
|
+
return tcpPort
|
355
|
+
|
356
|
+
def appendCreateFile(uio, aFile, quiet=False):
|
357
|
+
"""@brief USer interaction to append or create a file.
|
358
|
+
@param uio A UIO instance.
|
359
|
+
@param quiet If True do not show uio messages (apart from overwrite prompt.
|
360
|
+
@param aFile The file to append or delete."""
|
361
|
+
createFile = False
|
362
|
+
if os.path.isfile(aFile):
|
363
|
+
if uio.getBoolInput("Overwrite {} y/n".format(aFile)):
|
364
|
+
os.remove(aFile)
|
365
|
+
if not quiet:
|
366
|
+
uio.info("Deleted {}".format(aFile))
|
367
|
+
createFile = True
|
368
|
+
else:
|
369
|
+
if not quiet:
|
370
|
+
uio.info("Appending to {}".format(aFile))
|
371
|
+
|
372
|
+
else:
|
373
|
+
createFile = True
|
374
|
+
|
375
|
+
if createFile:
|
376
|
+
fd = open(aFile, 'w')
|
377
|
+
fd.close()
|
378
|
+
if not quiet:
|
379
|
+
uio.info("Created {}".format(aFile))
|
380
|
+
|
381
|
+
def getAbsFile(filename):
|
382
|
+
"""@brief Check that the file exists in several places.
|
383
|
+
1 - The startup folder
|
384
|
+
2 - An 'assets' folder in the startup folder
|
385
|
+
3 - An 'assets' folder in the startup parent folder
|
386
|
+
3 - In an 'assets' folder in a python site-packages folder.
|
387
|
+
@param filename The name of the icon file.
|
388
|
+
@return The abs path of the file or None if not found."""
|
389
|
+
file_found = None
|
390
|
+
abs_filename = os.path.abspath(filename)
|
391
|
+
if os.path.isfile(abs_filename):
|
392
|
+
file_found = abs_filename
|
393
|
+
|
394
|
+
else:
|
395
|
+
startup_file = os.path.abspath(sys.argv[0])
|
396
|
+
startup_path = os.path.dirname(startup_file)
|
397
|
+
path1 = os.path.join(startup_path, 'assets')
|
398
|
+
abs_filename = os.path.join(path1, filename)
|
399
|
+
if os.path.isfile(abs_filename):
|
400
|
+
file_found = abs_filename
|
401
|
+
|
402
|
+
else:
|
403
|
+
startup_parent_path = os.path.join(startup_path, '..')
|
404
|
+
path2 = os.path.join(startup_parent_path, 'assets')
|
405
|
+
abs_filename = os.path.join(path2, filename)
|
406
|
+
if os.path.isfile(abs_filename):
|
407
|
+
file_found = abs_filename
|
408
|
+
|
409
|
+
else:
|
410
|
+
# Try all the site packages folders we know about.
|
411
|
+
for path in sys.path:
|
412
|
+
if 'site-packages' in path:
|
413
|
+
site_packages_path = path
|
414
|
+
path3 = os.path.join(site_packages_path, 'assets')
|
415
|
+
abs_filename = os.path.join(path3, filename)
|
416
|
+
if os.path.isfile(abs_filename):
|
417
|
+
file_found = abs_filename
|
418
|
+
break
|
419
|
+
|
420
|
+
return file_found
|
p3lib/json_networking.py
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
import socketserver
|
4
|
+
import socket
|
5
|
+
from struct import pack, unpack
|
6
|
+
import json
|
7
|
+
from time import sleep
|
8
|
+
|
9
|
+
class JSONNetworking(Exception):
|
10
|
+
pass
|
11
|
+
|
12
|
+
class JSONServer(socketserver.ThreadingTCPServer):
|
13
|
+
"""@brief Responsible for accepting tcp connections to receive and send json messages."""
|
14
|
+
|
15
|
+
daemon_threads = True
|
16
|
+
allow_reuse_address = True
|
17
|
+
|
18
|
+
class JsonServerHandler (socketserver.BaseRequestHandler):
|
19
|
+
|
20
|
+
LEN_FIELD = 4
|
21
|
+
DEFAULT_RX_POLL_SECS = 0.02
|
22
|
+
DEFAULT_RX_BUFFER_SIZE = 2048
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def DictToJSON(aDict):
|
26
|
+
"""@brief convert a python dictionary into JSON text
|
27
|
+
@param aDict The python dictionary to be converted
|
28
|
+
@return The JSON text representing aDict"""
|
29
|
+
return json.dumps(aDict)
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def JSONToDict(jsonText):
|
33
|
+
"""@brief Convert from JSON text to a python dict. Throws a ValueError
|
34
|
+
if the text is formatted incorrectly.
|
35
|
+
@param jsonText The JSON text to be converted to a dict
|
36
|
+
@return a python dict object"""
|
37
|
+
return json.loads(jsonText)
|
38
|
+
|
39
|
+
@staticmethod
|
40
|
+
def TX(request, theDict):
|
41
|
+
"""@brief Write the dict to the socket as json text
|
42
|
+
@param request The request object passed from the handle() method.
|
43
|
+
@theDict The python dictionary to send."""
|
44
|
+
if request:
|
45
|
+
msg = JsonServerHandler.DictToJSON(theDict).encode()
|
46
|
+
sFmt = ">%ds" % len(msg)
|
47
|
+
body = pack(sFmt, msg)
|
48
|
+
bodyLen = pack('>I', len(body))
|
49
|
+
txBuf = bodyLen + body
|
50
|
+
request.send(txBuf)
|
51
|
+
|
52
|
+
else:
|
53
|
+
raise RuntimeError("TX socket error")
|
54
|
+
|
55
|
+
@staticmethod
|
56
|
+
def GetBodyLen(rxBytes):
|
57
|
+
"""@brief Get the length of the body of the message.
|
58
|
+
@param rxBytes The rx buffer containing bytes received.
|
59
|
+
@return The length of the body of the message or 0 if we do not have a complete message in the rx buffer."""
|
60
|
+
bodyLenFound = 0
|
61
|
+
#If we have enough data to extract the length field (start of PDU)
|
62
|
+
if len(rxBytes) >= JsonServerHandler.LEN_FIELD:
|
63
|
+
# Read the length of the message
|
64
|
+
bodyLen = unpack(">I", rxBytes[:JsonServerHandler.LEN_FIELD])[0]
|
65
|
+
#If we have the len field + the message body
|
66
|
+
if len(rxBytes) >= JsonServerHandler.LEN_FIELD+bodyLen:
|
67
|
+
bodyLenFound = bodyLen
|
68
|
+
return bodyLenFound
|
69
|
+
|
70
|
+
@staticmethod
|
71
|
+
def MsgAvail(rxBytes):
|
72
|
+
"""@brief Determine is a complete message is present in the rx buffer.
|
73
|
+
@param rxBytes The rx buffer containing bytes received.
|
74
|
+
@return True if a complete message is present in the RX buffer."""
|
75
|
+
msgAvail = False
|
76
|
+
|
77
|
+
bodyLen = JsonServerHandler.GetBodyLen(rxBytes)
|
78
|
+
|
79
|
+
if bodyLen:
|
80
|
+
msgAvail = True
|
81
|
+
|
82
|
+
return msgAvail
|
83
|
+
|
84
|
+
def tx(self, request, theDict, throwError=True):
|
85
|
+
"""@brief send a python dictionary object to the client via json.
|
86
|
+
@param request The request object to send data on.
|
87
|
+
@param theDict The dictionary to send
|
88
|
+
@param throwError If True then an exception will be thrown if an error occurs.
|
89
|
+
If False then this method will fail silentley.
|
90
|
+
@return True on success. False on failure if throwError = False"""
|
91
|
+
try:
|
92
|
+
JsonServerHandler.TX(request, theDict)
|
93
|
+
return True
|
94
|
+
except:
|
95
|
+
if throwError:
|
96
|
+
raise
|
97
|
+
return False
|
98
|
+
|
99
|
+
def _getDict(self):
|
100
|
+
"""@brief Get dict from rx data.
|
101
|
+
@return The oldest dict in the rx buffer."""
|
102
|
+
rxDict = None
|
103
|
+
bodyLen = JsonServerHandler.GetBodyLen(self._rxBuffer)
|
104
|
+
#If we have a complete message in the RX buffer
|
105
|
+
if bodyLen > 0:
|
106
|
+
body = self._rxBuffer[JsonServerHandler.LEN_FIELD:JsonServerHandler.LEN_FIELD+bodyLen]
|
107
|
+
rxDict = JsonServerHandler.JSONToDict( body.decode() )
|
108
|
+
#Remove the message just received from the RX buffer
|
109
|
+
self._rxBuffer = self._rxBuffer[JsonServerHandler.LEN_FIELD+bodyLen:]
|
110
|
+
|
111
|
+
return rxDict
|
112
|
+
|
113
|
+
def rx(self, blocking=True,
|
114
|
+
pollPeriodSeconds=DEFAULT_RX_POLL_SECS,
|
115
|
+
rxBufferSize=DEFAULT_RX_BUFFER_SIZE):
|
116
|
+
"""@brief Get a python dictionary object from the server.
|
117
|
+
@param blocking If True block until complete message is received.
|
118
|
+
@param pollPeriodSeconds If blocking wait for this period in seconds between checking for RX data.
|
119
|
+
@param rxBufferSize The size of the receive buffer in bytes.
|
120
|
+
@return A received dictionary of None if not blocking and no dictionary is available."""
|
121
|
+
#If we don't have an rx buffer, create one.
|
122
|
+
if '_rxBuffer' not in dir(self):
|
123
|
+
self._rxBuffer = bytearray()
|
124
|
+
|
125
|
+
while not JsonServerHandler.MsgAvail(self._rxBuffer):
|
126
|
+
|
127
|
+
try:
|
128
|
+
rxd = self.request.recv(rxBufferSize)
|
129
|
+
if len(rxd) > 0:
|
130
|
+
self._rxBuffer = self._rxBuffer + rxd
|
131
|
+
else:
|
132
|
+
raise RuntimeError("Socket closed")
|
133
|
+
|
134
|
+
if not blocking:
|
135
|
+
break
|
136
|
+
|
137
|
+
except BlockingIOError:
|
138
|
+
if not blocking:
|
139
|
+
break
|
140
|
+
|
141
|
+
if blocking:
|
142
|
+
sleep(pollPeriodSeconds)
|
143
|
+
|
144
|
+
return self._getDict()
|
145
|
+
|
146
|
+
def handle(self):
|
147
|
+
"""@brief Handle connections to the server."""
|
148
|
+
raise JSONNetworking("!!! You must override this method in a subclass !!!")
|
149
|
+
|
150
|
+
class JSONClient(object):
|
151
|
+
|
152
|
+
def __init__(self, address, port, keepAliveActSec=1, keepAliveTxSec=15, keepAliveFailTriggerCount=8):
|
153
|
+
"""@brief Connect to a JSONServer socket.
|
154
|
+
If on a Linux system then the connection will apply a keepalive to the TCP connection.
|
155
|
+
By default this is 2 minutes.
|
156
|
+
@param address The address of the JSONServer.
|
157
|
+
@param The port on the JSON server to connect to.
|
158
|
+
@param keepAliveActSec Activate the keepalive failure this many seconds after it is triggered.
|
159
|
+
@param keepAliveTxSec Send a TCP keepalive periodically. This defines the period in seconds.
|
160
|
+
@param keepAliveFailTriggerCount Trigger a keepalive failure when this many keepalives fail consecutively."""
|
161
|
+
|
162
|
+
self._socket = socket.socket()
|
163
|
+
self._socket.connect( (address, port) )
|
164
|
+
|
165
|
+
if hasattr(socket, 'SOL_SOCKET') and\
|
166
|
+
hasattr(socket, 'SO_KEEPALIVE') and\
|
167
|
+
hasattr(socket, 'IPPROTO_TCP') and\
|
168
|
+
hasattr(socket, 'TCP_KEEPIDLE') and\
|
169
|
+
hasattr(socket, 'TCP_KEEPINTVL') and\
|
170
|
+
hasattr(socket, 'TCP_KEEPCNT'):
|
171
|
+
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
172
|
+
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, keepAliveActSec)
|
173
|
+
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, keepAliveTxSec)
|
174
|
+
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, keepAliveFailTriggerCount)
|
175
|
+
self._socket.setblocking(False)
|
176
|
+
self._rxBuffer = bytearray()
|
177
|
+
|
178
|
+
def tx(self, theDict, throwError=True):
|
179
|
+
"""@brief send a python dictionary object to the server via json
|
180
|
+
@param theDict The dictionary to send
|
181
|
+
@param throwError If True then an exception will be thrown if an error occurs.
|
182
|
+
If False then this method will fail silentley.
|
183
|
+
@return True on success. False on failure if throwError = False"""
|
184
|
+
try:
|
185
|
+
JsonServerHandler.TX(self._socket, theDict)
|
186
|
+
return True
|
187
|
+
except:
|
188
|
+
if throwError:
|
189
|
+
raise
|
190
|
+
return False
|
191
|
+
|
192
|
+
def _getDict(self):
|
193
|
+
"""@brief Get dict from rx data.
|
194
|
+
@return The oldest dict in the rx buffer."""
|
195
|
+
rxDict = None
|
196
|
+
bodyLen = JsonServerHandler.GetBodyLen(self._rxBuffer)
|
197
|
+
#If we have a complete message in the RX buffer
|
198
|
+
if bodyLen > 0:
|
199
|
+
body = self._rxBuffer[JsonServerHandler.LEN_FIELD:JsonServerHandler.LEN_FIELD+bodyLen]
|
200
|
+
rxDict = JsonServerHandler.JSONToDict( body.decode() )
|
201
|
+
#Remove the message just received from the RX buffer
|
202
|
+
self._rxBuffer = self._rxBuffer[JsonServerHandler.LEN_FIELD+bodyLen:]
|
203
|
+
|
204
|
+
return rxDict
|
205
|
+
|
206
|
+
def rx(self, blocking=True,
|
207
|
+
pollPeriodSeconds=JsonServerHandler.DEFAULT_RX_POLL_SECS,
|
208
|
+
rxBufferSize=JsonServerHandler.DEFAULT_RX_BUFFER_SIZE):
|
209
|
+
"""@brief Get a python dictionary object from the server.
|
210
|
+
@param blocking If True block until complete message is received.
|
211
|
+
@param pollPeriodSeconds If blocking wait for this period in seconds between checking for RX data.
|
212
|
+
@param rxBufferSize The size of the receive buffer in bytes.
|
213
|
+
@return A received dictionary of None if not blocking and no dictionary is available."""
|
214
|
+
|
215
|
+
while not JsonServerHandler.MsgAvail(self._rxBuffer):
|
216
|
+
|
217
|
+
try:
|
218
|
+
rxd = self._socket.recv(rxBufferSize)
|
219
|
+
if len(rxd) > 0:
|
220
|
+
self._rxBuffer = self._rxBuffer + rxd
|
221
|
+
else:
|
222
|
+
raise RuntimeError("Socket closed")
|
223
|
+
|
224
|
+
if not blocking:
|
225
|
+
break
|
226
|
+
|
227
|
+
except BlockingIOError:
|
228
|
+
if not blocking:
|
229
|
+
break
|
230
|
+
|
231
|
+
if blocking:
|
232
|
+
sleep(pollPeriodSeconds)
|
233
|
+
|
234
|
+
return self._getDict()
|
235
|
+
|
236
|
+
def close(self):
|
237
|
+
"""@brief Close the socket connection to the server."""
|
238
|
+
if self._socket:
|
239
|
+
self._socket.close()
|