mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
[selec_meter] Add EM4M (3-phase) support via model option (#17782)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick@home-assistant.io> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
Claude Sonnet 5
pre-commit-ci-lite[bot]
J. Nick Koston
J. Nick Koston
parent
8acf885241
commit
5017165800
@@ -0,0 +1,129 @@
|
||||
sensor:
|
||||
- platform: selec_meter
|
||||
modbus_id: modbus_bus
|
||||
model: em4m
|
||||
byte_order: little_endian
|
||||
phase_a:
|
||||
voltage:
|
||||
name: SelecEM4M Voltage L1
|
||||
current:
|
||||
name: SelecEM4M Current L1
|
||||
active_power:
|
||||
name: SelecEM4M Active Power L1
|
||||
reactive_power:
|
||||
name: SelecEM4M Reactive Power L1
|
||||
apparent_power:
|
||||
name: SelecEM4M Apparent Power L1
|
||||
power_factor:
|
||||
name: SelecEM4M Power Factor L1
|
||||
import_active_energy:
|
||||
name: SelecEM4M Import Active Energy L1
|
||||
export_active_energy:
|
||||
name: SelecEM4M Export Active Energy L1
|
||||
import_reactive_energy:
|
||||
name: SelecEM4M Import Reactive Energy L1
|
||||
export_reactive_energy:
|
||||
name: SelecEM4M Export Reactive Energy L1
|
||||
apparent_energy:
|
||||
name: SelecEM4M Apparent Energy L1
|
||||
phase_b:
|
||||
voltage:
|
||||
name: SelecEM4M Voltage L2
|
||||
current:
|
||||
name: SelecEM4M Current L2
|
||||
active_power:
|
||||
name: SelecEM4M Active Power L2
|
||||
reactive_power:
|
||||
name: SelecEM4M Reactive Power L2
|
||||
apparent_power:
|
||||
name: SelecEM4M Apparent Power L2
|
||||
power_factor:
|
||||
name: SelecEM4M Power Factor L2
|
||||
import_active_energy:
|
||||
name: SelecEM4M Import Active Energy L2
|
||||
export_active_energy:
|
||||
name: SelecEM4M Export Active Energy L2
|
||||
import_reactive_energy:
|
||||
name: SelecEM4M Import Reactive Energy L2
|
||||
export_reactive_energy:
|
||||
name: SelecEM4M Export Reactive Energy L2
|
||||
apparent_energy:
|
||||
name: SelecEM4M Apparent Energy L2
|
||||
phase_c:
|
||||
voltage:
|
||||
name: SelecEM4M Voltage L3
|
||||
current:
|
||||
name: SelecEM4M Current L3
|
||||
active_power:
|
||||
name: SelecEM4M Active Power L3
|
||||
reactive_power:
|
||||
name: SelecEM4M Reactive Power L3
|
||||
apparent_power:
|
||||
name: SelecEM4M Apparent Power L3
|
||||
power_factor:
|
||||
name: SelecEM4M Power Factor L3
|
||||
import_active_energy:
|
||||
name: SelecEM4M Import Active Energy L3
|
||||
export_active_energy:
|
||||
name: SelecEM4M Export Active Energy L3
|
||||
import_reactive_energy:
|
||||
name: SelecEM4M Import Reactive Energy L3
|
||||
export_reactive_energy:
|
||||
name: SelecEM4M Export Reactive Energy L3
|
||||
apparent_energy:
|
||||
name: SelecEM4M Apparent Energy L3
|
||||
voltage_l12:
|
||||
name: SelecEM4M Voltage L12
|
||||
voltage_l23:
|
||||
name: SelecEM4M Voltage L23
|
||||
voltage_l31:
|
||||
name: SelecEM4M Voltage L31
|
||||
average_voltage_ll:
|
||||
name: SelecEM4M Average Voltage LL
|
||||
active_power:
|
||||
name: SelecEM4M Active Power
|
||||
reactive_power:
|
||||
name: SelecEM4M Reactive Power
|
||||
apparent_power:
|
||||
name: SelecEM4M Apparent Power
|
||||
voltage:
|
||||
name: SelecEM4M Average Voltage
|
||||
current:
|
||||
name: SelecEM4M Average Current
|
||||
power_factor:
|
||||
name: SelecEM4M Average Power Factor
|
||||
frequency:
|
||||
name: SelecEM4M Frequency
|
||||
import_active_energy:
|
||||
name: SelecEM4M Total Import Active Energy
|
||||
export_active_energy:
|
||||
name: SelecEM4M Total Export Active Energy
|
||||
import_reactive_energy:
|
||||
name: SelecEM4M Total Import Reactive Energy
|
||||
export_reactive_energy:
|
||||
name: SelecEM4M Total Export Reactive Energy
|
||||
net_active_energy_mains:
|
||||
name: SelecEM4M Net Active Energy Mains
|
||||
net_reactive_energy_mains:
|
||||
name: SelecEM4M Net Reactive Energy Mains
|
||||
net_apparent_energy_mains:
|
||||
name: SelecEM4M Net Apparent Energy Mains
|
||||
net_active_energy_dg:
|
||||
name: SelecEM4M Net Active Energy DG
|
||||
net_reactive_energy_dg:
|
||||
name: SelecEM4M Net Reactive Energy DG
|
||||
net_apparent_energy_dg:
|
||||
name: SelecEM4M Net Apparent Energy DG
|
||||
maximum_demand_active_power:
|
||||
name: SelecEM4M Maximum Demand Active Power
|
||||
disabled_by_default: true
|
||||
maximum_demand_reactive_power:
|
||||
name: SelecEM4M Maximum Demand Reactive Power
|
||||
disabled_by_default: true
|
||||
maximum_demand_apparent_power:
|
||||
name: SelecEM4M Maximum Demand Apparent Power
|
||||
disabled_by_default: true
|
||||
serial_number:
|
||||
name: SelecEM4M Serial Number
|
||||
dg_sensing:
|
||||
name: SelecEM4M DG Sensing
|
||||
@@ -0,0 +1,8 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
flow_control_pin: GPIO26
|
||||
|
||||
packages:
|
||||
modbus: !include ../../test_build_components/common/modbus/esp32-idf.yaml
|
||||
selec_meter: !include common-em4m.yaml
|
||||
@@ -0,0 +1,8 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO0
|
||||
rx_pin: GPIO2
|
||||
flow_control_pin: GPIO15
|
||||
|
||||
packages:
|
||||
modbus: !include ../../test_build_components/common/modbus/esp8266-ard.yaml
|
||||
selec_meter: !include common-em4m.yaml
|
||||
@@ -4,6 +4,5 @@ substitutions:
|
||||
flow_control_pin: GPIO26
|
||||
|
||||
packages:
|
||||
selec_meter: !include common.yaml
|
||||
modbus: !include ../../test_build_components/common/modbus/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -4,6 +4,5 @@ substitutions:
|
||||
flow_control_pin: GPIO15
|
||||
|
||||
packages:
|
||||
selec_meter: !include common.yaml
|
||||
modbus: !include ../../test_build_components/common/modbus/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -4,6 +4,5 @@ substitutions:
|
||||
flow_control_pin: GPIO6
|
||||
|
||||
packages:
|
||||
selec_meter: !include common.yaml
|
||||
modbus: !include ../../test_build_components/common/modbus/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Tests for the selec_meter configuration schema."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.selec_meter import sensor as selec_meter
|
||||
import esphome.config_validation as cv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
|
||||
def _validate(config: dict[str, Any]) -> ConfigType:
|
||||
return selec_meter.CONFIG_SCHEMA(config)
|
||||
|
||||
|
||||
def test_model_defaults_to_em2m() -> None:
|
||||
config = _validate({"voltage": {"name": "Voltage"}})
|
||||
assert config["model"] == selec_meter.MODEL_EM2M
|
||||
|
||||
|
||||
def test_model_is_case_insensitive() -> None:
|
||||
config = _validate({"model": "EM4M"})
|
||||
assert config["model"] == selec_meter.MODEL_EM4M
|
||||
|
||||
|
||||
def test_model_rejects_unknown_value() -> None:
|
||||
with pytest.raises(cv.Invalid, match="Unknown value 'em9m'"):
|
||||
_validate({"model": "em9m"})
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "key", "value"),
|
||||
[
|
||||
("em2m", "voltage_l12", {"name": "Test"}),
|
||||
("em2m", "serial_number", {"name": "Test"}),
|
||||
("em2m", "dg_sensing", {"name": "Test"}),
|
||||
("em2m", "phase_a", {"voltage": {"name": "Voltage"}}),
|
||||
("em2m", "phase_b", {"voltage": {"name": "Voltage"}}),
|
||||
("em2m", "phase_c", {"voltage": {"name": "Voltage"}}),
|
||||
("em4m", "total_active_energy", {"name": "Test"}),
|
||||
("em4m", "total_reactive_energy", {"name": "Test"}),
|
||||
("em4m", "apparent_energy", {"name": "Test"}),
|
||||
],
|
||||
)
|
||||
def test_key_rejected_on_wrong_model(model: str, key: str, value: Any) -> None:
|
||||
with pytest.raises(cv.Invalid, match="extra keys not allowed") as err:
|
||||
_validate({"model": model, key: value})
|
||||
assert err.value.path == [key]
|
||||
|
||||
|
||||
def test_em4m_only_key_rejected_on_default_model() -> None:
|
||||
with pytest.raises(cv.Invalid, match="extra keys not allowed"):
|
||||
_validate({"voltage_l12": {"name": "Test"}})
|
||||
|
||||
|
||||
def test_em4m_with_phases_accepted() -> None:
|
||||
config = _validate(
|
||||
{
|
||||
"model": "em4m",
|
||||
"phase_a": {"voltage": {"name": "Voltage L1"}},
|
||||
"phase_c": {"power_factor": {"name": "PF L3"}},
|
||||
}
|
||||
)
|
||||
assert "voltage" in config["phase_a"]
|
||||
assert "power_factor" in config["phase_c"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "byte_order", "word_swap"),
|
||||
[
|
||||
("em2m", "little_endian", True),
|
||||
("em4m", "big_endian", False),
|
||||
],
|
||||
)
|
||||
def test_byte_order_default_per_model(
|
||||
model: str, byte_order: str, word_swap: bool
|
||||
) -> None:
|
||||
config = _validate({"model": model})
|
||||
assert config["byte_order"] == byte_order
|
||||
assert config["byte_order"].enum_value is word_swap
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "byte_order", "word_swap"),
|
||||
[
|
||||
("em2m", "big_endian", False),
|
||||
("em4m", "LITTLE_ENDIAN", True),
|
||||
("em4m", "lsrf", True),
|
||||
("em2m", "msrf", False),
|
||||
],
|
||||
)
|
||||
def test_byte_order_explicit_override(
|
||||
model: str, byte_order: str, word_swap: bool
|
||||
) -> None:
|
||||
config = _validate({"model": model, "byte_order": byte_order})
|
||||
assert config["byte_order"].enum_value is word_swap
|
||||
|
||||
|
||||
def test_byte_order_rejects_unknown_value() -> None:
|
||||
with pytest.raises(cv.Invalid, match="Unknown value"):
|
||||
_validate({"byte_order": "middle_endian"})
|
||||
Reference in New Issue
Block a user