urfp 0.2__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.
- urfp/__init__.py +0 -0
- urfp/_version.py +1 -0
- urfp/argtype.py +114 -0
- urfp/console.py +111 -0
- urfp/core.py +551 -0
- urfp/http.py +135 -0
- urfp/receiver.py +146 -0
- urfp/tinypacket.py +882 -0
- urfp-0.2.dist-info/METADATA +83 -0
- urfp-0.2.dist-info/RECORD +12 -0
- urfp-0.2.dist-info/WHEEL +4 -0
- urfp-0.2.dist-info/licenses/LICENSE +21 -0
urfp/__init__.py
ADDED
|
File without changes
|
urfp/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2"
|
urfp/argtype.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
""" Argument type description class for use in tinypacket """
|
|
2
|
+
|
|
3
|
+
class URFPArgType:
|
|
4
|
+
""" This class is used to describe the data structure of one variable, function
|
|
5
|
+
return value, function arguments, or source content. It is especially needed when
|
|
6
|
+
receiving data from simple devices that cannot efficiently augment all data
|
|
7
|
+
with field names and type info, e.g. in URFPTinypacket """
|
|
8
|
+
|
|
9
|
+
# Binary representation as in Tinypacket
|
|
10
|
+
URFP_TYPE_INFO = 0x00
|
|
11
|
+
|
|
12
|
+
# Type byte
|
|
13
|
+
URFP_STRUCT_START = 0x40
|
|
14
|
+
URFP_ARRAY_START = 0x41 # followed by int32 array size (-1 or > 0)
|
|
15
|
+
URFP_ARRAY_END = 0x42
|
|
16
|
+
URFP_STRUCT_END = 0x43
|
|
17
|
+
URFP_BOOL = 0x44
|
|
18
|
+
URFP_STRING = 0x45
|
|
19
|
+
URFP_FLOAT = 0x46
|
|
20
|
+
URFP_DOUBLE = 0x47
|
|
21
|
+
URFP_INT = 0x48 # to be completed with ld2(wordlen) in LSB
|
|
22
|
+
URFP_INT8 = 0x48 # = URFP_INT | ld2(1) = URFP_INT
|
|
23
|
+
URFP_INT16 = 0x49 # = URFP_INT | ld2(2)
|
|
24
|
+
URFP_INT32 = 0x4A # = URFP_INT | ld2(4)
|
|
25
|
+
URFP_INT64 = 0x4B # = URFP_INT | ld2(8)
|
|
26
|
+
URFP_UNSIGNED = 0x4C # to be completed with ld2(wordlen) in LSB
|
|
27
|
+
URFP_UINT8 = 0x4C # = URFP_UNSIGNED | ld2(1) = URFP_UNSIGNED
|
|
28
|
+
URFP_UINT16 = 0x4D # = URFP_UNSIGNED | ld2(2)
|
|
29
|
+
URFP_UINT32 = 0x4E # = URFP_UNSIGNED | ld2(4)
|
|
30
|
+
URFP_UINT64 = 0x4F # = URFP_UNSIGNED | ld2(8)
|
|
31
|
+
|
|
32
|
+
# 0x50..0xFF reserved for custom types
|
|
33
|
+
|
|
34
|
+
# Type mode
|
|
35
|
+
URFP_ARG = 0x01 # Indicates that type information describes an argument not result
|
|
36
|
+
URFP_NAMED = 0x02 # Indicates that a name string follows type information
|
|
37
|
+
|
|
38
|
+
def __init__(self, name, length=0, type_byte=None, type_str=None):
|
|
39
|
+
self.name = name
|
|
40
|
+
self.length = length
|
|
41
|
+
if type_str:
|
|
42
|
+
if type_str == '{':
|
|
43
|
+
self.code = URFPArgType.URFP_STRUCT_START
|
|
44
|
+
if type_str[0] == '[':
|
|
45
|
+
self.code = URFPArgType.URFP_ARRAY_START
|
|
46
|
+
if length==0:
|
|
47
|
+
if len(type_str)>0:
|
|
48
|
+
self.length = int(type_str[1:])
|
|
49
|
+
else:
|
|
50
|
+
self.length = -1
|
|
51
|
+
if type_str == ']':
|
|
52
|
+
self.code = URFPArgType.URFP_ARRAY_END
|
|
53
|
+
if type_str == '}':
|
|
54
|
+
self.code = URFPArgType.URFP_STRUCT_END
|
|
55
|
+
if type_str == 'bool':
|
|
56
|
+
self.code = URFPArgType.URFP_BOOL
|
|
57
|
+
elif type_str == 'string':
|
|
58
|
+
self.code = URFPArgType.URFP_STRING
|
|
59
|
+
elif type_str == 'float':
|
|
60
|
+
self.code = URFPArgType.URFP_FLOAT
|
|
61
|
+
elif type_str == 'double':
|
|
62
|
+
self.code = URFPArgType.URFP_DOUBLE
|
|
63
|
+
elif type_str[0] == 'i':
|
|
64
|
+
self.code = URFPArgType.URFP_INT
|
|
65
|
+
elif type_str[0] == 'u':
|
|
66
|
+
self.code = URFPArgType.URFP_UNSIGNED
|
|
67
|
+
|
|
68
|
+
if self.code in (URFPArgType.URFP_INT, URFPArgType.URFP_UNSIGNED):
|
|
69
|
+
if type_str[1:] == '16':
|
|
70
|
+
self.code = self.code | 0x01
|
|
71
|
+
elif type_str[1:] == '32':
|
|
72
|
+
self.code = self.code | 0x02
|
|
73
|
+
elif type_str[1:] == '64':
|
|
74
|
+
self.code = self.code | 0x03
|
|
75
|
+
|
|
76
|
+
else: # no type_str
|
|
77
|
+
if type_byte is None:
|
|
78
|
+
raise TypeError('Either type_byte or type_str must be specified')
|
|
79
|
+
|
|
80
|
+
self.code = type_byte
|
|
81
|
+
if type_byte == 0x41 and length==0:
|
|
82
|
+
raise TypeError('A nonzero length must be given for arrays')
|
|
83
|
+
|
|
84
|
+
def __str__(self):
|
|
85
|
+
if self.code == URFPArgType.URFP_STRUCT_START:
|
|
86
|
+
t = self.name + ':' + '{'
|
|
87
|
+
elif self.code == URFPArgType.URFP_ARRAY_START:
|
|
88
|
+
if self.length != -1:
|
|
89
|
+
t = f'{self.name}:[{self.length}'
|
|
90
|
+
else:
|
|
91
|
+
t = f'{self.name}:['
|
|
92
|
+
elif self.code == URFPArgType.URFP_ARRAY_END:
|
|
93
|
+
t = ']'
|
|
94
|
+
elif self.code == URFPArgType.URFP_STRUCT_END:
|
|
95
|
+
t = '}'
|
|
96
|
+
elif self.code == URFPArgType.URFP_BOOL:
|
|
97
|
+
t = self.name + ':' + 'bool'
|
|
98
|
+
elif self.code == URFPArgType.URFP_STRING:
|
|
99
|
+
t = self.name + ':' + 'string'
|
|
100
|
+
elif URFPArgType.URFP_INT <= self.code < URFPArgType.URFP_INT+0x08: # some int
|
|
101
|
+
width = 8 << (self.code & 3)
|
|
102
|
+
if self.code < URFPArgType.URFP_UNSIGNED:
|
|
103
|
+
t = f'{self.name}:i{width}'
|
|
104
|
+
else: # URFP_UNSIGNED
|
|
105
|
+
t = f'{self.name}:u{width}'
|
|
106
|
+
elif self.code == URFPArgType.URFP_FLOAT:
|
|
107
|
+
t = f'{self.name}:float'
|
|
108
|
+
elif self.code == URFPArgType.URFP_DOUBLE:
|
|
109
|
+
t = f'{self.name}:double'
|
|
110
|
+
elif self.code == URFPArgType.URFP_TYPE_INFO:
|
|
111
|
+
t = f'{self.name}:<type info>'
|
|
112
|
+
else:
|
|
113
|
+
t = f'{self.name}:<unknown({int(self.code)})>'
|
|
114
|
+
return t
|
urfp/console.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
""" Peer for talking to URFP console server """
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
import logging
|
|
6
|
+
import serial
|
|
7
|
+
|
|
8
|
+
class URFPConsoleChatter:
|
|
9
|
+
""" This class provides methods to talk to a URFP Console server """
|
|
10
|
+
|
|
11
|
+
def __init__(self, device="/dev/ttyS0", logger=None):
|
|
12
|
+
"""Constructor """
|
|
13
|
+
self._logger = (logger if logger else logging.getLogger(__name__))
|
|
14
|
+
self._serial = serial.Serial(device, 115200, timeout=1)
|
|
15
|
+
self._tag = 64 # ord() of most recently used tag
|
|
16
|
+
self._env = { }
|
|
17
|
+
|
|
18
|
+
def __exit__(self, type_, value, traceback):
|
|
19
|
+
self._serial.close()
|
|
20
|
+
|
|
21
|
+
def _send(self,q):
|
|
22
|
+
self._logger.debug('TX:%s', str(q))
|
|
23
|
+
for c in q:
|
|
24
|
+
self._serial.write([c])
|
|
25
|
+
time.sleep(0.01)
|
|
26
|
+
self._serial.write(b'\r')
|
|
27
|
+
time.sleep(0.01)
|
|
28
|
+
|
|
29
|
+
def _recv(self):
|
|
30
|
+
r = self._serial.readline()
|
|
31
|
+
if r:
|
|
32
|
+
self._logger.debug('RX:%s', str(r))
|
|
33
|
+
else:
|
|
34
|
+
self._logger.debug('-nothing-')
|
|
35
|
+
return r
|
|
36
|
+
|
|
37
|
+
##==--
|
|
38
|
+
|
|
39
|
+
def _next_tag(self, curr_tag):
|
|
40
|
+
next_tag = curr_tag + 1
|
|
41
|
+
if next_tag > ord('Z'):
|
|
42
|
+
next_tag = ord('A')
|
|
43
|
+
return next_tag
|
|
44
|
+
|
|
45
|
+
def perform_request(self, code, *args):
|
|
46
|
+
""" This method performs the request. 'code' is the two-letter code without the tag.
|
|
47
|
+
The tag is chosen and added by this method as appropriate. The args should be a dict
|
|
48
|
+
of data to transmit with the request. They will be serialized in the order in which
|
|
49
|
+
they appear in the dict (starting with Python 3.7) and must match whatever the server
|
|
50
|
+
expects regarding order, type and size. The serializer is work in progress, see serialize()
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
tag = None
|
|
54
|
+
new_tag = self._next_tag(self._tag)
|
|
55
|
+
while not tag and new_tag != self._tag:
|
|
56
|
+
if new_tag not in self._env:
|
|
57
|
+
tag = new_tag # found an unused tag
|
|
58
|
+
else:
|
|
59
|
+
new_tag = self._next_tag(new_tag)
|
|
60
|
+
|
|
61
|
+
if not tag:
|
|
62
|
+
self._logger.info("Didn't find an unique new tag for request")
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
self._env[tag] = { 'code':code, 'final': False, 'input': r'' }
|
|
66
|
+
|
|
67
|
+
# args is a dict. Only use the values
|
|
68
|
+
output = code + chr(tag) + ' ' + ' '.join(args[0].values())
|
|
69
|
+
|
|
70
|
+
# Transmit, with optional small delay between bytes (slow UART in device)
|
|
71
|
+
|
|
72
|
+
self._send(b'')
|
|
73
|
+
self._recv() # synced
|
|
74
|
+
|
|
75
|
+
self._send(bytes(output, encoding='ascii'))
|
|
76
|
+
self._recv() # ignore echo
|
|
77
|
+
|
|
78
|
+
# Receive. Todo: Check TAG
|
|
79
|
+
|
|
80
|
+
retval = None
|
|
81
|
+
final = r'\*' + code + '.'
|
|
82
|
+
line = self._recv()
|
|
83
|
+
while line:
|
|
84
|
+
line = line.decode('utf-8')
|
|
85
|
+
# Split line at whitespaces; keep strings enclosed with "" and remove the quotes
|
|
86
|
+
matches = re.findall(r'"(.*?)"|([^\s]+)', line)
|
|
87
|
+
words = [m[0] if m[0]!='' else m[1] for m in matches]
|
|
88
|
+
if re.match(final, words[0]):
|
|
89
|
+
return retval
|
|
90
|
+
# Return words in a list of lists
|
|
91
|
+
if len(words) > 0:
|
|
92
|
+
if retval is None:
|
|
93
|
+
retval = []
|
|
94
|
+
retval.append(words[1:-1])
|
|
95
|
+
line = self._recv()
|
|
96
|
+
return retval
|
|
97
|
+
|
|
98
|
+
def perform_list_request(self, cmd, listname, params):
|
|
99
|
+
""" retrieve array[l of (i32,string) pairs AKA (id,name) """
|
|
100
|
+
results:list[list[str,str]] = self.perform_request(cmd, params)
|
|
101
|
+
# Return as a JSON-like dictionary
|
|
102
|
+
json_results = {listname: [{"id": int(items[0]), "name": items[1]} for items in results]}
|
|
103
|
+
return json_results
|
|
104
|
+
|
|
105
|
+
def perform_help_request(self, elemtype, elemid):
|
|
106
|
+
""" Ask for details about element """
|
|
107
|
+
json_response = self.perform_request('h'+elemtype, {'id':str(elemid)})
|
|
108
|
+
help_dict = { 'desc': json_response['desc'], 'type': {}, 'args': {} }
|
|
109
|
+
for k in json_response:
|
|
110
|
+
if k != 'desc':
|
|
111
|
+
help_dict['type'][k] = json_response[k]
|