microstrain-daq-utils 0.9.0b0__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.
daq_utils/__init__.py ADDED
@@ -0,0 +1,316 @@
1
+ import opendaq as daq
2
+
3
+
4
+ class DaqTypeFactory:
5
+ """Creates openDAQ typed values (enumerations, structs) without requiring explicit type manager or string wrapping.
6
+
7
+ Args:
8
+ instance: The openDAQ Instance to retrieve the type manager from.
9
+
10
+ Example:
11
+ daq_types = daq_utils.DaqTypeFactory(instance)
12
+ voltage = daq_types.enum("MSCL_Wireless_Voltage", "voltage_3000mV")
13
+ """
14
+
15
+ def __init__(self, instance):
16
+ self._instance = instance
17
+ self._type_manager = instance.context.type_manager
18
+
19
+ def enum(self, type_name, value_name):
20
+ """Creates an openDAQ Enumeration value.
21
+
22
+ Args:
23
+ type_name: The openDAQ enum type name.
24
+ value_name: The enum value name.
25
+
26
+ Example:
27
+ voltage = daq_types.enum("MSCL_Wireless_Voltage", "voltage_3000mV")
28
+ """
29
+ return daq.Enumeration(daq.String(type_name), daq.String(value_name), self._type_manager)
30
+
31
+ def struct(self, type_name, fields):
32
+ """Creates an openDAQ Struct value.
33
+
34
+ Args:
35
+ type_name: The openDAQ struct type name.
36
+ fields: A dict containing the struct's field names and values. Python primitives
37
+ (bool, int, float) are converted to their openDAQ equivalents automatically.
38
+ openDAQ types (e.g. Enumeration) are passed through as-is.
39
+
40
+ Example:
41
+ linear_eq = daq_types.struct("MSCL_Wireless_LinearEquation", {"Slope": 1.0, "Offset": 0.0})
42
+ """
43
+ daq_fields = daq.Dict()
44
+
45
+ for key, value in fields.items():
46
+ if isinstance(value, bool):
47
+ daq_fields[key] = daq.Boolean(value)
48
+ elif isinstance(value, int):
49
+ daq_fields[key] = daq.Integer(value)
50
+ elif isinstance(value, float):
51
+ daq_fields[key] = daq.Float(value)
52
+ else:
53
+ daq_fields[key] = value
54
+
55
+ return daq.Struct(daq.String(type_name), daq_fields, self._type_manager)
56
+
57
+
58
+ class DaqTypeInspector:
59
+ """Inspects openDAQ registered types without requiring an explicit instance argument on each call.
60
+
61
+ Args:
62
+ instance: The openDAQ Instance to retrieve the type manager from.
63
+
64
+ Example:
65
+ inspector = daq_utils.DaqTypeInspector(instance)
66
+ inspector.describe("MSCL_Wireless_ShuntCalCmdInfo")
67
+ inspector.describe("MSCL_Wireless_Voltage")
68
+ """
69
+
70
+ def __init__(self, instance):
71
+ self._instance = instance
72
+ # Releasing an IEnumerationType Python wrapper corrupts the underlying C++ object in the
73
+ # openDAQ Python binding, causing a segfault on any subsequent call for the same type. Caching
74
+ # the names as plain Python strings after the first call avoids touching the C++ object again.
75
+ #
76
+ # NOTE: Temporary workaround — remove once the openDAQ version is updated with a fix.
77
+ self._enum_name_cache: dict[str, list[str]] = {}
78
+
79
+ def describe(self, type_name):
80
+ """Prints the fields and values for a registered enum or struct type.
81
+
82
+ Args:
83
+ type_name: The full registered type name.
84
+
85
+ Example:
86
+ inspector.describe("MSCL_Wireless_AutoCalCompletionFlag")
87
+ inspector.describe("MSCL_Wireless_LinearEquation")
88
+ """
89
+ type_obj = self._instance.context.type_manager.get_type(type_name)
90
+
91
+ if daq.IEnumerationType.can_cast_from(type_obj):
92
+ self._describe_enum(type_name, type_obj)
93
+ else:
94
+ self._describe_struct(type_name, type_obj)
95
+
96
+ def _describe_enum(self, type_name, type_obj):
97
+ if type_name not in self._enum_name_cache:
98
+ enum_type = daq.IEnumerationType.cast_from(type_obj)
99
+ self._enum_name_cache[type_name] = [str(k) for k in enum_type.as_dictionary]
100
+
101
+ names = self._enum_name_cache[type_name]
102
+ header = 'Enumerator'
103
+ width = max(len(header), max(len(str(n)) for n in names))
104
+
105
+ print()
106
+ print(f'{header:<{width}}')
107
+ print('-' * width)
108
+
109
+ for name in names:
110
+ print(str(name))
111
+
112
+ print()
113
+
114
+ def _describe_struct(self, type_name, type_obj):
115
+ struct_type = daq.IStructType.cast_from(type_obj)
116
+ builder = daq.StructBuilder(daq.String(type_name), self._instance.context.type_manager)
117
+
118
+ rows = [
119
+ (str(name), _field_type_label(value))
120
+ for name, value in zip(struct_type.field_names, builder.field_values)
121
+ ]
122
+ headers = ('Field', 'Type')
123
+ col_widths = [max(len(r[i]) for r in rows + [headers]) for i in range(2)]
124
+
125
+ print()
126
+ print(f'{headers[0]:<{col_widths[0]}} | {headers[1]:<{col_widths[1]}}')
127
+ print(f'{"-" * col_widths[0]}-+-{"-" * col_widths[1]}')
128
+
129
+ for name, type_str in rows:
130
+ print(f'{name:<{col_widths[0]}} | {type_str:<{col_widths[1]}}')
131
+
132
+ print()
133
+
134
+
135
+ def _field_type_label(value):
136
+ if value is None:
137
+ return '?'
138
+ if isinstance(value, bool):
139
+ return 'Bool'
140
+ if isinstance(value, int):
141
+ return 'Int'
142
+ if isinstance(value, float):
143
+ return 'Float'
144
+ if isinstance(value, str):
145
+ return 'String'
146
+ if daq.IEnumeration.can_cast_from(value):
147
+ return f'Enumeration[{daq.IEnumeration.cast_from(value).enumeration_type.name}]'
148
+ try:
149
+ return f'Struct[{value.struct_type.name}]'
150
+ except AttributeError:
151
+ return type(value).__name__
152
+
153
+
154
+ def _prop_type_label(obj, prop):
155
+ vt = prop.value_type
156
+ if vt == daq.CoreType.ctBool:
157
+ return 'Bool'
158
+ if vt == daq.CoreType.ctInt:
159
+ return 'Int'
160
+ if vt == daq.CoreType.ctFloat:
161
+ return 'Float'
162
+ if vt == daq.CoreType.ctString:
163
+ return 'String'
164
+ if vt == daq.CoreType.ctEnumeration:
165
+ try:
166
+ value = obj.get_property_value(prop.name)
167
+ return f'Enumeration[{daq.IEnumeration.cast_from(value).enumeration_type.name}]'
168
+ except Exception:
169
+ return 'Enumeration'
170
+ if vt == daq.CoreType.ctStruct:
171
+ try:
172
+ return f'Struct[{obj.get_property_value(prop.name).struct_type.name}]'
173
+ except Exception:
174
+ return 'Struct'
175
+ if vt == daq.CoreType.ctList:
176
+ try:
177
+ items = obj.get_property_value(prop.name)
178
+ first = next(iter(items), None)
179
+ if first is not None:
180
+ return f'List<{_field_type_label(first)}>'
181
+ except Exception:
182
+ pass
183
+ return 'List'
184
+ if vt == daq.CoreType.ctDict:
185
+ return 'Dict'
186
+ raw = str(vt).split('CoreType.')[-1]
187
+ return raw[2:] if raw.startswith('ct') else raw
188
+
189
+
190
+ def call(root, path, *args):
191
+ """Calls an openDAQ function property and return its result.
192
+
193
+ This function handles casting to a callable automatically.
194
+
195
+ Args:
196
+ root: The root openDAQ property object to start from.
197
+ path: The name or dot-notation path from the root to the function property.
198
+ *args: Optional arguments to pass to the function property.
199
+
200
+ Returns:
201
+ The result returned by the function property, typically a PropertyObject.
202
+
203
+ Raises:
204
+ RuntimeError: If the property does not exist or cannot be cast properly.
205
+
206
+ Example:
207
+ # Function with no arguments
208
+ result = call(channel, "Setup.Configure.Apply")
209
+
210
+ # Function with arguments
211
+ result = call(channel, "Capabilities.ChannelType", 1)
212
+
213
+ # Query the result
214
+ print(result.get_property_value("Success"))
215
+ """
216
+ return daq.IFunction.cast_from(root.get_property_value(path))(*args)
217
+
218
+
219
+ def _depth_first_search(root, prefix=''):
220
+ """Depth-first search generator that yields (obj, prop, prefix, path) for every visible property."""
221
+
222
+ # Uses a stack with reversed insertion to maintain original property order. This keeps related subgroups
223
+ # adjacent in output (e.g. [Setup, Setup.Configure, Setup.Configure.Sampling, ...]) vs. breadth-first which
224
+ # interleaves levels (e.g. [Setup, Capabilities, ..., Setup.Configure, ...]).
225
+ stack = [(root, prefix)]
226
+
227
+ while stack:
228
+ obj, prefix = stack.pop()
229
+ subgroups = []
230
+
231
+ for prop in obj.visible_properties:
232
+ path = f'{prefix}.{prop.name}' if prefix else prop.name
233
+ yield obj, prop, prefix, path
234
+ if prop.value_type == daq.CoreType.ctObject:
235
+ subgroups.append((obj.get_property_value(prop.name), path))
236
+
237
+ subgroups.reverse()
238
+ stack.extend(subgroups)
239
+
240
+
241
+ def find(root, name):
242
+ """Returns the full dot-notation path of a property or group given its name.
243
+
244
+ Searches all groups and subgroups. Returns 'Not found' if no match is found.
245
+
246
+ Args:
247
+ root: The openDAQ object to search (channel, device, group, etc.).
248
+ name: The property or group name to search for.
249
+
250
+ Example:
251
+ find(channel, 'LostBeaconTimeout')
252
+ # => 'Setup.Configure.Sampling.LostBeaconTimeout'
253
+
254
+ find(channel, 'Sampling')
255
+ # => 'Setup.Configure.Sampling'
256
+ """
257
+ return next(
258
+ (path for _, prop, _, path in _depth_first_search(root) if prop.name == name),
259
+ 'Not found'
260
+ )
261
+
262
+
263
+ def groups(root):
264
+ """Returns all property groups and subgroups as dot-notation path strings.
265
+
266
+ Args:
267
+ root: The openDAQ object to query property groups from (channel, device, group, etc.).
268
+ """
269
+ return [
270
+ path for _, prop, _, path in _depth_first_search(root)
271
+ if prop.value_type == daq.CoreType.ctObject
272
+ ]
273
+
274
+
275
+ def print_groups(root):
276
+ """Prints all property groups and subgroups using dot-notation paths, sorted alphabetically.
277
+
278
+ Args:
279
+ root: The openDAQ object to query property groups from (channel, device, group, etc.).
280
+ """
281
+ print('\n'.join(sorted(groups(root))))
282
+
283
+
284
+ def properties(root, group=None):
285
+ """Returns properties as a list of (group, name, type) tuples.
286
+
287
+ Args:
288
+ root: The openDAQ object to query properties from.
289
+ group: Optional dot-notation group path to filter properties.
290
+ """
291
+ start_obj = root.get_property_value(group) if group else root
292
+ start_prefix = group or ''
293
+
294
+ return [
295
+ (prefix or 'None', prop.name, _prop_type_label(obj, prop))
296
+ for obj, prop, prefix, _ in _depth_first_search(start_obj, start_prefix)
297
+ if prop.value_type != daq.CoreType.ctObject
298
+ ]
299
+
300
+
301
+ def print_properties(root, group=None):
302
+ """Prints properties as an aligned table, sorted alphabetically by group then property name.
303
+
304
+ Args:
305
+ root: The openDAQ object to query properties from.
306
+ group: Optional dot-notation group path to filter properties.
307
+ """
308
+ rows = sorted(properties(root, group), key=lambda r: (r[0], r[1]))
309
+ headers = ('Group', 'Property', 'Type')
310
+ col_widths = [max(len(r[i]) for r in rows + [headers]) for i in range(3)]
311
+
312
+ print(f'{headers[0]:<{col_widths[0]}} | {headers[1]:<{col_widths[1]}} | {headers[2]:<{col_widths[2]}}')
313
+ print(f'{"-" * col_widths[0]}-+-{"-" * col_widths[1]}-+-{"-" * col_widths[2]}')
314
+
315
+ for row in rows:
316
+ print(f'{row[0]:<{col_widths[0]}} | {row[1]:<{col_widths[1]}} | {row[2]:<{col_widths[2]}}')
daq_utils/wireless.py ADDED
@@ -0,0 +1,31 @@
1
+ import opendaq as daq
2
+
3
+
4
+ def list_nodes(device):
5
+ """Prints a table of all wireless nodes discovered by the device.
6
+
7
+ Displays each node's index, ID, and model name.
8
+
9
+ Args:
10
+ device: The openDAQ device (wireless base station).
11
+ """
12
+ channels = device.get_channels()
13
+
14
+ if not channels:
15
+ print('No nodes found.')
16
+ return
17
+
18
+ rows = [(str(i), channel.name) for i, channel in enumerate(channels)]
19
+
20
+ headers = ('#', 'Model (Node ID)')
21
+ col_widths = [max(len(r[i]) for r in rows + [headers]) for i in range(2)]
22
+
23
+ print()
24
+
25
+ print(f'{headers[0]:<{col_widths[0]}} | {headers[1]:<{col_widths[1]}}')
26
+ print(f'{"-" * col_widths[0]}-+-{"-" * col_widths[1]}')
27
+
28
+ for row in rows:
29
+ print(f'{row[0]:<{col_widths[0]}} | {row[1]:<{col_widths[1]}}')
30
+
31
+ print()
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: microstrain-daq-utils
3
+ Version: 0.9.0b0
4
+ Summary: An opinionated library that enhances working with openDAQ modules from MicroStrain by HBK.
5
+ Maintainer-email: MicroStrain by HBK <microstrainsupport@hbkworld.com>
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: opendaq==3.30.3
8
+ Provides-Extra: jupyter
9
+ Requires-Dist: microstrain-daq-jupyter; extra == 'jupyter'
@@ -0,0 +1,5 @@
1
+ daq_utils/__init__.py,sha256=qw96V4sCGld-cK5z9deY5JJt5tobKllLzPz7fADFCfM,11091
2
+ daq_utils/wireless.py,sha256=4CUt4XQczoChlWDFcKnMPXNHVDGV097D-q0tzMzSwJI,807
3
+ microstrain_daq_utils-0.9.0b0.dist-info/METADATA,sha256=BpRk76JcmSTYRTu_Pty_ui422KVz8_HJ2NsaQFB6CWQ,375
4
+ microstrain_daq_utils-0.9.0b0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ microstrain_daq_utils-0.9.0b0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any