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,439 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 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
|
+
import re
|
|
31
|
+
from typing import Self
|
|
32
|
+
|
|
33
|
+
from .physical_units import PhysicalValue
|
|
34
|
+
from .register_type import RegisterType, Units, UnitSystem, UnitTableEntry
|
|
35
|
+
|
|
36
|
+
UNIT_PATTERN = re.compile(r"(.*)/(.*)$")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Error(Exception):
|
|
40
|
+
"""Base class for all exceptions raised by this module."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PhysicalQuantity:
|
|
44
|
+
"""A physical quantity is a measurement that is expressed as a value
|
|
45
|
+
and a a unit, for example, 10 kW has a value of 10 and a unit of
|
|
46
|
+
"kW"."""
|
|
47
|
+
|
|
48
|
+
# The currency symbol/string to use for the monetary units:
|
|
49
|
+
_currency: str = "***call set_currency()***"
|
|
50
|
+
|
|
51
|
+
# The unit system PhysicalQuantity.to_preferred() should use by default.
|
|
52
|
+
_default_unit_system = UnitSystem.METRIC
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def is_preferred(unit_system: UnitSystem, unit: str) -> bool:
|
|
56
|
+
"""Check if a unit is preferred in the given unit system.
|
|
57
|
+
|
|
58
|
+
Returns `True` if the unit is preferred in the given unit
|
|
59
|
+
system.
|
|
60
|
+
|
|
61
|
+
Required arguments:
|
|
62
|
+
|
|
63
|
+
unit_system -- The unit system to check against.
|
|
64
|
+
|
|
65
|
+
unit -- The unit name to check.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
return unit in Units.preferred[unit_system.value]
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def time_integrated_unit(unit: str) -> str:
|
|
72
|
+
"""Given a quantity of a particular unit, derive the unit that
|
|
73
|
+
results when integrating the quantity over time.
|
|
74
|
+
|
|
75
|
+
This basically appends the string "·s" to the unit name,
|
|
76
|
+
except this method knows to simplify certain units. For
|
|
77
|
+
example, 'm^3/s·s' is simplified to 'm^3'.
|
|
78
|
+
|
|
79
|
+
Required arguments:
|
|
80
|
+
|
|
81
|
+
unit -- The unit name to integrate over time.
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
m = UNIT_PATTERN.match(unit)
|
|
85
|
+
if m and m.lastindex and m.lastindex >= 2:
|
|
86
|
+
prefix = m.group(1)
|
|
87
|
+
suffix = m.group(2)
|
|
88
|
+
if suffix == "s":
|
|
89
|
+
# ${prefix}/s·s cancels out:
|
|
90
|
+
return prefix
|
|
91
|
+
if prefix == "":
|
|
92
|
+
# "/${suffix}" => "s/${suffix}"
|
|
93
|
+
return "s/" + suffix
|
|
94
|
+
return prefix + "·s/" + suffix
|
|
95
|
+
if unit == "":
|
|
96
|
+
return "s"
|
|
97
|
+
return unit + "·s"
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def available_units(cls, type_code: str, is_cumul: bool) -> list[str]:
|
|
101
|
+
entry = Units.table.get(type_code)
|
|
102
|
+
|
|
103
|
+
if not isinstance(entry, UnitTableEntry):
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
unit = entry.cumul_unit if is_cumul else entry.rate_unit
|
|
107
|
+
if unit is None:
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
return Units.units.alternate_units(unit)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def scales(cls, unit: str) -> list[str]:
|
|
114
|
+
return Units.units.scaled_units(unit)
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
value: float | PhysicalValue,
|
|
119
|
+
type_code: str | None = None,
|
|
120
|
+
is_cumul: bool = False,
|
|
121
|
+
unit: str | None = None,
|
|
122
|
+
diff: bool = False,
|
|
123
|
+
):
|
|
124
|
+
"""Create a PhysicalQuantity object.
|
|
125
|
+
|
|
126
|
+
The object can be created based on a magnitude and either its
|
|
127
|
+
unit or its type-code. Alternatively, the object can also be
|
|
128
|
+
created directly from a PhysicalValue object.
|
|
129
|
+
|
|
130
|
+
Required arguments:
|
|
131
|
+
|
|
132
|
+
value -- The magnitude of the physical quantity to create or
|
|
133
|
+
the PhysicalValue object to create the quantity for.
|
|
134
|
+
|
|
135
|
+
Keyword arguments:
|
|
136
|
+
|
|
137
|
+
type_code -- The type-code to use to determine the physical
|
|
138
|
+
unit of the created quantity.
|
|
139
|
+
|
|
140
|
+
is_cumul -- If `type_code` is specified, this must be `True`
|
|
141
|
+
if the value is cumulative (rather than a rate-of-change).
|
|
142
|
+
|
|
143
|
+
diff -- If `unit` is specified or if `value` is a
|
|
144
|
+
PhysicalValue object, this argument must be `True` if the
|
|
145
|
+
created quantity should represent a change in value (i.e.,
|
|
146
|
+
a difference). This argument is relevant only for unit
|
|
147
|
+
conversions that involve an additive term, as is the case,
|
|
148
|
+
e.g., when converting temperature from celsius to
|
|
149
|
+
fahrenheit or kelvin. For example, 0°C equals 32°F as an
|
|
150
|
+
absolute temperature, but a temperature change of 0°C
|
|
151
|
+
corresponds to a temperature change of 0°F, so for the
|
|
152
|
+
second case, this argument would have to be set to `True`
|
|
153
|
+
to yield correct unit conversions.
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
if isinstance(value, PhysicalValue):
|
|
157
|
+
self.pv = value
|
|
158
|
+
self.diff = diff
|
|
159
|
+
else:
|
|
160
|
+
if unit is None:
|
|
161
|
+
if type_code is None:
|
|
162
|
+
raise Error("Unit or type-code must be specified.")
|
|
163
|
+
ute = Units.table[type_code]
|
|
164
|
+
unit = ute.cumul_unit if is_cumul else ute.rate_unit
|
|
165
|
+
if unit is None:
|
|
166
|
+
raise Error(f"Unit unknown for type-code {type_code}.")
|
|
167
|
+
self.diff = type_code == RegisterType.TEMP_DIFF
|
|
168
|
+
else:
|
|
169
|
+
self.diff = diff
|
|
170
|
+
self.pv = PhysicalValue(value, unit)
|
|
171
|
+
|
|
172
|
+
def __str__(self):
|
|
173
|
+
return self.pv.__str__()
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def value(self) -> float:
|
|
177
|
+
"""Return the value (magnitude) of the quantity."""
|
|
178
|
+
return self.pv.value
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def unit(self) -> str:
|
|
182
|
+
"""Return the unit of the quantity.
|
|
183
|
+
|
|
184
|
+
Note: when presenting a quantity to the user,
|
|
185
|
+
PhysicalQuantity.locale_unit() should be used.
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
return self.pv.unit
|
|
189
|
+
|
|
190
|
+
def to(self, unit: str, dt: float | None = None) -> float:
|
|
191
|
+
"""Return the quantity's value in a given unit.
|
|
192
|
+
|
|
193
|
+
Raises an Error if the conversion is not possible.
|
|
194
|
+
|
|
195
|
+
Required arguments:
|
|
196
|
+
|
|
197
|
+
unit -- The unit the quantity's value should be converted to.
|
|
198
|
+
|
|
199
|
+
dt -- If the quantity is a cumulative value this must specify
|
|
200
|
+
the time in seconds over which the quantity was measured.
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
x = self._to_unit(unit, dt)
|
|
204
|
+
if x is None:
|
|
205
|
+
raise Error("Conversion not possible.", self.pv.unit, unit)
|
|
206
|
+
return x
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def locale_unit(self):
|
|
210
|
+
"""Return the localized unit name.
|
|
211
|
+
|
|
212
|
+
This must be used when presenting a quantity to the user. For
|
|
213
|
+
most units, this is identical to PhysiqlQuantity.unit().
|
|
214
|
+
However, for monetary values this method replaces the string
|
|
215
|
+
"${currency}" in units with the string established with
|
|
216
|
+
PhysicalQuantity.set_currency(). Note that once a unit is
|
|
217
|
+
localized, it cannot be converted to other units anymore, so
|
|
218
|
+
this should be used for display purposes only.
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
return self.pv.unit.replace("${currency}", self._currency)
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
def set_currency(cls, currency: str):
|
|
225
|
+
"""Set the symbol/string to use as the local currency.
|
|
226
|
+
|
|
227
|
+
Required arguments:
|
|
228
|
+
|
|
229
|
+
currency: The string representing the local currency. For
|
|
230
|
+
example, "$", "€", "¥", "CHF", or similar.
|
|
231
|
+
|
|
232
|
+
"""
|
|
233
|
+
cls._currency = currency
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def set_unit_system(cls, default_unit_system: UnitSystem):
|
|
237
|
+
"""Select the unit system to use by default.
|
|
238
|
+
|
|
239
|
+
Required arguments:
|
|
240
|
+
|
|
241
|
+
default_unit_system -- Must be one of UnitSystem.METRIC
|
|
242
|
+
(metric units) or UnitSystem.IMPERIAL (imperial, aka, US
|
|
243
|
+
conventional units).
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
cls._default_unit_system = default_unit_system
|
|
247
|
+
|
|
248
|
+
def to_unit(self, unit: str, dt: float | None = None) -> Self:
|
|
249
|
+
"""Convert the quantity to a specified unit.
|
|
250
|
+
|
|
251
|
+
Returns the physical quantity itself.
|
|
252
|
+
|
|
253
|
+
Required arguments:
|
|
254
|
+
|
|
255
|
+
unit -- The unit to convert the quantity to.
|
|
256
|
+
|
|
257
|
+
Keyword arguments:
|
|
258
|
+
|
|
259
|
+
dt -- The time-duration over which the quantity was measured
|
|
260
|
+
(default None). This is needed because some conversions
|
|
261
|
+
(e.g., °C·s to °K·s or °F·s) require a time-dependent
|
|
262
|
+
adjustment.
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
x = self._to_unit(unit, dt)
|
|
266
|
+
if x is not None:
|
|
267
|
+
self._set(x, unit)
|
|
268
|
+
return self
|
|
269
|
+
|
|
270
|
+
def to_preferred(
|
|
271
|
+
self, unit_system: UnitSystem | None = None, dt: float | None = None
|
|
272
|
+
) -> Self:
|
|
273
|
+
"""Convert the quantity to the preferred unit of the specified
|
|
274
|
+
unit system.
|
|
275
|
+
|
|
276
|
+
Returns the physical quantity itself.
|
|
277
|
+
|
|
278
|
+
Keyword arguments:
|
|
279
|
+
|
|
280
|
+
unit_system -- The unit system to use for the conversion. If
|
|
281
|
+
`None`, the unit system established with a call to
|
|
282
|
+
PhysicalQuantity.set_unit_system() is used.
|
|
283
|
+
|
|
284
|
+
dt -- The time-duration over which the quantity was measured.
|
|
285
|
+
This is needed only for certain type conversions (e.g.,
|
|
286
|
+
°C·s to °K·s or F·s) that require a time-dependent
|
|
287
|
+
adjustment.
|
|
288
|
+
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
if unit_system is None:
|
|
292
|
+
unit_system = self._default_unit_system
|
|
293
|
+
|
|
294
|
+
available = Units.units.alternate_units(self.pv.unit)
|
|
295
|
+
for unit in available:
|
|
296
|
+
preferred_unit = None
|
|
297
|
+
if PhysicalQuantity.is_preferred(unit_system, unit):
|
|
298
|
+
preferred_unit = unit
|
|
299
|
+
else:
|
|
300
|
+
for scaled in Units.units.scaled_units(unit):
|
|
301
|
+
if PhysicalQuantity.is_preferred(unit_system, scaled):
|
|
302
|
+
preferred_unit = scaled
|
|
303
|
+
break
|
|
304
|
+
if preferred_unit is not None:
|
|
305
|
+
x = Units.units.convert(
|
|
306
|
+
self.pv.value, self.pv.unit, preferred_unit, self.diff, dt
|
|
307
|
+
)
|
|
308
|
+
if x is not None:
|
|
309
|
+
return self._set(x, preferred_unit)
|
|
310
|
+
break
|
|
311
|
+
# could not find preferred unit; leave quantity unchanged
|
|
312
|
+
return self
|
|
313
|
+
|
|
314
|
+
def to_cumul(self, dt: float) -> Self:
|
|
315
|
+
"""Convert this physical quantity, which must be an average
|
|
316
|
+
rate over a given time-period, to the corresponding cumulative
|
|
317
|
+
value.
|
|
318
|
+
|
|
319
|
+
Returns the physical quantity itself.
|
|
320
|
+
|
|
321
|
+
Required arguments:
|
|
322
|
+
|
|
323
|
+
dt -- The time in seconds over which the average rate was
|
|
324
|
+
measured.
|
|
325
|
+
|
|
326
|
+
"""
|
|
327
|
+
# first, convert the unit to the primary unit, since the primary
|
|
328
|
+
# units are most easily simplified by time_integrated_unit():
|
|
329
|
+
primary_unit = Units.units.primary_unit(self.pv.unit)
|
|
330
|
+
if primary_unit is not None and primary_unit != self.pv.unit:
|
|
331
|
+
self.to_unit(primary_unit, dt)
|
|
332
|
+
|
|
333
|
+
cumul_unit = PhysicalQuantity.time_integrated_unit(self.pv.unit)
|
|
334
|
+
return self._set(dt * self.pv.value, cumul_unit)
|
|
335
|
+
|
|
336
|
+
def auto_scale(self) -> Self:
|
|
337
|
+
"""If the measurement unit (e.g., 'W') has scaled units
|
|
338
|
+
available (e.g., 'mW' or 'kW'), convert this quantity to a
|
|
339
|
+
scaled unit such that the absolute value is either zero or in
|
|
340
|
+
the range from 1 to 1000.
|
|
341
|
+
|
|
342
|
+
Returns the physical quantity itself.
|
|
343
|
+
|
|
344
|
+
"""
|
|
345
|
+
self.pv = Units.units.auto_scale(self.pv)
|
|
346
|
+
return self
|
|
347
|
+
|
|
348
|
+
def _to_unit(self, unit: str, dt: float | None = None) -> float | None:
|
|
349
|
+
"""Try to convert this quantity to another unit.
|
|
350
|
+
|
|
351
|
+
If the conversion is not possible, `None` is returned.
|
|
352
|
+
Otherwise, the value in the desired unit is returned.
|
|
353
|
+
|
|
354
|
+
Required arguments:
|
|
355
|
+
|
|
356
|
+
unit -- The unit to convert the quantity to.
|
|
357
|
+
|
|
358
|
+
dt -- The time-duration over which the quantity was measured.
|
|
359
|
+
This is needed only for certain type conversions (e.g.,
|
|
360
|
+
°C·s to °K·s or F·s) that require a time-dependent
|
|
361
|
+
adjustment.
|
|
362
|
+
|
|
363
|
+
"""
|
|
364
|
+
x = Units.units.convert(
|
|
365
|
+
self.pv.value, self.pv.unit, unit, self.diff, dt
|
|
366
|
+
)
|
|
367
|
+
if x is not None:
|
|
368
|
+
return x
|
|
369
|
+
|
|
370
|
+
primary_unit = Units.units.primary_unit(self.pv.unit) or ""
|
|
371
|
+
if primary_unit[-2:] == "·s" and unit[-2:] == "·s":
|
|
372
|
+
if self.pv.unit != primary_unit:
|
|
373
|
+
x = Units.units.convert(
|
|
374
|
+
self.pv.value, self.pv.unit, primary_unit, self.diff, dt
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
x = self.pv.value
|
|
378
|
+
|
|
379
|
+
if x is not None:
|
|
380
|
+
# If the conversion between two rate-units involves a
|
|
381
|
+
# simple scale-factor, then the time-integrated
|
|
382
|
+
# (cumulative) units of those rate-units can be
|
|
383
|
+
# converted using the same factor. This only works as
|
|
384
|
+
# long as the rate-conversion is linear. Fortunately,
|
|
385
|
+
# we don't have any crazy units using non-linear
|
|
386
|
+
# conversions so far.
|
|
387
|
+
x = Units.units.convert(
|
|
388
|
+
x, primary_unit[:-2], unit[:-2], self.diff, dt
|
|
389
|
+
)
|
|
390
|
+
if x is not None:
|
|
391
|
+
return x
|
|
392
|
+
# conversion failed - leave the quantity unchanged
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
def _set(self, val: float, unit: str) -> Self:
|
|
396
|
+
"""Establish a new value for the quantity.
|
|
397
|
+
|
|
398
|
+
Returns the physical quantity itself.
|
|
399
|
+
|
|
400
|
+
Required arguments:
|
|
401
|
+
|
|
402
|
+
val -- The new magnitude of the quantity.
|
|
403
|
+
|
|
404
|
+
unit -- The new unit of the quantity.
|
|
405
|
+
|
|
406
|
+
"""
|
|
407
|
+
self.pv.value = val
|
|
408
|
+
self.pv.unit = unit
|
|
409
|
+
return self
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def test():
|
|
413
|
+
pq = PhysicalQuantity(3.1415, "P")
|
|
414
|
+
print(pq)
|
|
415
|
+
print(pq.to_preferred(UnitSystem.METRIC), pq)
|
|
416
|
+
pq.auto_scale()
|
|
417
|
+
print("auto-scaled", pq)
|
|
418
|
+
print(pq.to_unit("kW"), pq)
|
|
419
|
+
pq.to_cumul(3600)
|
|
420
|
+
print(pq)
|
|
421
|
+
print(pq.to_preferred(UnitSystem.METRIC), pq)
|
|
422
|
+
|
|
423
|
+
pq = PhysicalQuantity(24, "T")
|
|
424
|
+
print(pq)
|
|
425
|
+
print(pq.to_unit("°F"), pq)
|
|
426
|
+
|
|
427
|
+
pq = PhysicalQuantity(24, "T")
|
|
428
|
+
print(pq.to_preferred(UnitSystem.IMPERIAL), pq)
|
|
429
|
+
|
|
430
|
+
pq = PhysicalQuantity(24 * (24 * 3600), "T", is_cumul=True)
|
|
431
|
+
print(pq)
|
|
432
|
+
print(pq.to_unit("°F·d", dt=24 * 3600), pq)
|
|
433
|
+
|
|
434
|
+
print(PhysicalQuantity.available_units("P", False))
|
|
435
|
+
print(PhysicalQuantity.available_units("P", True))
|
|
436
|
+
print(PhysicalQuantity.scales("W"))
|
|
437
|
+
print(PhysicalQuantity.scales("Wh"))
|
|
438
|
+
print(PhysicalQuantity.available_units("T", False))
|
|
439
|
+
print(PhysicalQuantity.available_units("T", True))
|