pyflind 0.1.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.
- pyflind/__init__.py +0 -0
- pyflind/bidirmap.py +41 -0
- pyflind/codec.py +156 -0
- pyflind/device.py +1243 -0
- pyflind/deviceio.py +277 -0
- pyflind/group.py +285 -0
- pyflind/recording.py +258 -0
- pyflind/smx00.py +573 -0
- pyflind/smx00_cli.py +185 -0
- pyflind/util.py +69 -0
- pyflind-0.1.2.dist-info/METADATA +24 -0
- pyflind-0.1.2.dist-info/RECORD +16 -0
- pyflind-0.1.2.dist-info/WHEEL +5 -0
- pyflind-0.1.2.dist-info/entry_points.txt +2 -0
- pyflind-0.1.2.dist-info/licenses/LICENSE +21 -0
- pyflind-0.1.2.dist-info/top_level.txt +1 -0
pyflind/__init__.py
ADDED
|
File without changes
|
pyflind/bidirmap.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
class BidirectionalStrIntMap(object):
|
|
3
|
+
''' Dictionary with bidirectional str<->int lookup.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self._dict = {}
|
|
8
|
+
|
|
9
|
+
def __getitem__(self, key):
|
|
10
|
+
if isinstance(key, int):
|
|
11
|
+
if key not in self._dict.values():
|
|
12
|
+
raise IndexError(key)
|
|
13
|
+
return list(self._dict.keys())[list(self._dict.values()).index(key)]
|
|
14
|
+
elif isinstance(key, str):
|
|
15
|
+
if key not in self._dict:
|
|
16
|
+
raise KeyError(key)
|
|
17
|
+
return self._dict[key]
|
|
18
|
+
else:
|
|
19
|
+
raise TypeError(key)
|
|
20
|
+
|
|
21
|
+
def __contains__(self, key):
|
|
22
|
+
return key in self._dict or key in self._dict.values()
|
|
23
|
+
|
|
24
|
+
def __repr__(self):
|
|
25
|
+
return self.__str__()
|
|
26
|
+
|
|
27
|
+
def __str__(self):
|
|
28
|
+
s = f'{type(self).__name__}: {{ \n'
|
|
29
|
+
for k, v in self._dict.items():
|
|
30
|
+
s += f' {v: 4d} (0x{v:02x}): {k}\n'
|
|
31
|
+
s += '}'
|
|
32
|
+
return s
|
|
33
|
+
|
|
34
|
+
def keys(self):
|
|
35
|
+
return self._dict.keys()
|
|
36
|
+
|
|
37
|
+
def values(self):
|
|
38
|
+
return self._dict.values()
|
|
39
|
+
|
|
40
|
+
def items(self):
|
|
41
|
+
return self._dict.items()
|
pyflind/codec.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import struct
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class V2SerialProtocolCodec:
|
|
6
|
+
'''Encoder/decoder for FLI serial protocol version 2.
|
|
7
|
+
'''
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.logger = logging.getLogger('V2SerialProtocolCodec')
|
|
11
|
+
self.data = b''
|
|
12
|
+
self.esc_char = False
|
|
13
|
+
self.valid_packet = False
|
|
14
|
+
self.dt_reg_read = 0x3
|
|
15
|
+
|
|
16
|
+
def push_char(self, in_char):
|
|
17
|
+
'''Push a received char to the decoder.
|
|
18
|
+
|
|
19
|
+
The decoder maintains state between calls and only returns data
|
|
20
|
+
values when the char completes a received packet to decode.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
in_char: received char
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
timestamp: device sample counter on complete packet, None otherwise
|
|
27
|
+
streams: dict of decoded stream values
|
|
28
|
+
reg_vals: list of register (address, value) tuples
|
|
29
|
+
'''
|
|
30
|
+
timestamp = None
|
|
31
|
+
streams = dict()
|
|
32
|
+
reg_vals = []
|
|
33
|
+
if not self.esc_char and in_char == 0x1B:
|
|
34
|
+
self.esc_char = True
|
|
35
|
+
elif self.esc_char:
|
|
36
|
+
self.esc_char = False
|
|
37
|
+
self.data += bytes([in_char])
|
|
38
|
+
elif in_char == 0x0A:
|
|
39
|
+
self.valid_packet = True
|
|
40
|
+
self.data = b''
|
|
41
|
+
elif self.valid_packet and in_char == 0x0D:
|
|
42
|
+
if len(self.data) < 7:
|
|
43
|
+
self.logger.error(f'not enough bytes in packet: {len(self.data)}')
|
|
44
|
+
self.data = b''
|
|
45
|
+
else:
|
|
46
|
+
timestamp = int.from_bytes(self.data[0:2], byteorder='big')
|
|
47
|
+
streams = dict()
|
|
48
|
+
self.data = self.data[2:]
|
|
49
|
+
while len(self.data):
|
|
50
|
+
datatype = int.from_bytes(self.data[0:1], byteorder='big')
|
|
51
|
+
try:
|
|
52
|
+
dataval = struct.unpack('>i', self.data[1:5])[0]
|
|
53
|
+
if datatype == self.dt_reg_read:
|
|
54
|
+
raw_reg = (dataval >> 16) & 0xFFFF
|
|
55
|
+
raw_val = dataval & 0xFFFF
|
|
56
|
+
reg_vals.append((raw_reg, raw_val))
|
|
57
|
+
else:
|
|
58
|
+
streams[datatype] = dataval
|
|
59
|
+
except struct.error as e:
|
|
60
|
+
self.logger.error(f'failed to unpack int: {e}')
|
|
61
|
+
timestamp = None
|
|
62
|
+
streams = dict()
|
|
63
|
+
reg_vals = []
|
|
64
|
+
self.data = b''
|
|
65
|
+
self.valid_packet = False
|
|
66
|
+
break
|
|
67
|
+
self.data = self.data[5:]
|
|
68
|
+
self.valid_packet = False
|
|
69
|
+
elif self.valid_packet:
|
|
70
|
+
self.data += bytes([in_char])
|
|
71
|
+
elif self.data != b'':
|
|
72
|
+
self.logger.warning(f'partial data read detected! self.data: {self.data}')
|
|
73
|
+
|
|
74
|
+
return timestamp, streams, reg_vals
|
|
75
|
+
|
|
76
|
+
def get_max_streams(self, baud, fs, crc=False):
|
|
77
|
+
'''Compute the max number of streams supported for a given baud and samplerate.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
baud: serial communications bitrate
|
|
81
|
+
fs: stream samplerate
|
|
82
|
+
crc: bool indicating state of CRC enable
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
int: max number of streams supportable
|
|
86
|
+
'''
|
|
87
|
+
bits_per_byte = 10 # 8 data, 1 start, 1 stop
|
|
88
|
+
overhead_bytes = 4 # framing and timestamp
|
|
89
|
+
if crc:
|
|
90
|
+
overhead_bytes += 2 # CRC-16
|
|
91
|
+
bytes_per_stream = 5 # 1 stream ID, 4 stream value
|
|
92
|
+
|
|
93
|
+
bytes_per_sample_avail = int(baud / bits_per_byte / fs) - overhead_bytes
|
|
94
|
+
|
|
95
|
+
return int(bytes_per_sample_avail/bytes_per_stream)
|
|
96
|
+
|
|
97
|
+
def int_to_hex_bytes(self, value, nbytes=2):
|
|
98
|
+
'''Convert integer to ascii encoded hex bytes array.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
value: integer value
|
|
102
|
+
nbytes: number of hex bytes to output (0-padded on left)
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bytes: encoded value
|
|
106
|
+
'''
|
|
107
|
+
if type(value) != int:
|
|
108
|
+
raise TypeError(value)
|
|
109
|
+
if value < 0 or value > 2**(nbytes*4)-1:
|
|
110
|
+
raise ValueError(value)
|
|
111
|
+
return f'{value:0{nbytes}X}'.encode('ascii')
|
|
112
|
+
|
|
113
|
+
def _one_time_read(self):
|
|
114
|
+
return b'#' + self.int_to_hex_bytes(3) + b'FFFF'
|
|
115
|
+
|
|
116
|
+
def one_time_read_cmd(self, address):
|
|
117
|
+
'''Build a command for one-time-read of a register.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
address: register address to read
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
bytes: encoded command
|
|
124
|
+
'''
|
|
125
|
+
cmd = b'@' + self.int_to_hex_bytes(3)
|
|
126
|
+
cmd += self.int_to_hex_bytes(address, nbytes=4)
|
|
127
|
+
cmd += self._one_time_read()
|
|
128
|
+
return cmd
|
|
129
|
+
|
|
130
|
+
def reg_write_cmd(self, address, value):
|
|
131
|
+
'''Build a command to write a register value.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
address: register address
|
|
135
|
+
value: register value
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
bytes: encoded command
|
|
139
|
+
'''
|
|
140
|
+
cmd = b'@' + self.int_to_hex_bytes(address)
|
|
141
|
+
cmd += self.int_to_hex_bytes(value, nbytes=4)
|
|
142
|
+
return cmd
|
|
143
|
+
|
|
144
|
+
def config_stream_cmd(self, sid, enable):
|
|
145
|
+
'''Build a command to configure a stream.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
sid: stream id
|
|
149
|
+
enable: True to enable, False to disable
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
bytes: encoded command
|
|
153
|
+
'''
|
|
154
|
+
cmd = b'#' + self.int_to_hex_bytes(sid)
|
|
155
|
+
cmd += self.int_to_hex_bytes(1 if enable else 0, nbytes=4)
|
|
156
|
+
return cmd
|