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 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