scietex.hal.vfd 2.0.2__tar.gz → 2.1.0__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.
- {scietex_hal_vfd-2.0.2/src/scietex.hal.vfd.egg-info → scietex_hal_vfd-2.1.0}/PKG-INFO +15 -3
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/README.md +14 -2
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/base/data.py +12 -1
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/intek/spe_b/client.py +13 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/intek/spe_b/registers.py +27 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/intek/spe_b/rs485.py +12 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/version.py +1 -1
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/client.py +3 -1
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/registers.py +5 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/rs485.py +3 -1
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/client.py +3 -1
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/rs485.py +3 -1
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0/src/scietex.hal.vfd.egg-info}/PKG-INFO +15 -3
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/tests/test_modbus_client.py +32 -6
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/tests/test_registers.py +13 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/LICENSE +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/pyproject.toml +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/setup.cfg +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/__init__.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/base/__init__.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/base/capabilities.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/base/protocol.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/base/rs485.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/intek/__init__.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/intek/spe_b/__init__.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/intek/spe_b/errors.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/modbus_drivers.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/py.typed +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/__init__.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/__init__.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/errors.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/__init__.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/errors.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/registers.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex.hal.vfd.egg-info/SOURCES.txt +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex.hal.vfd.egg-info/dependency_links.txt +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex.hal.vfd.egg-info/entry_points.txt +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex.hal.vfd.egg-info/requires.txt +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex.hal.vfd.egg-info/top_level.txt +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/tests/test_modbus_drivers.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/tests/test_version.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/tests/test_vesper_e4_8400_registers.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/tests/test_vesper_e5_8200_registers.py +0 -0
- {scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/tests/test_vesper_optional_reads.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scietex.hal.vfd
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Scietex VFD HAL package
|
|
5
5
|
Author-email: Anton Bondarenko <bond.anton@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -71,8 +71,9 @@ A driver reaches a drive over one of two transports:
|
|
|
71
71
|
unsupported command before dispatching it.
|
|
72
72
|
- `VFDParameters`, which reports frequency in both Hz and percent of the
|
|
73
73
|
configured maximum, plus output current, voltage and power, and the optional
|
|
74
|
-
DC bus voltage and
|
|
75
|
-
when the drive does not report it, which is distinct
|
|
74
|
+
DC bus voltage, temperatures and run-time counters a drive exposes. An
|
|
75
|
+
optional field is `None` when the drive does not report it, which is distinct
|
|
76
|
+
from a reported `0.0`.
|
|
76
77
|
- Implemented drive models, each with both transports:
|
|
77
78
|
- `Intek SPE-B` — direct RS485 (`SPEvB`) and gateway (`IntekSPEBModbusVFD`).
|
|
78
79
|
- `Vesper E5-8200` — direct RS485 (`VesperE58200`) and gateway
|
|
@@ -94,10 +95,21 @@ A driver reaches a drive over one of two transports:
|
|
|
94
95
|
| `dc_bus_voltage` | yes | yes | yes |
|
|
95
96
|
| `heatsink_temperature_c` | no (`None`) | no (`None`) | yes |
|
|
96
97
|
| `motor_temperature_c` | no (`None`) | no (`None`) | no (`None`) |
|
|
98
|
+
| `total_run_hours` | yes | no (`None`) | no (`None`) |
|
|
99
|
+
| `total_power_on_hours` | yes | no (`None`) | no (`None`) |
|
|
97
100
|
|
|
98
101
|
Only the E4-8400 exposes a live temperature reading (`004Dh`, U1-46). The other
|
|
99
102
|
two report overheat only as a fault code, and a fault is not a temperature.
|
|
100
103
|
|
|
104
|
+
The SPE-B keeps two run-time counters (`0x101A` power-on, `0x101B` run). The
|
|
105
|
+
manual states no unit; the drive was measured incrementing the power-on counter
|
|
106
|
+
by exactly one over sixty seconds, so both are minutes and are reported as
|
|
107
|
+
hours. The E4-8400 defines U1-48 and U1-49 as "run time 1" and "run time 2"
|
|
108
|
+
without stating a unit or which is cumulative, and no drive was available to
|
|
109
|
+
measure them, so both hours fields stay `None` for that model. The E5-8200 has
|
|
110
|
+
no counter at all. A caller that needs hours for either drive accumulates them
|
|
111
|
+
itself.
|
|
112
|
+
|
|
101
113
|
¹ The SPE-B reports the operating frequency only as a percentage of `P0.10`, so
|
|
102
114
|
`frequency_hz` is `None` until `P0.10` has been read. A drive that refuses
|
|
103
115
|
`P0.10` still reports `frequency_percent`, current, voltage and power; the
|
|
@@ -23,8 +23,9 @@ A driver reaches a drive over one of two transports:
|
|
|
23
23
|
unsupported command before dispatching it.
|
|
24
24
|
- `VFDParameters`, which reports frequency in both Hz and percent of the
|
|
25
25
|
configured maximum, plus output current, voltage and power, and the optional
|
|
26
|
-
DC bus voltage and
|
|
27
|
-
when the drive does not report it, which is distinct
|
|
26
|
+
DC bus voltage, temperatures and run-time counters a drive exposes. An
|
|
27
|
+
optional field is `None` when the drive does not report it, which is distinct
|
|
28
|
+
from a reported `0.0`.
|
|
28
29
|
- Implemented drive models, each with both transports:
|
|
29
30
|
- `Intek SPE-B` — direct RS485 (`SPEvB`) and gateway (`IntekSPEBModbusVFD`).
|
|
30
31
|
- `Vesper E5-8200` — direct RS485 (`VesperE58200`) and gateway
|
|
@@ -46,10 +47,21 @@ A driver reaches a drive over one of two transports:
|
|
|
46
47
|
| `dc_bus_voltage` | yes | yes | yes |
|
|
47
48
|
| `heatsink_temperature_c` | no (`None`) | no (`None`) | yes |
|
|
48
49
|
| `motor_temperature_c` | no (`None`) | no (`None`) | no (`None`) |
|
|
50
|
+
| `total_run_hours` | yes | no (`None`) | no (`None`) |
|
|
51
|
+
| `total_power_on_hours` | yes | no (`None`) | no (`None`) |
|
|
49
52
|
|
|
50
53
|
Only the E4-8400 exposes a live temperature reading (`004Dh`, U1-46). The other
|
|
51
54
|
two report overheat only as a fault code, and a fault is not a temperature.
|
|
52
55
|
|
|
56
|
+
The SPE-B keeps two run-time counters (`0x101A` power-on, `0x101B` run). The
|
|
57
|
+
manual states no unit; the drive was measured incrementing the power-on counter
|
|
58
|
+
by exactly one over sixty seconds, so both are minutes and are reported as
|
|
59
|
+
hours. The E4-8400 defines U1-48 and U1-49 as "run time 1" and "run time 2"
|
|
60
|
+
without stating a unit or which is cumulative, and no drive was available to
|
|
61
|
+
measure them, so both hours fields stay `None` for that model. The E5-8200 has
|
|
62
|
+
no counter at all. A caller that needs hours for either drive accumulates them
|
|
63
|
+
itself.
|
|
64
|
+
|
|
53
65
|
¹ The SPE-B reports the operating frequency only as a percentage of `P0.10`, so
|
|
54
66
|
`frequency_hz` is `None` until `P0.10` has been read. A drive that refuses
|
|
55
67
|
`P0.10` still reports `frequency_percent`, current, voltage and power; the
|
|
@@ -62,6 +62,13 @@ class VFDParameters(msgspec.Struct, frozen=True):
|
|
|
62
62
|
motor_temperature_c: Motor winding or PTC temperature in degrees
|
|
63
63
|
Celsius, or ``None`` when the drive does not report it.
|
|
64
64
|
state: Current VFD state.
|
|
65
|
+
total_run_hours: Cumulative motor run time in hours, or ``None`` when
|
|
66
|
+
the drive keeps no such counter. A drive that reports the counter
|
|
67
|
+
but refuses the read also yields ``None``: the caller cannot tell
|
|
68
|
+
"no counter" from "unreadable", and both mean the value must come
|
|
69
|
+
from somewhere else.
|
|
70
|
+
total_power_on_hours: Cumulative powered-on time in hours, or ``None``
|
|
71
|
+
when the drive keeps no such counter.
|
|
65
72
|
"""
|
|
66
73
|
|
|
67
74
|
frequency_hz: float | None # Current operating frequency in Hz
|
|
@@ -73,6 +80,8 @@ class VFDParameters(msgspec.Struct, frozen=True):
|
|
|
73
80
|
heatsink_temperature_c: float | None = None # Power-module temperature, deg C
|
|
74
81
|
motor_temperature_c: float | None = None # Motor temperature, deg C
|
|
75
82
|
state: VFDState = VFDState.STOPPED # Current VFD state
|
|
83
|
+
total_run_hours: float | None = None # Cumulative run time, hours
|
|
84
|
+
total_power_on_hours: float | None = None # Cumulative power-on time, hours
|
|
76
85
|
|
|
77
86
|
def __str__(self) -> str:
|
|
78
87
|
return (
|
|
@@ -84,7 +93,9 @@ class VFDParameters(msgspec.Struct, frozen=True):
|
|
|
84
93
|
f"dc_bus={self.dc_bus_voltage} V, "
|
|
85
94
|
f"heatsink={self.heatsink_temperature_c} C, "
|
|
86
95
|
f"motor={self.motor_temperature_c} C, "
|
|
87
|
-
f"state={self.state}
|
|
96
|
+
f"state={self.state}, "
|
|
97
|
+
f"run_hours={self.total_run_hours}, "
|
|
98
|
+
f"power_on_hours={self.total_power_on_hours})"
|
|
88
99
|
)
|
|
89
100
|
|
|
90
101
|
|
|
@@ -270,6 +270,10 @@ class IntekSPEBModbusVFD(VFDProtocol):
|
|
|
270
270
|
the status register still reports its measurements, so the state falls
|
|
271
271
|
back to ``VFDState.UNKNOWN`` instead of failing the whole snapshot.
|
|
272
272
|
|
|
273
|
+
The two run-time counters are read as a second block and are optional
|
|
274
|
+
for the same reason: a drive that refuses them still reports its
|
|
275
|
+
measurements, and the hours fields stay ``None``.
|
|
276
|
+
|
|
273
277
|
Returns:
|
|
274
278
|
VFDParameters: The decoded parameters. Both temperature fields are
|
|
275
279
|
``None``: SPE-B reports overheat as a fault code, not a reading.
|
|
@@ -287,6 +291,9 @@ class IntekSPEBModbusVFD(VFDProtocol):
|
|
|
287
291
|
block[registers.REG_OUTPUT_CURRENT - registers.PARAMETERS_BLOCK_ADDRESS]
|
|
288
292
|
* self._current_resolution
|
|
289
293
|
)
|
|
294
|
+
run_time = await self._read_optional_registers(
|
|
295
|
+
registers.RUN_TIME_BLOCK_ADDRESS, registers.RUN_TIME_BLOCK_COUNT
|
|
296
|
+
)
|
|
290
297
|
parameters = VFDParameters(
|
|
291
298
|
frequency_hz=self._percent_to_hz(percent),
|
|
292
299
|
frequency_percent=percent,
|
|
@@ -301,6 +308,12 @@ class IntekSPEBModbusVFD(VFDProtocol):
|
|
|
301
308
|
block[registers.REG_DC_BUS_VOLTAGE - registers.PARAMETERS_BLOCK_ADDRESS]
|
|
302
309
|
),
|
|
303
310
|
state=await self.read_state(),
|
|
311
|
+
total_power_on_hours=(
|
|
312
|
+
registers.run_time_to_hours(run_time[0]) if run_time is not None else None
|
|
313
|
+
),
|
|
314
|
+
total_run_hours=(
|
|
315
|
+
registers.run_time_to_hours(run_time[1]) if run_time is not None else None
|
|
316
|
+
),
|
|
304
317
|
)
|
|
305
318
|
self.logger.debug("%s: %s", self.label, parameters)
|
|
306
319
|
return parameters
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/intek/spe_b/registers.py
RENAMED
|
@@ -54,6 +54,16 @@ REG_AUX_FREQUENCY_Y = 0x1020 # Auxiliary frequency Y, raw
|
|
|
54
54
|
PARAMETERS_BLOCK_ADDRESS = REG_OPERATING_FREQUENCY
|
|
55
55
|
PARAMETERS_BLOCK_COUNT = 5
|
|
56
56
|
|
|
57
|
+
# The two consecutive run-time counters, read as a second block because they sit
|
|
58
|
+
# outside the measurement block. The manual calls them "raw" and states no unit;
|
|
59
|
+
# the drive was measured incrementing 0x101A by one per minute, so both are
|
|
60
|
+
# minutes. `0x1015` (remaining run time) is deliberately not read: it is the
|
|
61
|
+
# countdown to a maintenance interval, not a total, and it reads 0 when no
|
|
62
|
+
# interval is configured.
|
|
63
|
+
RUN_TIME_BLOCK_ADDRESS = REG_TOTAL_POWER_ON_TIME
|
|
64
|
+
RUN_TIME_BLOCK_COUNT = 2
|
|
65
|
+
RUN_TIME_RESOLUTION = 60.0 # Counter units per hour
|
|
66
|
+
|
|
57
67
|
# Control register, FC06 write only.
|
|
58
68
|
REG_CONTROL = 0x2000
|
|
59
69
|
CONTROL_FORWARD_RUN = 1
|
|
@@ -185,6 +195,23 @@ def raw_to_percent(raw: int) -> float:
|
|
|
185
195
|
return raw / ANALOG_OUTPUT_MAX_RAW * 100.0
|
|
186
196
|
|
|
187
197
|
|
|
198
|
+
def run_time_to_hours(raw: int) -> float:
|
|
199
|
+
"""
|
|
200
|
+
Convert a run-time counter register into hours.
|
|
201
|
+
|
|
202
|
+
The manual states no unit for `REG_TOTAL_POWER_ON_TIME` and
|
|
203
|
+
`REG_TOTAL_RUN_TIME`. The drive was measured incrementing the power-on
|
|
204
|
+
counter by exactly one over sixty seconds, so the counters are minutes.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
raw: The register value.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
float: The counter as hours.
|
|
211
|
+
"""
|
|
212
|
+
return raw / RUN_TIME_RESOLUTION
|
|
213
|
+
|
|
214
|
+
|
|
188
215
|
def status_to_state(status: int) -> VFDState:
|
|
189
216
|
"""
|
|
190
217
|
Decode the status register into a `VFDState`.
|
|
@@ -127,6 +127,12 @@ class SPEvB(VFD):
|
|
|
127
127
|
else None
|
|
128
128
|
)
|
|
129
129
|
state = await self.read_state()
|
|
130
|
+
run_time = await self.read_registers(
|
|
131
|
+
registers.RUN_TIME_BLOCK_ADDRESS,
|
|
132
|
+
registers.RUN_TIME_BLOCK_COUNT,
|
|
133
|
+
holding=True,
|
|
134
|
+
raise_on_error=False,
|
|
135
|
+
)
|
|
130
136
|
return VFDParameters(
|
|
131
137
|
frequency_hz=frequency_hz,
|
|
132
138
|
frequency_percent=percent,
|
|
@@ -141,6 +147,12 @@ class SPEvB(VFD):
|
|
|
141
147
|
block[registers.REG_DC_BUS_VOLTAGE - registers.PARAMETERS_BLOCK_ADDRESS]
|
|
142
148
|
),
|
|
143
149
|
state=state,
|
|
150
|
+
total_power_on_hours=(
|
|
151
|
+
registers.run_time_to_hours(run_time[0]) if run_time is not None else None
|
|
152
|
+
),
|
|
153
|
+
total_run_hours=(
|
|
154
|
+
registers.run_time_to_hours(run_time[1]) if run_time is not None else None
|
|
155
|
+
),
|
|
144
156
|
)
|
|
145
157
|
|
|
146
158
|
async def read_state(self) -> VFDState:
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/client.py
RENAMED
|
@@ -254,7 +254,9 @@ class VesperE48400ModbusVFD(VFDProtocol):
|
|
|
254
254
|
Returns:
|
|
255
255
|
VFDParameters: The decoded parameters. ``heatsink_temperature_c`` is
|
|
256
256
|
populated from U1-46; ``motor_temperature_c`` stays ``None``
|
|
257
|
-
because the drive reports motor overheat only as a fault.
|
|
257
|
+
because the drive reports motor overheat only as a fault. Both
|
|
258
|
+
hours fields stay ``None``: U1-48 and U1-49 are undocumented as
|
|
259
|
+
to unit and meaning, so the caller accumulates the hours.
|
|
258
260
|
|
|
259
261
|
Raises:
|
|
260
262
|
ModbusOperationError: If the measurement block or E1-04 cannot be
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/registers.py
RENAMED
|
@@ -109,6 +109,11 @@ REG_COUNTER = 0x004E # U1-47, counter value
|
|
|
109
109
|
REG_RUN_TIME_1 = 0x004F # U1-48, run time 1
|
|
110
110
|
REG_RUN_TIME_2 = 0x0050 # U1-49, run time 2
|
|
111
111
|
|
|
112
|
+
# U1-48 and U1-49 are not read: the manual names them "run time 1" and "run time
|
|
113
|
+
# 2" without stating a unit or which one is cumulative, and no drive was
|
|
114
|
+
# available to measure them. `VFDParameters.total_run_hours` therefore stays
|
|
115
|
+
# ``None`` for this model and the caller accumulates the hours itself.
|
|
116
|
+
|
|
112
117
|
# Fault snapshot, read only. The values are captured when the last fault
|
|
113
118
|
# occurred and are not updated while the drive runs.
|
|
114
119
|
REG_FAULT_OUTPUT_FREQUENCY = 0x0083 # U2-04, output frequency at fault
|
|
@@ -206,7 +206,9 @@ class VesperE48400(VFD):
|
|
|
206
206
|
Returns:
|
|
207
207
|
VFDParameters: The decoded parameters. ``heatsink_temperature_c`` is
|
|
208
208
|
populated from U1-46; ``motor_temperature_c`` stays ``None``
|
|
209
|
-
because the drive reports motor overheat only as a fault.
|
|
209
|
+
because the drive reports motor overheat only as a fault. Both
|
|
210
|
+
hours fields stay ``None``: U1-48 and U1-49 are undocumented as
|
|
211
|
+
to unit and meaning, so the caller accumulates the hours.
|
|
210
212
|
|
|
211
213
|
Raises:
|
|
212
214
|
ModbusOperationError: If the measurement block or E1-04 cannot be
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/client.py
RENAMED
|
@@ -228,7 +228,9 @@ class VesperE58200ModbusVFD(VFDProtocol):
|
|
|
228
228
|
back to ``VFDState.UNKNOWN`` instead of failing the whole snapshot.
|
|
229
229
|
|
|
230
230
|
Returns:
|
|
231
|
-
VFDParameters: The decoded parameters.
|
|
231
|
+
VFDParameters: The decoded parameters. Both hours fields stay
|
|
232
|
+
``None``: the drive exposes no run-time counter, so the caller
|
|
233
|
+
accumulates the hours.
|
|
232
234
|
|
|
233
235
|
Raises:
|
|
234
236
|
ModbusOperationError: If the measurement block cannot be read.
|
|
@@ -138,7 +138,9 @@ class VesperE58200(VFD):
|
|
|
138
138
|
back to ``VFDState.UNKNOWN`` instead of failing the whole snapshot.
|
|
139
139
|
|
|
140
140
|
Returns:
|
|
141
|
-
VFDParameters: The decoded parameters.
|
|
141
|
+
VFDParameters: The decoded parameters. Both hours fields stay
|
|
142
|
+
``None``: the drive exposes no run-time counter, so the caller
|
|
143
|
+
accumulates the hours.
|
|
142
144
|
|
|
143
145
|
Raises:
|
|
144
146
|
ModbusOperationError: If the measurement block cannot be read.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scietex.hal.vfd
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Scietex VFD HAL package
|
|
5
5
|
Author-email: Anton Bondarenko <bond.anton@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -71,8 +71,9 @@ A driver reaches a drive over one of two transports:
|
|
|
71
71
|
unsupported command before dispatching it.
|
|
72
72
|
- `VFDParameters`, which reports frequency in both Hz and percent of the
|
|
73
73
|
configured maximum, plus output current, voltage and power, and the optional
|
|
74
|
-
DC bus voltage and
|
|
75
|
-
when the drive does not report it, which is distinct
|
|
74
|
+
DC bus voltage, temperatures and run-time counters a drive exposes. An
|
|
75
|
+
optional field is `None` when the drive does not report it, which is distinct
|
|
76
|
+
from a reported `0.0`.
|
|
76
77
|
- Implemented drive models, each with both transports:
|
|
77
78
|
- `Intek SPE-B` — direct RS485 (`SPEvB`) and gateway (`IntekSPEBModbusVFD`).
|
|
78
79
|
- `Vesper E5-8200` — direct RS485 (`VesperE58200`) and gateway
|
|
@@ -94,10 +95,21 @@ A driver reaches a drive over one of two transports:
|
|
|
94
95
|
| `dc_bus_voltage` | yes | yes | yes |
|
|
95
96
|
| `heatsink_temperature_c` | no (`None`) | no (`None`) | yes |
|
|
96
97
|
| `motor_temperature_c` | no (`None`) | no (`None`) | no (`None`) |
|
|
98
|
+
| `total_run_hours` | yes | no (`None`) | no (`None`) |
|
|
99
|
+
| `total_power_on_hours` | yes | no (`None`) | no (`None`) |
|
|
97
100
|
|
|
98
101
|
Only the E4-8400 exposes a live temperature reading (`004Dh`, U1-46). The other
|
|
99
102
|
two report overheat only as a fault code, and a fault is not a temperature.
|
|
100
103
|
|
|
104
|
+
The SPE-B keeps two run-time counters (`0x101A` power-on, `0x101B` run). The
|
|
105
|
+
manual states no unit; the drive was measured incrementing the power-on counter
|
|
106
|
+
by exactly one over sixty seconds, so both are minutes and are reported as
|
|
107
|
+
hours. The E4-8400 defines U1-48 and U1-49 as "run time 1" and "run time 2"
|
|
108
|
+
without stating a unit or which is cumulative, and no drive was available to
|
|
109
|
+
measure them, so both hours fields stay `None` for that model. The E5-8200 has
|
|
110
|
+
no counter at all. A caller that needs hours for either drive accumulates them
|
|
111
|
+
itself.
|
|
112
|
+
|
|
101
113
|
¹ The SPE-B reports the operating frequency only as a percentage of `P0.10`, so
|
|
102
114
|
`frequency_hz` is `None` until `P0.10` has been read. A drive that refuses
|
|
103
115
|
`P0.10` still reports `frequency_percent`, current, voltage and power; the
|
|
@@ -132,8 +132,8 @@ def test_read_before_connect_raises():
|
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
def test_read_parameters_scales_percent_current_and_reads_state():
|
|
135
|
-
"""One block read at 0x1001, then the status register."""
|
|
136
|
-
vfd, stub = _connected([_read([5000, 540, 380, 123, 45]), _read([1])])
|
|
135
|
+
"""One block read at 0x1001, the run-time block, then the status register."""
|
|
136
|
+
vfd, stub = _connected([_read([5000, 540, 380, 123, 45]), _read([3231, 120]), _read([1])])
|
|
137
137
|
parameters = asyncio.run(vfd.read_parameters())
|
|
138
138
|
|
|
139
139
|
assert stub.calls == [
|
|
@@ -143,6 +143,7 @@ def test_read_parameters_scales_percent_current_and_reads_state():
|
|
|
143
143
|
registers.PARAMETERS_BLOCK_COUNT,
|
|
144
144
|
1,
|
|
145
145
|
),
|
|
146
|
+
("read", registers.RUN_TIME_BLOCK_ADDRESS, registers.RUN_TIME_BLOCK_COUNT, 1),
|
|
146
147
|
("read", registers.REG_STATUS, 1, 1),
|
|
147
148
|
]
|
|
148
149
|
assert parameters.frequency_percent == pytest.approx(50.0)
|
|
@@ -151,22 +152,36 @@ def test_read_parameters_scales_percent_current_and_reads_state():
|
|
|
151
152
|
assert parameters.output_voltage == pytest.approx(380.0)
|
|
152
153
|
assert parameters.output_power == pytest.approx(45.0)
|
|
153
154
|
assert parameters.state is VFDState.RUNNING_FORWARD
|
|
155
|
+
assert parameters.total_power_on_hours == pytest.approx(53.85)
|
|
156
|
+
assert parameters.total_run_hours == pytest.approx(2.0)
|
|
154
157
|
|
|
155
158
|
|
|
156
159
|
def test_read_parameters_uses_the_coarse_current_resolution():
|
|
157
160
|
"""Pd.06 = 1 means 0.1 A per count."""
|
|
158
|
-
vfd, _ = _connected([_read([0, 0, 0, 123, 0]), _read([3])], current_raw=1)
|
|
161
|
+
vfd, _ = _connected([_read([0, 0, 0, 123, 0]), _read([0, 0]), _read([3])], current_raw=1)
|
|
159
162
|
assert asyncio.run(vfd.read_parameters()).output_current == pytest.approx(12.3)
|
|
160
163
|
|
|
161
164
|
|
|
162
165
|
def test_read_parameters_reports_no_hz_when_p010_is_zero():
|
|
163
166
|
"""An unusable maximum frequency keeps the percentage but cannot scale it."""
|
|
164
|
-
vfd, _ = _connected([_read([5000, 0, 0, 0, 0]), _read([3])], max_frequency_raw=0)
|
|
167
|
+
vfd, _ = _connected([_read([5000, 0, 0, 0, 0]), _read([0, 0]), _read([3])], max_frequency_raw=0)
|
|
165
168
|
parameters = asyncio.run(vfd.read_parameters())
|
|
166
169
|
assert parameters.frequency_percent == pytest.approx(50.0)
|
|
167
170
|
assert parameters.frequency_hz is None
|
|
168
171
|
|
|
169
172
|
|
|
173
|
+
def test_read_parameters_survives_an_unreadable_run_time_block():
|
|
174
|
+
"""A refused run-time block leaves the hours unknown, not the snapshot empty."""
|
|
175
|
+
vfd, _ = _connected(
|
|
176
|
+
[_read([5000, 540, 380, 123, 45]), ExceptionResponse(function_code=3), _read([1])]
|
|
177
|
+
)
|
|
178
|
+
parameters = asyncio.run(vfd.read_parameters())
|
|
179
|
+
assert parameters.frequency_hz == pytest.approx(25.0)
|
|
180
|
+
assert parameters.total_run_hours is None
|
|
181
|
+
assert parameters.total_power_on_hours is None
|
|
182
|
+
assert parameters.state is VFDState.RUNNING_FORWARD
|
|
183
|
+
|
|
184
|
+
|
|
170
185
|
def test_connect_survives_a_refused_p010_and_still_reports_measurements():
|
|
171
186
|
"""A drive that refuses P0.10 must not blank the whole snapshot.
|
|
172
187
|
|
|
@@ -181,6 +196,7 @@ def test_connect_survives_a_refused_p010_and_still_reports_measurements():
|
|
|
181
196
|
ExceptionResponse(function_code=3), # P0.10
|
|
182
197
|
_read([1]), # Pd.06 = coarse, 0.1 A
|
|
183
198
|
_read([5000, 540, 380, 123, 45]),
|
|
199
|
+
_read([3231, 120]), # run-time block
|
|
184
200
|
_read([1]), # status: running forward
|
|
185
201
|
]
|
|
186
202
|
)
|
|
@@ -194,6 +210,7 @@ def test_connect_survives_a_refused_p010_and_still_reports_measurements():
|
|
|
194
210
|
assert parameters.output_voltage == pytest.approx(380.0)
|
|
195
211
|
assert parameters.dc_bus_voltage == pytest.approx(540.0)
|
|
196
212
|
assert parameters.state is VFDState.RUNNING_FORWARD
|
|
213
|
+
assert parameters.total_run_hours == pytest.approx(2.0)
|
|
197
214
|
|
|
198
215
|
|
|
199
216
|
def test_set_frequency_still_refuses_when_p010_is_unknown():
|
|
@@ -210,12 +227,19 @@ def test_read_parameters_survives_an_unreadable_status_register():
|
|
|
210
227
|
would mean a drive that refuses one diagnostic register never reports its
|
|
211
228
|
frequency, current or voltage at all.
|
|
212
229
|
"""
|
|
213
|
-
vfd, _ = _connected(
|
|
230
|
+
vfd, _ = _connected(
|
|
231
|
+
[
|
|
232
|
+
_read([5000, 540, 380, 123, 45]),
|
|
233
|
+
_read([3231, 120]),
|
|
234
|
+
ExceptionResponse(function_code=3),
|
|
235
|
+
]
|
|
236
|
+
)
|
|
214
237
|
parameters = asyncio.run(vfd.read_parameters())
|
|
215
238
|
assert parameters.frequency_hz == pytest.approx(25.0)
|
|
216
239
|
assert parameters.output_current == pytest.approx(1.23)
|
|
217
240
|
assert parameters.output_voltage == pytest.approx(380.0)
|
|
218
241
|
assert parameters.state is VFDState.UNKNOWN
|
|
242
|
+
assert parameters.total_run_hours == pytest.approx(2.0)
|
|
219
243
|
|
|
220
244
|
|
|
221
245
|
def test_read_parameters_still_raises_when_the_measurement_block_fails():
|
|
@@ -332,7 +356,7 @@ def test_write_raises_on_an_exception_response():
|
|
|
332
356
|
|
|
333
357
|
def test_read_data_returns_the_parameter_fields():
|
|
334
358
|
"""`read_data` flattens `VFDParameters` into a plain mapping."""
|
|
335
|
-
vfd, _ = _connected([_read([5000, 0, 380, 100, 10]), _read([2])])
|
|
359
|
+
vfd, _ = _connected([_read([5000, 0, 380, 100, 10]), _read([3231, 120]), _read([2])])
|
|
336
360
|
data = asyncio.run(vfd.read_data())
|
|
337
361
|
assert data == {
|
|
338
362
|
"frequency_hz": pytest.approx(25.0),
|
|
@@ -344,4 +368,6 @@ def test_read_data_returns_the_parameter_fields():
|
|
|
344
368
|
"heatsink_temperature_c": None,
|
|
345
369
|
"motor_temperature_c": None,
|
|
346
370
|
"state": VFDState.RUNNING_BACKWARD,
|
|
371
|
+
"total_run_hours": pytest.approx(2.0),
|
|
372
|
+
"total_power_on_hours": pytest.approx(53.85),
|
|
347
373
|
}
|
|
@@ -39,6 +39,19 @@ def test_parameters_block_starts_at_the_operating_frequency():
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
def test_run_time_block_spans_the_two_counters():
|
|
43
|
+
"""Power-on time is 0x101A and run time 0x101B, so one read covers both."""
|
|
44
|
+
assert registers.RUN_TIME_BLOCK_ADDRESS == 0x101A
|
|
45
|
+
assert registers.RUN_TIME_BLOCK_COUNT == 2
|
|
46
|
+
assert registers.RUN_TIME_BLOCK_ADDRESS + registers.RUN_TIME_BLOCK_COUNT - 1 == 0x101B
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.parametrize("raw, expected_hours", [(0, 0.0), (60, 1.0), (3231, 53.85)])
|
|
50
|
+
def test_run_time_to_hours(raw, expected_hours):
|
|
51
|
+
"""The counters are minutes: the drive was measured incrementing by one per minute."""
|
|
52
|
+
assert registers.run_time_to_hours(raw) == pytest.approx(expected_hours)
|
|
53
|
+
|
|
54
|
+
|
|
42
55
|
@pytest.mark.parametrize(
|
|
43
56
|
"percent, expected_hz",
|
|
44
57
|
[(100.0, 50.0), (50.0, 25.0), (0.0, 0.0), (-100.0, -50.0), (200.0, 100.0)],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/__init__.py
RENAMED
|
File without changes
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e4_8400/errors.py
RENAMED
|
File without changes
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/__init__.py
RENAMED
|
File without changes
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/errors.py
RENAMED
|
File without changes
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex/hal/vfd/vesper/e5_8200/registers.py
RENAMED
|
File without changes
|
|
File without changes
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex.hal.vfd.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{scietex_hal_vfd-2.0.2 → scietex_hal_vfd-2.1.0}/src/scietex.hal.vfd.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|