boneIO 1.5.0.dev3__py3-none-any.whl → 1.5.0.dev5__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.
- boneio/components/input/detectors.py +45 -0
- boneio/components/output/basic.py +14 -2
- boneio/core/config/schema_converter.py +25 -1
- boneio/core/config/yaml_util.py +31 -19
- boneio/core/state/manager.py +26 -1
- boneio/hardware/gpio/input/manager.py +52 -0
- boneio/modbus/devices/energy_meters/dts1964_3f.json +247 -0
- boneio/modbus/devices/energy_meters/le-03mw.json +369 -365
- boneio/modbus/devices/energy_meters/le-03mwct.json +369 -365
- boneio/modbus/devices/energy_meters/orno-or-we-517.json +6 -1
- boneio/modbus/devices/energy_meters/sdm120.json +122 -118
- boneio/modbus/devices/energy_meters/sdm630.json +5 -0
- boneio/modbus/devices/energy_meters/socomec_e03.json +74 -11
- boneio/modbus/devices/energy_meters/socomec_e23.json +124 -18
- boneio/modbus/devices/hvac/eht-topventil-plus.json +37 -8
- boneio/modbus/devices/hvac/fujitsu-ac.json +7 -2
- boneio/modbus/devices/{inverters → hvac}/thessla.json +7 -2
- boneio/modbus/devices/hvac/ventclear.json +8 -3
- boneio/modbus/devices/{other → hvac}/wanas415.json +5 -2
- boneio/modbus/devices/inverters/sofar.json +378 -249
- boneio/modbus/devices/other/esp32_relay_x4_modbus.json +7 -2
- boneio/modbus/devices/other/n4dsc08.json +7 -2
- boneio/modbus/devices/other/r4dcb08.json +70 -9
- boneio/modbus/devices/sensors/boneio-edge-temp.json +152 -137
- boneio/modbus/devices/sensors/cwt.json +28 -4
- boneio/modbus/devices/sensors/dyp-a12-ultrasonic.json +14 -2
- boneio/modbus/devices/sensors/gdfs.json +7 -2
- boneio/modbus/devices/sensors/gdfx.json +7 -2
- boneio/modbus/devices/sensors/liquid-sensor.json +18 -3
- boneio/modbus/devices/sensors/pt100.json +29 -17
- boneio/modbus/devices/sensors/sht20.json +27 -4
- boneio/modbus/devices/sensors/sht30.json +27 -4
- boneio/modbus/entities/derived/text.py +0 -1
- boneio/modbus/mock_coordinator.py +283 -0
- boneio/runner.py +11 -0
- boneio/schema/schema.yaml +1 -27
- boneio/version.py +1 -1
- boneio/webui/app.py +7 -0
- boneio/webui/dashboard_cards.py +39 -0
- boneio/webui/frontend-dist/assets/{ConfigEditor-DaxrGxsh.js → ConfigEditor-B5IWaYQG.js} +1 -1
- boneio/webui/frontend-dist/assets/{aiWizardPrompt-D9Tr5iGZ.js → aiWizardPrompt-DGU7pfbV.js} +1 -1
- boneio/webui/frontend-dist/assets/index-D6g-vdBv.css +2 -0
- boneio/webui/frontend-dist/assets/{index-B5k6qEwR.js → index-Uwrd1-uP.js} +29 -29
- boneio/webui/frontend-dist/index.html +2 -2
- boneio/webui/frontend-dist/schema/config.schema.json +7 -1
- boneio/webui/frontend-dist/schema/modbus_devices.schema.json +7 -1
- boneio/webui/frontend-dist/sw.js +1 -1
- boneio/webui/hvac_svg_templates.py +240 -0
- boneio/webui/modbus_card_templates.py +576 -0
- boneio/webui/routes/__init__.py +3 -0
- boneio/webui/routes/dashboard.py +33 -35
- boneio/webui/routes/dev_fake_device.py +703 -0
- boneio/webui/routes/modbus.py +40 -1
- boneio/webui/schema/config.schema.json +7 -1
- boneio/webui/schema/modbus_devices.schema.json +7 -1
- {boneio-1.5.0.dev3.dist-info → boneio-1.5.0.dev5.dist-info}/METADATA +1 -1
- {boneio-1.5.0.dev3.dist-info → boneio-1.5.0.dev5.dist-info}/RECORD +61 -57
- boneio/modbus/devices/sensors/dts1964_3f.json +0 -242
- boneio/webui/frontend-dist/assets/index-CJECcrbK.css +0 -2
- {boneio-1.5.0.dev3.dist-info → boneio-1.5.0.dev5.dist-info}/WHEEL +0 -0
- {boneio-1.5.0.dev3.dist-info → boneio-1.5.0.dev5.dist-info}/entry_points.txt +0 -0
- {boneio-1.5.0.dev3.dist-info → boneio-1.5.0.dev5.dist-info}/licenses/LICENSE +0 -0
- {boneio-1.5.0.dev3.dist-info → boneio-1.5.0.dev5.dist-info}/top_level.txt +0 -0
|
@@ -109,6 +109,7 @@ class MultiClickDetector:
|
|
|
109
109
|
self._pin = pin
|
|
110
110
|
self._max_long_press_seconds = max_long_press_seconds
|
|
111
111
|
self._state = ClickState()
|
|
112
|
+
self._boot_press_suppressed = False # Set by GpioManager._seed_initial_states
|
|
112
113
|
|
|
113
114
|
# Pre-compute which click types should be delayed in exclusive mode
|
|
114
115
|
# based on enabled sequences
|
|
@@ -491,6 +492,21 @@ class MultiClickDetector:
|
|
|
491
492
|
)
|
|
492
493
|
return
|
|
493
494
|
|
|
495
|
+
# Boot-press suppression: if GpioManager detected this pin was
|
|
496
|
+
# LOW at startup, the first FALLING_EDGE is a phantom boot event.
|
|
497
|
+
# Record the timestamp (for debounce) but do NOT schedule timers.
|
|
498
|
+
if self._boot_press_suppressed:
|
|
499
|
+
_LOGGER.info(
|
|
500
|
+
"Suppressing phantom boot press on %s (%s) — recording timestamp only",
|
|
501
|
+
self._name,
|
|
502
|
+
self._pin,
|
|
503
|
+
)
|
|
504
|
+
self._state.last_press_ts = timestamp_s
|
|
505
|
+
self._state.last_press_loop_ts = None
|
|
506
|
+
self._state.last_release_ts = None
|
|
507
|
+
self._boot_press_suppressed = False
|
|
508
|
+
return
|
|
509
|
+
|
|
494
510
|
_LOGGER.debug("PRESSED: %s (%s)", self._name, self._pin)
|
|
495
511
|
self._state.last_press_ts = timestamp_s
|
|
496
512
|
self._state.last_press_loop_ts = self._loop.time() # Store loop time for duration calc
|
|
@@ -552,6 +568,35 @@ class MultiClickDetector:
|
|
|
552
568
|
|
|
553
569
|
press_duration_ms = (timestamp_s - self._state.last_press_ts) * 1000 if self._state.last_press_ts else 0
|
|
554
570
|
_LOGGER.debug("RELEASED: %s (%s) after %.1f ms", self._name, self._pin, press_duration_ms)
|
|
571
|
+
|
|
572
|
+
# Guard: stale release from phantom press at boot.
|
|
573
|
+
# If time since press exceeds max_long_press_seconds, the press
|
|
574
|
+
# was a startup artifact (GPIO settling at boot), not a real user
|
|
575
|
+
# action. Reset the detector so the next real press is accepted.
|
|
576
|
+
if self._state.last_press_ts and press_duration_ms > self._max_long_press_seconds * 1000:
|
|
577
|
+
_LOGGER.warning(
|
|
578
|
+
"Stale release on %s (%s): %.1fs since press exceeds max %.0fs. "
|
|
579
|
+
"Likely phantom press from boot. Resetting detector.",
|
|
580
|
+
self._name,
|
|
581
|
+
self._pin,
|
|
582
|
+
press_duration_ms / 1000,
|
|
583
|
+
self._max_long_press_seconds,
|
|
584
|
+
)
|
|
585
|
+
self._state.last_press_ts = None
|
|
586
|
+
self._state.last_press_loop_ts = None
|
|
587
|
+
self._state.last_release_ts = timestamp_s
|
|
588
|
+
self._state.click_count = 0
|
|
589
|
+
if self._state.long_press_timer:
|
|
590
|
+
self._state.long_press_timer.cancel()
|
|
591
|
+
self._state.long_press_timer = None
|
|
592
|
+
self._state.long_press_scheduled_loop_ts = None
|
|
593
|
+
if self._state.long_hold_periodic_timer:
|
|
594
|
+
self._state.long_hold_periodic_timer.cancel()
|
|
595
|
+
self._state.long_hold_periodic_timer = None
|
|
596
|
+
self._state.executed_long_actions = set()
|
|
597
|
+
self._state.last_repeat_times = {}
|
|
598
|
+
return
|
|
599
|
+
|
|
555
600
|
self._state.last_release_ts = timestamp_s
|
|
556
601
|
|
|
557
602
|
_LOGGER.debug(
|
|
@@ -186,7 +186,13 @@ class BasicOutput(BasicMqtt):
|
|
|
186
186
|
"""
|
|
187
187
|
can_turn_on = self.check_interlock()
|
|
188
188
|
if can_turn_on:
|
|
189
|
-
|
|
189
|
+
try:
|
|
190
|
+
await self._loop.run_in_executor(None, self.turn_on, timestamp)
|
|
191
|
+
except RuntimeError as err:
|
|
192
|
+
if "Executor shutdown" in str(err):
|
|
193
|
+
self.turn_on(timestamp) # Fall back to sync during shutdown
|
|
194
|
+
else:
|
|
195
|
+
raise
|
|
190
196
|
else:
|
|
191
197
|
_LOGGER.warning("Interlock active: cannot turn on %s.", self.id)
|
|
192
198
|
# Workaround for HA is sendind state ON/OFF without physically changing the relay.
|
|
@@ -197,7 +203,13 @@ class BasicOutput(BasicMqtt):
|
|
|
197
203
|
|
|
198
204
|
async def async_turn_off(self, timestamp=None) -> None:
|
|
199
205
|
"""Turn off the relay asynchronously."""
|
|
200
|
-
|
|
206
|
+
try:
|
|
207
|
+
await self._loop.run_in_executor(None, self.turn_off, timestamp)
|
|
208
|
+
except RuntimeError as err:
|
|
209
|
+
if "Executor shutdown" in str(err):
|
|
210
|
+
self.turn_off(timestamp) # Fall back to sync during shutdown
|
|
211
|
+
else:
|
|
212
|
+
raise
|
|
201
213
|
await self.async_send_state()
|
|
202
214
|
|
|
203
215
|
async def async_toggle(self, timestamp=None) -> None:
|
|
@@ -271,7 +271,31 @@ def main():
|
|
|
271
271
|
# Load the schema
|
|
272
272
|
with open(schema_file) as f:
|
|
273
273
|
schema = yaml.load(f, Loader=BoneIOLoader)
|
|
274
|
-
|
|
274
|
+
|
|
275
|
+
# Dynamically inject modbus device models into the schema.
|
|
276
|
+
# NOTE: This is a standalone copy of the logic from yaml_util.py.
|
|
277
|
+
# We cannot import yaml_util here because it pulls in the full boneIO
|
|
278
|
+
# dependency graph (cerberus, pymodbus, smbus2 …) which is NOT installed
|
|
279
|
+
# in the CI environment that runs this converter script.
|
|
280
|
+
devices_dir = os.path.normpath(
|
|
281
|
+
os.path.join(script_dir, "..", "..", "modbus", "devices")
|
|
282
|
+
)
|
|
283
|
+
models: list[str] = []
|
|
284
|
+
if os.path.isdir(devices_dir):
|
|
285
|
+
for root, dirs, files in os.walk(devices_dir):
|
|
286
|
+
dirs[:] = [d for d in dirs if d != "__pycache__"]
|
|
287
|
+
for fname in files:
|
|
288
|
+
if fname.endswith(".json"):
|
|
289
|
+
models.append(fname[:-5])
|
|
290
|
+
models.sort()
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
model_field = schema["modbus_devices"]["schema"]["schema"]["model"]
|
|
294
|
+
model_field["allowed"] = models
|
|
295
|
+
print(f"Injected {len(models)} modbus models into schema")
|
|
296
|
+
except (KeyError, TypeError):
|
|
297
|
+
print("WARNING: Could not inject modbus models into schema")
|
|
298
|
+
|
|
275
299
|
# Convert and save the main schema
|
|
276
300
|
json_schema = convert_cerberus_to_json_schema(schema)
|
|
277
301
|
main_schema_file = os.path.join(output_dir, "config.schema.json")
|
boneio/core/config/yaml_util.py
CHANGED
|
@@ -26,12 +26,39 @@ _SCHEMA_CACHE = None
|
|
|
26
26
|
_BOARD_CONFIG_CACHE = {}
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def _get_modbus_device_models() -> list[str]:
|
|
30
|
+
"""Scan modbus/devices/ directory and return filenames as model keys.
|
|
31
|
+
|
|
32
|
+
Only does os.walk (no JSON parsing) — <1ms even on BeagleBone.
|
|
33
|
+
"""
|
|
34
|
+
devices_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), "../../modbus/devices"))
|
|
35
|
+
models = []
|
|
36
|
+
if os.path.isdir(devices_dir):
|
|
37
|
+
for root, dirs, files in os.walk(devices_dir):
|
|
38
|
+
dirs[:] = [d for d in dirs if d != "__pycache__"]
|
|
39
|
+
for fname in files:
|
|
40
|
+
if fname.endswith(".json"):
|
|
41
|
+
models.append(fname[:-5])
|
|
42
|
+
return sorted(models)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _inject_modbus_models(schema: dict) -> None:
|
|
46
|
+
"""Dynamically set allowed modbus model values from device files."""
|
|
47
|
+
try:
|
|
48
|
+
model_field = schema["modbus_devices"]["schema"]["schema"]["model"]
|
|
49
|
+
model_field["allowed"] = _get_modbus_device_models()
|
|
50
|
+
_LOGGER.debug("Injected %d modbus models into schema", len(model_field["allowed"]))
|
|
51
|
+
except (KeyError, TypeError):
|
|
52
|
+
_LOGGER.warning("Could not inject modbus models into schema")
|
|
53
|
+
|
|
54
|
+
|
|
29
55
|
def _get_schema():
|
|
30
56
|
"""Get schema from cache or load it if not cached."""
|
|
31
57
|
global _SCHEMA_CACHE
|
|
32
58
|
if _SCHEMA_CACHE is None:
|
|
33
59
|
_LOGGER.debug("Loading schema from file (first time)")
|
|
34
60
|
_SCHEMA_CACHE = load_yaml_file(schema_file)
|
|
61
|
+
_inject_modbus_models(_SCHEMA_CACHE)
|
|
35
62
|
return _SCHEMA_CACHE
|
|
36
63
|
|
|
37
64
|
|
|
@@ -653,29 +680,14 @@ class CustomValidator(Validator):
|
|
|
653
680
|
raise ValueError(f"Could not parse power value: {value}")
|
|
654
681
|
num = float(match.group(1))
|
|
655
682
|
unit = match.group(2) or "w"
|
|
656
|
-
if unit in ("w", ""):
|
|
683
|
+
if unit in ("w", "", "wh"):
|
|
657
684
|
multiplier = 1.0
|
|
658
|
-
elif unit
|
|
659
|
-
multiplier = 1000.0
|
|
660
|
-
elif unit == "mw":
|
|
661
|
-
multiplier = 1_000_000.0
|
|
662
|
-
elif unit == "gw":
|
|
663
|
-
multiplier = 1_000_000_000.0
|
|
664
|
-
elif unit == "mw":
|
|
665
|
-
multiplier = 1_000_000.0
|
|
666
|
-
elif unit == "kwh":
|
|
667
|
-
# 1 kWh = 1000 W (for 1h). For config, treat as 1000W average.
|
|
685
|
+
elif unit in ("kw", "kwh"):
|
|
668
686
|
multiplier = 1000.0
|
|
669
|
-
elif unit
|
|
687
|
+
elif unit in ("mw", "mwh"):
|
|
670
688
|
multiplier = 1_000_000.0
|
|
671
|
-
elif unit
|
|
689
|
+
elif unit in ("gw", "gwh"):
|
|
672
690
|
multiplier = 1_000_000_000.0
|
|
673
|
-
elif unit == "mw":
|
|
674
|
-
multiplier = 1_000_000.0
|
|
675
|
-
elif unit == "wh":
|
|
676
|
-
multiplier = 1.0
|
|
677
|
-
elif unit == "mw" or unit == "mw" or unit == "mw" or unit == "mw" or unit == "mw" or unit == "mw":
|
|
678
|
-
multiplier = 1_000_000.0
|
|
679
691
|
else:
|
|
680
692
|
_LOGGER.warning(f"Unknown unit for power value: {unit}")
|
|
681
693
|
raise ValueError(f"Unknown unit for power value: {unit}")
|
boneio/core/state/manager.py
CHANGED
|
@@ -25,6 +25,7 @@ class StateManager:
|
|
|
25
25
|
_LOGGER.info("Loaded state file from %s", self._file)
|
|
26
26
|
self._file_uptodate = False
|
|
27
27
|
self._save_attributes_callback = None
|
|
28
|
+
self._shutting_down = False
|
|
28
29
|
|
|
29
30
|
def load_states(self) -> dict:
|
|
30
31
|
"""Load state file.
|
|
@@ -105,6 +106,8 @@ class StateManager:
|
|
|
105
106
|
if attr_type not in self._state:
|
|
106
107
|
self._state[attr_type] = {}
|
|
107
108
|
self._state[attr_type][attribute] = value
|
|
109
|
+
if self._shutting_down:
|
|
110
|
+
return
|
|
108
111
|
if self._save_attributes_callback is not None:
|
|
109
112
|
self._save_attributes_callback.cancel()
|
|
110
113
|
self._save_attributes_callback = None
|
|
@@ -182,4 +185,26 @@ class StateManager:
|
|
|
182
185
|
# Let's not save state if something happens same time.
|
|
183
186
|
return False
|
|
184
187
|
async with self._lock:
|
|
185
|
-
|
|
188
|
+
try:
|
|
189
|
+
return await self._loop.run_in_executor(None, self._save_state)
|
|
190
|
+
except RuntimeError as err:
|
|
191
|
+
if "Executor shutdown" in str(err):
|
|
192
|
+
# Python 3.13+: executor already closed, fall back to sync
|
|
193
|
+
_LOGGER.debug("Executor shutdown, saving state synchronously")
|
|
194
|
+
return self._save_state()
|
|
195
|
+
raise
|
|
196
|
+
|
|
197
|
+
def cancel_pending_and_save(self) -> None:
|
|
198
|
+
"""Cancel any pending deferred save and do a final synchronous write.
|
|
199
|
+
|
|
200
|
+
Must be called during application shutdown BEFORE the event loop
|
|
201
|
+
closes the default executor. This prevents the RuntimeError:
|
|
202
|
+
'Executor shutdown has been called' on Python 3.13+.
|
|
203
|
+
"""
|
|
204
|
+
self._shutting_down = True
|
|
205
|
+
if self._save_attributes_callback is not None:
|
|
206
|
+
self._save_attributes_callback.cancel()
|
|
207
|
+
self._save_attributes_callback = None
|
|
208
|
+
# Final synchronous save to persist any in-memory changes
|
|
209
|
+
self._save_state()
|
|
210
|
+
_LOGGER.debug("StateManager: pending save cancelled, final state written")
|
|
@@ -302,6 +302,58 @@ class GpioManager:
|
|
|
302
302
|
_LOGGER.error("Error in on-start callback: %s", e)
|
|
303
303
|
self._on_start_callbacks.clear()
|
|
304
304
|
|
|
305
|
+
# Seed detectors with current GPIO state to prevent phantom events
|
|
306
|
+
# at boot. Must run after on-start callbacks so detectors are ready.
|
|
307
|
+
self._seed_initial_states()
|
|
308
|
+
|
|
309
|
+
def _seed_initial_states(self) -> None:
|
|
310
|
+
"""Read current GPIO states and seed detectors to prevent phantom presses.
|
|
311
|
+
|
|
312
|
+
At boot, some GPIO pins may already be LOW (e.g. button circuits pulling
|
|
313
|
+
down through pull-up resistors). gpiod generates a FALLING_EDGE for
|
|
314
|
+
this initial state, which the detector interprets as a real button
|
|
315
|
+
press — scheduling a long press timer that fires 400ms later and
|
|
316
|
+
triggers an action.
|
|
317
|
+
|
|
318
|
+
By setting ``_boot_press_suppressed`` on the detector, the first
|
|
319
|
+
FALLING_EDGE records the kernel timestamp (for debounce) but does
|
|
320
|
+
NOT schedule the long press timer. The subsequent RISING_EDGE
|
|
321
|
+
(when the user actually presses) is caught by the stale-release
|
|
322
|
+
guard and handled correctly.
|
|
323
|
+
"""
|
|
324
|
+
from boneio.components.input.detectors import MultiClickDetector
|
|
325
|
+
|
|
326
|
+
seeded = 0
|
|
327
|
+
for chip, request in self._requests.items():
|
|
328
|
+
for (c, line), detector in self._detectors.items():
|
|
329
|
+
if c != chip:
|
|
330
|
+
continue
|
|
331
|
+
if not isinstance(detector, MultiClickDetector):
|
|
332
|
+
continue
|
|
333
|
+
try:
|
|
334
|
+
values = request.get_values([line])
|
|
335
|
+
pin_is_low = not bool(values[0])
|
|
336
|
+
except Exception as exc:
|
|
337
|
+
_LOGGER.debug(
|
|
338
|
+
"Could not read initial state for chip%d/line%d: %s",
|
|
339
|
+
chip, line, exc,
|
|
340
|
+
)
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
if pin_is_low:
|
|
344
|
+
alias = self._aliases.get((chip, line), f"chip{chip}/line{line}")
|
|
345
|
+
_LOGGER.info(
|
|
346
|
+
"GPIO %s is LOW at startup — arming boot press suppression",
|
|
347
|
+
alias,
|
|
348
|
+
)
|
|
349
|
+
detector._boot_press_suppressed = True
|
|
350
|
+
seeded += 1
|
|
351
|
+
|
|
352
|
+
if seeded:
|
|
353
|
+
_LOGGER.info(
|
|
354
|
+
"Armed boot press suppression on %d GPIO detector(s)", seeded
|
|
355
|
+
)
|
|
356
|
+
|
|
305
357
|
def _handle_gpiod_events(self, chip: int, request: gpiod.LineRequest) -> None:
|
|
306
358
|
"""Handle GPIO events from libgpiod.
|
|
307
359
|
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
{
|
|
2
|
+
"model": "DTS1964 3-phase meter",
|
|
3
|
+
"manufacturer": "DTS",
|
|
4
|
+
"description": "DTS1964 3-phase power & energy meter",
|
|
5
|
+
"category": "energy_meters",
|
|
6
|
+
"default_address": 1,
|
|
7
|
+
"default_update_interval": "10s",
|
|
8
|
+
"registers_base": [
|
|
9
|
+
{
|
|
10
|
+
"base": 0,
|
|
11
|
+
"length": 60,
|
|
12
|
+
"register_type": "input",
|
|
13
|
+
"registers": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Voltage_Phase1",
|
|
16
|
+
"address": 0,
|
|
17
|
+
"unit_of_measurement": "V",
|
|
18
|
+
"state_class": "measurement",
|
|
19
|
+
"device_class": "voltage",
|
|
20
|
+
"value_type": "FP32"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "Voltage_Phase2",
|
|
24
|
+
"address": 2,
|
|
25
|
+
"unit_of_measurement": "V",
|
|
26
|
+
"state_class": "measurement",
|
|
27
|
+
"device_class": "voltage",
|
|
28
|
+
"value_type": "FP32"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "Voltage_Phase3",
|
|
32
|
+
"address": 4,
|
|
33
|
+
"unit_of_measurement": "V",
|
|
34
|
+
"state_class": "measurement",
|
|
35
|
+
"device_class": "voltage",
|
|
36
|
+
"value_type": "FP32"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "Voltage_Phase1_to_Phase2",
|
|
40
|
+
"address": 6,
|
|
41
|
+
"unit_of_measurement": "V",
|
|
42
|
+
"state_class": "measurement",
|
|
43
|
+
"device_class": "voltage",
|
|
44
|
+
"value_type": "FP32"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "Voltage_Phase2_to_Phase3",
|
|
48
|
+
"address": 8,
|
|
49
|
+
"unit_of_measurement": "V",
|
|
50
|
+
"state_class": "measurement",
|
|
51
|
+
"device_class": "voltage",
|
|
52
|
+
"value_type": "FP32"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "Voltage_Phase3_to_Phase1",
|
|
56
|
+
"address": 10,
|
|
57
|
+
"unit_of_measurement": "V",
|
|
58
|
+
"state_class": "measurement",
|
|
59
|
+
"device_class": "voltage",
|
|
60
|
+
"value_type": "FP32"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "Current_Phase1",
|
|
64
|
+
"address": 12,
|
|
65
|
+
"unit_of_measurement": "A",
|
|
66
|
+
"state_class": "measurement",
|
|
67
|
+
"device_class": "current",
|
|
68
|
+
"value_type": "FP32"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "Current_Phase2",
|
|
72
|
+
"address": 14,
|
|
73
|
+
"unit_of_measurement": "A",
|
|
74
|
+
"state_class": "measurement",
|
|
75
|
+
"device_class": "current",
|
|
76
|
+
"value_type": "FP32"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "Current_Phase3",
|
|
80
|
+
"address": 16,
|
|
81
|
+
"unit_of_measurement": "A",
|
|
82
|
+
"state_class": "measurement",
|
|
83
|
+
"device_class": "current",
|
|
84
|
+
"value_type": "FP32"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "Power_Phase1",
|
|
88
|
+
"address": 18,
|
|
89
|
+
"unit_of_measurement": "kW",
|
|
90
|
+
"state_class": "measurement",
|
|
91
|
+
"device_class": "power",
|
|
92
|
+
"value_type": "FP32"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "Power_Phase2",
|
|
96
|
+
"address": 20,
|
|
97
|
+
"unit_of_measurement": "kW",
|
|
98
|
+
"state_class": "measurement",
|
|
99
|
+
"device_class": "power",
|
|
100
|
+
"value_type": "FP32"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "Power_Phase3",
|
|
104
|
+
"address": 22,
|
|
105
|
+
"unit_of_measurement": "kW",
|
|
106
|
+
"state_class": "measurement",
|
|
107
|
+
"device_class": "power",
|
|
108
|
+
"value_type": "FP32"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "Total_Power",
|
|
112
|
+
"address": 24,
|
|
113
|
+
"unit_of_measurement": "kW",
|
|
114
|
+
"state_class": "measurement",
|
|
115
|
+
"device_class": "power",
|
|
116
|
+
"value_type": "FP32"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "Reactive_Power_Phase1",
|
|
120
|
+
"address": 26,
|
|
121
|
+
"unit_of_measurement": "kvar",
|
|
122
|
+
"state_class": "measurement",
|
|
123
|
+
"value_type": "FP32"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "Reactive_Power_Phase2",
|
|
127
|
+
"address": 28,
|
|
128
|
+
"unit_of_measurement": "kvar",
|
|
129
|
+
"state_class": "measurement",
|
|
130
|
+
"value_type": "FP32"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "Reactive_Power_Phase3",
|
|
134
|
+
"address": 30,
|
|
135
|
+
"unit_of_measurement": "kvar",
|
|
136
|
+
"state_class": "measurement",
|
|
137
|
+
"value_type": "FP32"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "Total_Reactive_Power",
|
|
141
|
+
"address": 32,
|
|
142
|
+
"unit_of_measurement": "kvar",
|
|
143
|
+
"state_class": "measurement",
|
|
144
|
+
"value_type": "FP32"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "Apparent_Power_Phase1",
|
|
148
|
+
"address": 34,
|
|
149
|
+
"unit_of_measurement": "kVA",
|
|
150
|
+
"state_class": "measurement",
|
|
151
|
+
"value_type": "FP32"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "Apparent_Power_Phase2",
|
|
155
|
+
"address": 36,
|
|
156
|
+
"unit_of_measurement": "kVA",
|
|
157
|
+
"state_class": "measurement",
|
|
158
|
+
"value_type": "FP32"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"name": "Apparent_Power_Phase3",
|
|
162
|
+
"address": 38,
|
|
163
|
+
"unit_of_measurement": "kVA",
|
|
164
|
+
"state_class": "measurement",
|
|
165
|
+
"value_type": "FP32"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "Total_Apparent_Power",
|
|
169
|
+
"address": 40,
|
|
170
|
+
"unit_of_measurement": "kVA",
|
|
171
|
+
"state_class": "measurement",
|
|
172
|
+
"value_type": "FP32"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "Power_Factor_Phase1",
|
|
176
|
+
"address": 42,
|
|
177
|
+
"unit_of_measurement": "",
|
|
178
|
+
"state_class": "measurement",
|
|
179
|
+
"device_class": "power_factor",
|
|
180
|
+
"value_type": "FP32"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "Power_Factor_Phase2",
|
|
184
|
+
"address": 44,
|
|
185
|
+
"unit_of_measurement": "",
|
|
186
|
+
"state_class": "measurement",
|
|
187
|
+
"device_class": "power_factor",
|
|
188
|
+
"value_type": "FP32"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"name": "Power_Factor_Phase3",
|
|
192
|
+
"address": 46,
|
|
193
|
+
"unit_of_measurement": "",
|
|
194
|
+
"state_class": "measurement",
|
|
195
|
+
"device_class": "power_factor",
|
|
196
|
+
"value_type": "FP32"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"name": "Power_Factor_Total",
|
|
200
|
+
"address": 48,
|
|
201
|
+
"unit_of_measurement": "",
|
|
202
|
+
"state_class": "measurement",
|
|
203
|
+
"device_class": "power_factor",
|
|
204
|
+
"value_type": "FP32"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "Frequency",
|
|
208
|
+
"address": 50,
|
|
209
|
+
"unit_of_measurement": "Hz",
|
|
210
|
+
"state_class": "measurement",
|
|
211
|
+
"device_class": "frequency",
|
|
212
|
+
"value_type": "FP32"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"name": "Import_Energy_Total",
|
|
216
|
+
"address": 52,
|
|
217
|
+
"unit_of_measurement": "kWh",
|
|
218
|
+
"state_class": "total_increasing",
|
|
219
|
+
"device_class": "energy",
|
|
220
|
+
"value_type": "FP32"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "Export_Energy_Total",
|
|
224
|
+
"address": 54,
|
|
225
|
+
"unit_of_measurement": "kWh",
|
|
226
|
+
"state_class": "total_increasing",
|
|
227
|
+
"device_class": "energy",
|
|
228
|
+
"value_type": "FP32"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"name": "Import_Reactive_Energy_Total",
|
|
232
|
+
"address": 56,
|
|
233
|
+
"unit_of_measurement": "kvarh",
|
|
234
|
+
"state_class": "total_increasing",
|
|
235
|
+
"value_type": "FP32"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"name": "Export_Reactive_Energy_Total",
|
|
239
|
+
"address": 58,
|
|
240
|
+
"unit_of_measurement": "kvarh",
|
|
241
|
+
"state_class": "total_increasing",
|
|
242
|
+
"value_type": "FP32"
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}
|