egauge-python 0.9.8__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.
- egauge/ctid/__init__.py +7 -0
- egauge/ctid/bit_stuffer.py +65 -0
- egauge/ctid/ctid.py +967 -0
- egauge/ctid/encoder.py +436 -0
- egauge/ctid/intel_hex_encoder.py +98 -0
- egauge/ctid/waveform.py +299 -0
- egauge/examples/data/test-ctid-decoder.raw +0 -0
- egauge/examples/test_capture.py +77 -0
- egauge/examples/test_common.py +26 -0
- egauge/examples/test_ctid.py +89 -0
- egauge/examples/test_ctid_decoder.py +93 -0
- egauge/examples/test_local.py +201 -0
- egauge/examples/test_register.py +104 -0
- egauge/loggers.py +72 -0
- egauge/pyside/__init__.py +0 -0
- egauge/pyside/ansi2html.py +112 -0
- egauge/pyside/terminal.py +295 -0
- egauge/webapi/__init__.py +34 -0
- egauge/webapi/auth.py +364 -0
- egauge/webapi/cloud/__init__.py +30 -0
- egauge/webapi/cloud/credentials.py +86 -0
- egauge/webapi/cloud/credentials_dialog.py +58 -0
- egauge/webapi/cloud/gui/credentials_dialog.py +100 -0
- egauge/webapi/cloud/serial_number.py +276 -0
- egauge/webapi/device/__init__.py +38 -0
- egauge/webapi/device/capture.py +453 -0
- egauge/webapi/device/ctid_info.py +553 -0
- egauge/webapi/device/device.py +349 -0
- egauge/webapi/device/local.py +268 -0
- egauge/webapi/device/physical_quantity.py +439 -0
- egauge/webapi/device/physical_units.py +473 -0
- egauge/webapi/device/register.py +338 -0
- egauge/webapi/device/register_row.py +145 -0
- egauge/webapi/device/register_type.py +851 -0
- egauge/webapi/device/slop.py +334 -0
- egauge/webapi/device/virtual_register.py +353 -0
- egauge/webapi/error.py +34 -0
- egauge/webapi/json_api.py +332 -0
- egauge_python-0.9.8.dist-info/METADATA +148 -0
- egauge_python-0.9.8.dist-info/RECORD +44 -0
- egauge_python-0.9.8.dist-info/WHEEL +5 -0
- egauge_python-0.9.8.dist-info/entry_points.txt +2 -0
- egauge_python-0.9.8.dist-info/licenses/LICENSE +22 -0
- egauge_python-0.9.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2020, 2022, 2024 eGauge Systems LLC
|
|
3
|
+
# 1644 Conestoga St, Suite 2
|
|
4
|
+
# Boulder, CO 80301
|
|
5
|
+
# voice: 720-545-9767
|
|
6
|
+
# email: davidm@egauge.net
|
|
7
|
+
#
|
|
8
|
+
# All rights reserved.
|
|
9
|
+
#
|
|
10
|
+
# MIT License
|
|
11
|
+
#
|
|
12
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
# in the Software without restriction, including without limitation the rights
|
|
15
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
# furnished to do so, subject to the following conditions:
|
|
18
|
+
#
|
|
19
|
+
# The above copyright notice and this permission notice shall be included in
|
|
20
|
+
# all copies or substantial portions of the Software.
|
|
21
|
+
#
|
|
22
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
28
|
+
# THE SOFTWARE.
|
|
29
|
+
#
|
|
30
|
+
"""This module provides access to the eGauge WebAPI's /api/register
|
|
31
|
+
service."""
|
|
32
|
+
|
|
33
|
+
import decimal
|
|
34
|
+
import json
|
|
35
|
+
|
|
36
|
+
from deprecated import deprecated
|
|
37
|
+
|
|
38
|
+
from ..error import Error
|
|
39
|
+
from ..json_api import JSONAPIError
|
|
40
|
+
from .device import Device
|
|
41
|
+
from .physical_quantity import PhysicalQuantity
|
|
42
|
+
from .register_row import RegisterRow
|
|
43
|
+
from .virtual_register import VirtualRegister
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RegisterError(Error):
|
|
47
|
+
"""Raised if for any register related errors."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def regnames_to_ranges(dev, regs):
|
|
51
|
+
"""Convert a list of register names to a register-ranges string."""
|
|
52
|
+
indices = set()
|
|
53
|
+
for name in regs:
|
|
54
|
+
formula = dev.reg_formula(name)
|
|
55
|
+
if formula is None:
|
|
56
|
+
indices.add(dev.reg_idx(name))
|
|
57
|
+
else:
|
|
58
|
+
for phys_reg in formula.phys_regs:
|
|
59
|
+
indices.add(dev.reg_idx(phys_reg))
|
|
60
|
+
indices = sorted(list(indices))
|
|
61
|
+
|
|
62
|
+
ranges = []
|
|
63
|
+
idx = 0
|
|
64
|
+
while idx < len(indices):
|
|
65
|
+
start = stop = indices[idx]
|
|
66
|
+
idx += 1
|
|
67
|
+
while idx < len(indices) and indices[idx] == stop + 1:
|
|
68
|
+
stop = indices[idx]
|
|
69
|
+
idx += 1
|
|
70
|
+
if start == stop:
|
|
71
|
+
ranges.append(str(start))
|
|
72
|
+
else:
|
|
73
|
+
ranges.append(str(start) + ":" + str(stop))
|
|
74
|
+
if len(ranges) < 1:
|
|
75
|
+
return None
|
|
76
|
+
return "+".join(ranges)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Register:
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
dev: Device,
|
|
83
|
+
params: dict | None = None,
|
|
84
|
+
regs: list[str] | None = None,
|
|
85
|
+
**kwargs,
|
|
86
|
+
):
|
|
87
|
+
"""Fetch register data from a meter.
|
|
88
|
+
|
|
89
|
+
Required arguments:
|
|
90
|
+
|
|
91
|
+
dev -- The device object to use to access the meter.
|
|
92
|
+
|
|
93
|
+
Keywoard arguments:
|
|
94
|
+
|
|
95
|
+
params -- A dictionary of query parameters that specify the
|
|
96
|
+
data to return (default `None`).
|
|
97
|
+
|
|
98
|
+
regs -- The list of registers to return (default `None`). If
|
|
99
|
+
`None`, all registers are returned. Otherwise,
|
|
100
|
+
data is returned only for the named registers.
|
|
101
|
+
|
|
102
|
+
Additional keyword arguments are passed along to the
|
|
103
|
+
requests.get() method.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
self.dev = dev
|
|
107
|
+
# maps regname to index in "register"/"row" arrays:
|
|
108
|
+
self.regorder = None
|
|
109
|
+
self.iter_range_idx = 0
|
|
110
|
+
self.iter_row_idx = 0
|
|
111
|
+
self.iter_ts = decimal.Decimal(0)
|
|
112
|
+
if params is None:
|
|
113
|
+
params = {}
|
|
114
|
+
self._requested_regs = regs
|
|
115
|
+
if regs is not None:
|
|
116
|
+
reg_ranges = regnames_to_ranges(dev, regs)
|
|
117
|
+
if reg_ranges is not None:
|
|
118
|
+
params["reg"] = reg_ranges
|
|
119
|
+
try:
|
|
120
|
+
self.raw = self.dev.get("/register", params=params, **kwargs)
|
|
121
|
+
except JSONAPIError as e:
|
|
122
|
+
raise RegisterError("failed to read registers: {e}", params) from e
|
|
123
|
+
|
|
124
|
+
def _create_regorder(self) -> dict[str, int]:
|
|
125
|
+
"""Create a dictionary mapping a register name to the index
|
|
126
|
+
within the `registers' and `row' arrays which contain the info
|
|
127
|
+
for that particular name.
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
regorder = {}
|
|
131
|
+
for index, reg in enumerate(self.raw["registers"]):
|
|
132
|
+
regorder[reg["name"]] = index
|
|
133
|
+
return regorder
|
|
134
|
+
|
|
135
|
+
def ts(self) -> decimal.Decimal | None:
|
|
136
|
+
"""Return the timestamp of the register rates.
|
|
137
|
+
|
|
138
|
+
The returned value is seconds since the Unix epoch.
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
if self.raw is None:
|
|
142
|
+
return None
|
|
143
|
+
return decimal.Decimal(self.raw["ts"])
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def regs(self) -> list[str]:
|
|
147
|
+
"""Return the list of available register names."""
|
|
148
|
+
if self._requested_regs:
|
|
149
|
+
return self._requested_regs
|
|
150
|
+
|
|
151
|
+
if self.regorder is None:
|
|
152
|
+
self.regorder = self._create_regorder()
|
|
153
|
+
|
|
154
|
+
return list(self.regorder.keys()) + self.dev.reg_virtuals()
|
|
155
|
+
|
|
156
|
+
def have(self, reg):
|
|
157
|
+
"""Check if a register is available (known).
|
|
158
|
+
|
|
159
|
+
Returns True if the register is known, False otherwise.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
Required arguments:
|
|
163
|
+
|
|
164
|
+
reg -- The name of the register to check for.
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
return reg in self.regs
|
|
168
|
+
|
|
169
|
+
def _rate(self, reg: str) -> float | None:
|
|
170
|
+
if self.raw is None:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
row = self.raw["registers"]
|
|
174
|
+
formula = self.dev.reg_formula(reg)
|
|
175
|
+
if isinstance(formula, VirtualRegister):
|
|
176
|
+
return formula.calc(lambda reg: self._row_rate(row, reg))
|
|
177
|
+
return self._row_rate(row, reg)
|
|
178
|
+
|
|
179
|
+
def _row_rate(self, row: list[dict], reg: str) -> float:
|
|
180
|
+
if self.regorder is None:
|
|
181
|
+
self.regorder = self._create_regorder()
|
|
182
|
+
|
|
183
|
+
idx = self.regorder[reg]
|
|
184
|
+
return row[idx]["rate"]
|
|
185
|
+
|
|
186
|
+
@deprecated(version="0.7.0", reason="use pq_rate() instead")
|
|
187
|
+
def rate(self, reg: str) -> float | None:
|
|
188
|
+
return self._rate(reg)
|
|
189
|
+
|
|
190
|
+
def pq_rate(self, reg: str) -> PhysicalQuantity | None:
|
|
191
|
+
"""Return the rate of change value of a register.
|
|
192
|
+
|
|
193
|
+
Returns `None` if the rate is unknown.
|
|
194
|
+
|
|
195
|
+
Required arguments:
|
|
196
|
+
|
|
197
|
+
reg -- The name of the register whose rate value to return.
|
|
198
|
+
|
|
199
|
+
"""
|
|
200
|
+
rate = self._rate(reg)
|
|
201
|
+
if rate is None:
|
|
202
|
+
return None
|
|
203
|
+
return PhysicalQuantity(
|
|
204
|
+
rate, self.type_code(reg), is_cumul=False
|
|
205
|
+
).to_preferred()
|
|
206
|
+
|
|
207
|
+
def type_code(self, reg) -> str:
|
|
208
|
+
"""Return the type-code of a register.
|
|
209
|
+
|
|
210
|
+
Required arguments:
|
|
211
|
+
|
|
212
|
+
reg -- The name of the register whose type-code to return.
|
|
213
|
+
|
|
214
|
+
"""
|
|
215
|
+
return self.dev.reg_type(reg)
|
|
216
|
+
|
|
217
|
+
@deprecated(version="0.7.0", reason="use Device.reg_formula() instead")
|
|
218
|
+
def formula(self, reg: str) -> str | None:
|
|
219
|
+
if self.raw is None:
|
|
220
|
+
return None
|
|
221
|
+
if self.regorder is None:
|
|
222
|
+
self.regorder = self._create_regorder()
|
|
223
|
+
return self.raw["registers"][self.regorder[reg]]["formula"]
|
|
224
|
+
|
|
225
|
+
def index(self, reg: str) -> int | None:
|
|
226
|
+
"""Return the register index of a register.
|
|
227
|
+
|
|
228
|
+
Required arguments:
|
|
229
|
+
|
|
230
|
+
reg -- The name of the register whose index to return.
|
|
231
|
+
|
|
232
|
+
"""
|
|
233
|
+
if self.raw is None:
|
|
234
|
+
return None
|
|
235
|
+
if self.regorder is None:
|
|
236
|
+
self.regorder = self._create_regorder()
|
|
237
|
+
return self.raw["registers"][self.regorder[reg]]["idx"]
|
|
238
|
+
|
|
239
|
+
def database_id(self, reg: str) -> int | None:
|
|
240
|
+
"""Return the database-id of a register.
|
|
241
|
+
|
|
242
|
+
Returns `None` if the register is unknown.
|
|
243
|
+
|
|
244
|
+
Required arguments:
|
|
245
|
+
|
|
246
|
+
reg -- The name of the register whose database id to return.
|
|
247
|
+
|
|
248
|
+
"""
|
|
249
|
+
if self.raw is None:
|
|
250
|
+
return None
|
|
251
|
+
if self.regorder is None:
|
|
252
|
+
self.regorder = self._create_regorder()
|
|
253
|
+
return self.raw["registers"][self.regorder[reg]]["did"]
|
|
254
|
+
|
|
255
|
+
def _make_row(
|
|
256
|
+
self, ts: decimal.Decimal, cumul_list: list[str]
|
|
257
|
+
) -> RegisterRow:
|
|
258
|
+
"""Convert a "rows" array to a RegisterRow()."""
|
|
259
|
+
row = RegisterRow(ts)
|
|
260
|
+
|
|
261
|
+
# first, fill in the physical registers:
|
|
262
|
+
phys = {}
|
|
263
|
+
for idx, cumul in enumerate(cumul_list):
|
|
264
|
+
ri = self.raw["registers"][idx]
|
|
265
|
+
reg = ri["name"]
|
|
266
|
+
cumul = int(cumul)
|
|
267
|
+
phys[reg] = cumul
|
|
268
|
+
if self._requested_regs and reg not in self._requested_regs:
|
|
269
|
+
continue
|
|
270
|
+
row.regs[reg] = cumul
|
|
271
|
+
row.type_codes[reg] = self.type_code(reg)
|
|
272
|
+
|
|
273
|
+
# second, fill in virtual registers (if any):
|
|
274
|
+
for reg in self.dev.reg_virtuals():
|
|
275
|
+
if self._requested_regs and reg not in self._requested_regs:
|
|
276
|
+
continue
|
|
277
|
+
formula = self.dev.reg_formula(reg)
|
|
278
|
+
if not isinstance(formula, VirtualRegister):
|
|
279
|
+
raise RegisterError(
|
|
280
|
+
f"Formula for virtual register {reg} is missing."
|
|
281
|
+
)
|
|
282
|
+
row.regs[reg] = formula.calc(lambda reg: phys[reg])
|
|
283
|
+
row.type_codes[reg] = self.type_code(reg)
|
|
284
|
+
return row
|
|
285
|
+
|
|
286
|
+
def __getitem__(self, index: int) -> RegisterRow:
|
|
287
|
+
"""Return the n-th row of cumulative register values as a
|
|
288
|
+
RegisterRow.
|
|
289
|
+
|
|
290
|
+
"""
|
|
291
|
+
range_idx = 0
|
|
292
|
+
while True:
|
|
293
|
+
curr_range = self.raw["ranges"][range_idx]
|
|
294
|
+
ts = decimal.Decimal(curr_range["ts"])
|
|
295
|
+
if index < len(curr_range["rows"]):
|
|
296
|
+
ts -= index * decimal.Decimal(str(curr_range["delta"]))
|
|
297
|
+
return self._make_row(ts, curr_range["rows"][index])
|
|
298
|
+
range_idx += 1
|
|
299
|
+
index -= len(curr_range["rows"])
|
|
300
|
+
|
|
301
|
+
def __iter__(self):
|
|
302
|
+
"""Iterate over cumulative register values.
|
|
303
|
+
|
|
304
|
+
Each iteration returns one row of data as a RegisterRow.
|
|
305
|
+
|
|
306
|
+
"""
|
|
307
|
+
if self.raw is None:
|
|
308
|
+
return None
|
|
309
|
+
self.iter_range_idx = 0
|
|
310
|
+
self.iter_row_idx = 0
|
|
311
|
+
return self
|
|
312
|
+
|
|
313
|
+
def __next__(self) -> RegisterRow:
|
|
314
|
+
if (
|
|
315
|
+
not isinstance(self.raw, dict)
|
|
316
|
+
or "ranges" not in self.raw
|
|
317
|
+
or self.iter_range_idx >= len(self.raw["ranges"])
|
|
318
|
+
):
|
|
319
|
+
raise StopIteration
|
|
320
|
+
curr_range = self.raw["ranges"][self.iter_range_idx]
|
|
321
|
+
|
|
322
|
+
if self.iter_row_idx == 0:
|
|
323
|
+
self.iter_ts = decimal.Decimal(curr_range["ts"])
|
|
324
|
+
|
|
325
|
+
row = self._make_row(
|
|
326
|
+
self.iter_ts, curr_range["rows"][self.iter_row_idx]
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
self.iter_ts -= decimal.Decimal(str(curr_range["delta"]))
|
|
330
|
+
self.iter_row_idx += 1
|
|
331
|
+
if self.iter_row_idx >= len(curr_range["rows"]):
|
|
332
|
+
self.iter_row_idx = 0
|
|
333
|
+
self.iter_range_idx += 1
|
|
334
|
+
return row
|
|
335
|
+
|
|
336
|
+
def __str__(self) -> str:
|
|
337
|
+
"""Returns the raw register data as a JSON-encoded string."""
|
|
338
|
+
return json.dumps(self.raw)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2020, 2022-2023 eGauge Systems LLC
|
|
3
|
+
# 1644 Conestoga St, Suite 2
|
|
4
|
+
# Boulder, CO 80301
|
|
5
|
+
# voice: 720-545-9767
|
|
6
|
+
# email: davidm@egauge.net
|
|
7
|
+
#
|
|
8
|
+
# All rights reserved.
|
|
9
|
+
#
|
|
10
|
+
# MIT License
|
|
11
|
+
#
|
|
12
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
# in the Software without restriction, including without limitation the rights
|
|
15
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
# furnished to do so, subject to the following conditions:
|
|
18
|
+
#
|
|
19
|
+
# The above copyright notice and this permission notice shall be included in
|
|
20
|
+
# all copies or substantial portions of the Software.
|
|
21
|
+
#
|
|
22
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
28
|
+
# THE SOFTWARE.
|
|
29
|
+
#
|
|
30
|
+
"""This module provides a helper class for a row of time-stamped
|
|
31
|
+
register data."""
|
|
32
|
+
|
|
33
|
+
import copy
|
|
34
|
+
import json
|
|
35
|
+
|
|
36
|
+
from deprecated import deprecated
|
|
37
|
+
|
|
38
|
+
from ..error import Error
|
|
39
|
+
from .physical_quantity import PhysicalQuantity
|
|
40
|
+
from .register_type import Units
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RegRowError(Error):
|
|
44
|
+
"""All errors in this module raise this exception."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RegisterRow:
|
|
48
|
+
"""A row of register data contains a timestamp and the signed 64-bit
|
|
49
|
+
register values for that timestamp. Subtracing one row from
|
|
50
|
+
another creates a difference row. On a difference row,
|
|
51
|
+
RegisterRow.pq_accu() can be called to get the amount by which a
|
|
52
|
+
register changed between the two rows.
|
|
53
|
+
|
|
54
|
+
Similarly, RegisterRow.pq_avg() can be called to calculate the
|
|
55
|
+
average register value that was in effect between the two rows."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, ts, regs=None, type_codes=None, is_diff=False):
|
|
58
|
+
self.ts = ts
|
|
59
|
+
self.is_diff = is_diff
|
|
60
|
+
if regs is None:
|
|
61
|
+
self.regs = {}
|
|
62
|
+
else:
|
|
63
|
+
self.regs = copy.copy(regs)
|
|
64
|
+
if type_codes is None:
|
|
65
|
+
self.type_codes = {}
|
|
66
|
+
else:
|
|
67
|
+
self.type_codes = copy.copy(type_codes)
|
|
68
|
+
|
|
69
|
+
@deprecated(version="0.7.0", reason="use pq_avg() instead")
|
|
70
|
+
def avg(self, regname) -> float:
|
|
71
|
+
"""Return the time-average of the register value."""
|
|
72
|
+
self._assert(self.is_diff)
|
|
73
|
+
return self.regs[regname] / self.ts
|
|
74
|
+
|
|
75
|
+
def pq_rate(self, regname) -> PhysicalQuantity:
|
|
76
|
+
"""Return register rate of change as a physical quantity in the
|
|
77
|
+
preferred unit of the default unit system (see
|
|
78
|
+
PhysicalQuantity.set_unit_system()).
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
self._assert(self.is_diff)
|
|
82
|
+
val = self.regs[regname]
|
|
83
|
+
return PhysicalQuantity(
|
|
84
|
+
val, self.type_codes[regname], is_cumul=False
|
|
85
|
+
).to_preferred()
|
|
86
|
+
|
|
87
|
+
def pq_avg(self, regname) -> PhysicalQuantity:
|
|
88
|
+
"""Return the time-average of the register value as a physical
|
|
89
|
+
quantity in the preferred unit of the default unit system
|
|
90
|
+
(see PhysicalQuantity.set_unit_system())."""
|
|
91
|
+
self._assert(self.is_diff)
|
|
92
|
+
ute = Units.table[self.type_codes[regname]]
|
|
93
|
+
val = self.regs[regname] / ute.fix_scale / self.ts
|
|
94
|
+
return PhysicalQuantity(
|
|
95
|
+
val, self.type_codes[regname], is_cumul=False
|
|
96
|
+
).to_preferred()
|
|
97
|
+
|
|
98
|
+
def pq_accu(self, regname):
|
|
99
|
+
"""Return the accumulated register value as a physical quantity in the
|
|
100
|
+
preferred unit of the default unit system (see
|
|
101
|
+
PhysicalQuantity.set_unit_system())."""
|
|
102
|
+
ute = Units.table[self.type_codes[regname]]
|
|
103
|
+
val = self.regs[regname] * ute.cumul_scale
|
|
104
|
+
|
|
105
|
+
# If the row is not a difference row, we cannot convert
|
|
106
|
+
# between units with additive constants (e.g., °C·d to °F·d).
|
|
107
|
+
# Set `dt` to None in that case, so those conversions will
|
|
108
|
+
# fail visibily:
|
|
109
|
+
dt = self.ts if self.is_diff else None
|
|
110
|
+
return PhysicalQuantity(
|
|
111
|
+
val, self.type_codes[regname], is_cumul=True
|
|
112
|
+
).to_preferred(dt=dt)
|
|
113
|
+
|
|
114
|
+
def __sub__(self, subtrahend):
|
|
115
|
+
"""Subtract two register rows from each other and return the result.
|
|
116
|
+
The SUBTRAHEND must have values for all of the registers in
|
|
117
|
+
the minuend row.
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
self._assert(not self.is_diff)
|
|
121
|
+
subtrahend._assert(not self.is_diff)
|
|
122
|
+
|
|
123
|
+
ret = RegisterRow(self.ts, self.regs, self.type_codes)
|
|
124
|
+
ret.ts = float(ret.ts - subtrahend.ts)
|
|
125
|
+
ret.is_diff = True
|
|
126
|
+
for name in self.regs:
|
|
127
|
+
ret.regs[name] -= subtrahend.regs[name]
|
|
128
|
+
return ret
|
|
129
|
+
|
|
130
|
+
def _assert(self, cond: bool):
|
|
131
|
+
if cond:
|
|
132
|
+
return
|
|
133
|
+
if self.is_diff:
|
|
134
|
+
raise RegRowError("Cannot subtract a difference row.")
|
|
135
|
+
raise RegRowError("The row must be a difference.")
|
|
136
|
+
|
|
137
|
+
def __str__(self):
|
|
138
|
+
return f"{self.ts}: {json.dumps(self.regs)}"
|
|
139
|
+
|
|
140
|
+
def __iter__(self):
|
|
141
|
+
"""Iterate over cumulative register values. Each iteration returns
|
|
142
|
+
one row of data as a RegisterRow.
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
return iter(self.regs)
|