pyflind 0.1.2__tar.gz

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-0.1.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 FieldLine Industries, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pyflind-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyflind
3
+ Version: 0.1.2
4
+ Summary: A python library for FieldLine Industries products
5
+ Author-email: Matt Liss <matt@fieldlineindustries.com>, Ezra Godfrey <eg@fieldlineinc.com>, Andreas Griesshammer <ag@fieldlineinc.com>
6
+ Maintainer-email: Matt Liss <matt@fieldlineindustries.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://fieldlineindustries.com
9
+ Project-URL: Repository, https://bitbucket.org/fl-industries/fli-python-oss
10
+ Keywords: FieldLine,sm200,sm300,smx00
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy
17
+ Requires-Dist: pyserial
18
+ Dynamic: license-file
19
+
20
+ # Python FieldLineIndustries
21
+
22
+ A python library for interfacing with FieldLine Industries' products.
23
+
24
+ TODO link to readthedocs
@@ -0,0 +1,5 @@
1
+ # Python FieldLineIndustries
2
+
3
+ A python library for interfacing with FieldLine Industries' products.
4
+
5
+ TODO link to readthedocs
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3", "wheel", "setuptools-git-versioning>=2.0,<3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools-git-versioning]
6
+ template = "{tag}.{ccount}"
7
+ dev_template = "{tag}.{ccount}"
8
+ dirty_template = "{tag}.{ccount}+g{sha}-dirty"
9
+ enabled = true
10
+
11
+ [project]
12
+ name = "pyflind"
13
+ description = "A python library for FieldLine Industries products"
14
+ keywords = ["FieldLine", "sm200", "sm300", "smx00"]
15
+ dynamic = ["version"]
16
+ dependencies = ["numpy", "pyserial"]
17
+ requires-python = ">=3.8"
18
+ authors = [
19
+ { name="Matt Liss", email="matt@fieldlineindustries.com" },
20
+ { name="Ezra Godfrey", email="eg@fieldlineinc.com" },
21
+ { name="Andreas Griesshammer", email="ag@fieldlineinc.com" },
22
+ ]
23
+ maintainers = [
24
+ { name="Matt Liss", email="matt@fieldlineindustries.com" },
25
+ ]
26
+ readme = "README.md"
27
+ classifiers = [
28
+ "Programming Language :: Python :: 3",
29
+ "Operating System :: OS Independent",
30
+ ]
31
+ license="MIT"
32
+ license-files = ["LICEN[CS]E*"]
33
+
34
+ [project.scripts]
35
+ smx00-cli = "pyflind.smx00_cli:main"
36
+
37
+ [project.urls]
38
+ Homepage = "https://fieldlineindustries.com"
39
+ Repository = "https://bitbucket.org/fl-industries/fli-python-oss"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -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()
@@ -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