mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
Compare commits
1 Commits
ade7880_fi
...
subproc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3ded96bb26 |
@@ -90,7 +90,7 @@ def main():
|
||||
def run_command(*cmd, ignore_error: bool = False):
|
||||
print(f"$ {shlex.join(list(cmd))}")
|
||||
if not args.dry_run:
|
||||
rc = subprocess.call(list(cmd))
|
||||
rc = subprocess.call(list(cmd), close_fds=False)
|
||||
if rc != 0 and not ignore_error:
|
||||
print("Command failed")
|
||||
sys.exit(1)
|
||||
|
@@ -36,7 +36,6 @@ from esphome.const import (
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
@@ -52,20 +51,6 @@ CONF_POWER_GAIN = "power_gain"
|
||||
|
||||
CONF_NEUTRAL = "neutral"
|
||||
|
||||
# Tuple of power channel phases
|
||||
POWER_PHASES = (CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C)
|
||||
|
||||
# Tuple of sensor types that can be configured for power channels
|
||||
POWER_SENSOR_TYPES = (
|
||||
CONF_CURRENT,
|
||||
CONF_VOLTAGE,
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
)
|
||||
|
||||
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NeutralChannel),
|
||||
@@ -165,64 +150,7 @@ POWER_CHANNEL_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def prefix_sensor_name(
|
||||
sensor_conf: ConfigType,
|
||||
channel_name: str,
|
||||
channel_config: ConfigType,
|
||||
sensor_type: str,
|
||||
) -> None:
|
||||
"""Helper to prefix sensor name with channel name.
|
||||
|
||||
Args:
|
||||
sensor_conf: The sensor configuration (dict or string)
|
||||
channel_name: The channel name to prefix with
|
||||
channel_config: The channel configuration to update
|
||||
sensor_type: The sensor type key in the channel config
|
||||
"""
|
||||
if isinstance(sensor_conf, dict) and CONF_NAME in sensor_conf:
|
||||
sensor_name = sensor_conf[CONF_NAME]
|
||||
if sensor_name and not sensor_name.startswith(channel_name):
|
||||
sensor_conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||
elif isinstance(sensor_conf, str):
|
||||
# Simple value case - convert to dict with prefixed name
|
||||
channel_config[sensor_type] = {CONF_NAME: f"{channel_name} {sensor_conf}"}
|
||||
|
||||
|
||||
def process_channel_sensors(
|
||||
config: ConfigType, channel_key: str, sensor_types: tuple
|
||||
) -> None:
|
||||
"""Process sensors for a channel and prefix their names.
|
||||
|
||||
Args:
|
||||
config: The main configuration
|
||||
channel_key: The channel key (e.g., CONF_PHASE_A, CONF_NEUTRAL)
|
||||
sensor_types: Tuple of sensor types to process for this channel
|
||||
"""
|
||||
if not (channel_config := config.get(channel_key)) or not (
|
||||
channel_name := channel_config.get(CONF_NAME)
|
||||
):
|
||||
return
|
||||
|
||||
for sensor_type in sensor_types:
|
||||
if sensor_conf := channel_config.get(sensor_type):
|
||||
prefix_sensor_name(sensor_conf, channel_name, channel_config, sensor_type)
|
||||
|
||||
|
||||
def preprocess_channels(config: ConfigType) -> ConfigType:
|
||||
"""Preprocess channel configurations to add channel name prefix to sensor names."""
|
||||
# Process power channels
|
||||
for channel in POWER_PHASES:
|
||||
process_channel_sensors(config, channel, POWER_SENSOR_TYPES)
|
||||
|
||||
# Process neutral channel
|
||||
process_channel_sensors(config, CONF_NEUTRAL, (CONF_CURRENT,))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
preprocess_channels,
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7880),
|
||||
@@ -239,7 +167,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x38)),
|
||||
.extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
||||
|
||||
@@ -260,7 +188,15 @@ async def neutral_channel(config):
|
||||
async def power_channel(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
for sensor_type in POWER_SENSOR_TYPES:
|
||||
for sensor_type in [
|
||||
CONF_CURRENT,
|
||||
CONF_VOLTAGE,
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
]:
|
||||
if conf := config.get(sensor_type):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{sensor_type}")(sens))
|
||||
@@ -280,6 +216,44 @@ async def power_channel(config):
|
||||
return var
|
||||
|
||||
|
||||
def final_validate(config):
|
||||
for channel in [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]:
|
||||
if channel := config.get(channel):
|
||||
channel_name = channel.get(CONF_NAME)
|
||||
|
||||
for sensor_type in [
|
||||
CONF_CURRENT,
|
||||
CONF_VOLTAGE,
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
]:
|
||||
if conf := channel.get(sensor_type):
|
||||
sensor_name = conf.get(CONF_NAME)
|
||||
if (
|
||||
sensor_name
|
||||
and channel_name
|
||||
and not sensor_name.startswith(channel_name)
|
||||
):
|
||||
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||
|
||||
if channel := config.get(CONF_NEUTRAL):
|
||||
channel_name = channel.get(CONF_NAME)
|
||||
if conf := channel.get(CONF_CURRENT):
|
||||
sensor_name = conf.get(CONF_NAME)
|
||||
if (
|
||||
sensor_name
|
||||
and channel_name
|
||||
and not sensor_name.startswith(channel_name)
|
||||
):
|
||||
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
@@ -36,7 +36,9 @@ def get_sdl_options(value):
|
||||
if value != "":
|
||||
return value
|
||||
try:
|
||||
return subprocess.check_output(["sdl2-config", "--cflags", "--libs"]).decode()
|
||||
return subprocess.check_output(
|
||||
["sdl2-config", "--cflags", "--libs"], close_fds=False
|
||||
).decode()
|
||||
except Exception as e:
|
||||
raise cv.Invalid("Unable to run sdl2-config - have you installed sdl2?") from e
|
||||
|
||||
|
@@ -10,7 +10,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INVERTED,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_STATE,
|
||||
CONF_ON_TURN_OFF,
|
||||
CONF_ON_TURN_ON,
|
||||
CONF_RESTORE_MODE,
|
||||
@@ -57,9 +56,6 @@ TurnOnAction = switch_ns.class_("TurnOnAction", automation.Action)
|
||||
SwitchPublishAction = switch_ns.class_("SwitchPublishAction", automation.Action)
|
||||
|
||||
SwitchCondition = switch_ns.class_("SwitchCondition", Condition)
|
||||
SwitchStateTrigger = switch_ns.class_(
|
||||
"SwitchStateTrigger", automation.Trigger.template(bool)
|
||||
)
|
||||
SwitchTurnOnTrigger = switch_ns.class_(
|
||||
"SwitchTurnOnTrigger", automation.Trigger.template()
|
||||
)
|
||||
@@ -81,11 +77,6 @@ _SWITCH_SCHEMA = (
|
||||
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
|
||||
RESTORE_MODES, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchStateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger),
|
||||
@@ -149,9 +140,6 @@ async def setup_switch_core_(var, config):
|
||||
|
||||
if (inverted := config.get(CONF_INVERTED)) is not None:
|
||||
cg.add(var.set_inverted(inverted))
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
for conf in config.get(CONF_ON_TURN_ON, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
@@ -64,13 +64,6 @@ template<typename... Ts> class SwitchCondition : public Condition<Ts...> {
|
||||
bool state_;
|
||||
};
|
||||
|
||||
class SwitchStateTrigger : public Trigger<bool> {
|
||||
public:
|
||||
SwitchStateTrigger(Switch *a_switch) {
|
||||
a_switch->add_on_state_callback([this](bool state) { this->trigger(state); });
|
||||
}
|
||||
};
|
||||
|
||||
class SwitchTurnOnTrigger : public Trigger<> {
|
||||
public:
|
||||
SwitchTurnOnTrigger(Switch *a_switch) {
|
||||
|
@@ -229,6 +229,7 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
close_fds=False,
|
||||
)
|
||||
stdout_thread = threading.Thread(target=self._stdout_thread)
|
||||
stdout_thread.daemon = True
|
||||
|
@@ -17,7 +17,9 @@ _LOGGER = logging.getLogger(__name__)
|
||||
def run_git_command(cmd, cwd=None) -> str:
|
||||
_LOGGER.debug("Running git command: %s", " ".join(cmd))
|
||||
try:
|
||||
ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False)
|
||||
ret = subprocess.run(
|
||||
cmd, cwd=cwd, capture_output=True, check=False, close_fds=False
|
||||
)
|
||||
except FileNotFoundError as err:
|
||||
raise cv.Invalid(
|
||||
"git is not installed but required for external_components.\n"
|
||||
|
@@ -114,7 +114,9 @@ def cpp_string_escape(string, encoding="utf-8"):
|
||||
def run_system_command(*args):
|
||||
import subprocess
|
||||
|
||||
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
|
||||
with subprocess.Popen(
|
||||
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False
|
||||
) as p:
|
||||
stdout, stderr = p.communicate()
|
||||
rc = p.returncode
|
||||
return rc, stdout, stderr
|
||||
|
@@ -211,7 +211,7 @@ def _decode_pc(config, addr):
|
||||
return
|
||||
command = [idedata.addr2line_path, "-pfiaC", "-e", idedata.firmware_elf_path, addr]
|
||||
try:
|
||||
translation = subprocess.check_output(command).decode().strip()
|
||||
translation = subprocess.check_output(command, close_fds=False).decode().strip()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.debug("Caught exception for command %s", command, exc_info=1)
|
||||
return
|
||||
|
@@ -239,7 +239,12 @@ def run_external_process(*cmd: str, **kwargs: Any) -> int | str:
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd, stdout=sub_stdout, stderr=sub_stderr, encoding="utf-8", check=False
|
||||
cmd,
|
||||
stdout=sub_stdout,
|
||||
stderr=sub_stderr,
|
||||
encoding="utf-8",
|
||||
check=False,
|
||||
close_fds=False,
|
||||
)
|
||||
return proc.stdout if capture_stdout else proc.returncode
|
||||
except KeyboardInterrupt: # pylint: disable=try-except-raise
|
||||
|
@@ -31,7 +31,11 @@ def run_format(executable, args, queue, lock, failed_files):
|
||||
invocation.append(path)
|
||||
|
||||
proc = subprocess.run(
|
||||
invocation, capture_output=True, encoding="utf-8", check=False
|
||||
invocation,
|
||||
capture_output=True,
|
||||
encoding="utf-8",
|
||||
check=False,
|
||||
close_fds=False,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
with lock:
|
||||
|
@@ -158,7 +158,11 @@ def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files):
|
||||
invocation.extend(options)
|
||||
|
||||
proc = subprocess.run(
|
||||
invocation, capture_output=True, encoding="utf-8", check=False
|
||||
invocation,
|
||||
capture_output=True,
|
||||
encoding="utf-8",
|
||||
check=False,
|
||||
close_fds=False,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
with lock:
|
||||
@@ -320,9 +324,11 @@ def main():
|
||||
print("Applying fixes ...")
|
||||
try:
|
||||
try:
|
||||
subprocess.call(["clang-apply-replacements-18", tmpdir])
|
||||
subprocess.call(
|
||||
["clang-apply-replacements-18", tmpdir], close_fds=False
|
||||
)
|
||||
except FileNotFoundError:
|
||||
subprocess.call(["clang-apply-replacements", tmpdir])
|
||||
subprocess.call(["clang-apply-replacements", tmpdir], close_fds=False)
|
||||
except FileNotFoundError:
|
||||
print(
|
||||
"Error please install clang-apply-replacements-18 or clang-apply-replacements.\n",
|
||||
|
@@ -55,4 +55,6 @@ for section in config.sections():
|
||||
tools.append("-t")
|
||||
tools.append(tool)
|
||||
|
||||
subprocess.check_call(["platformio", "pkg", "install", "-g", *libs, *platforms, *tools])
|
||||
subprocess.check_call(
|
||||
["platformio", "pkg", "install", "-g", *libs, *platforms, *tools], close_fds=False
|
||||
)
|
||||
|
@@ -13,7 +13,7 @@ def find_and_activate_virtualenv():
|
||||
try:
|
||||
# Get the top-level directory of the git repository
|
||||
my_path = subprocess.check_output(
|
||||
["git", "rev-parse", "--show-toplevel"], text=True
|
||||
["git", "rev-parse", "--show-toplevel"], text=True, close_fds=False
|
||||
).strip()
|
||||
except subprocess.CalledProcessError:
|
||||
print(
|
||||
@@ -44,7 +44,7 @@ def find_and_activate_virtualenv():
|
||||
def run_command():
|
||||
# Execute the remaining arguments in the new environment
|
||||
if len(sys.argv) > 1:
|
||||
subprocess.run(sys.argv[1:], check=False)
|
||||
subprocess.run(sys.argv[1:], check=False, close_fds=False)
|
||||
else:
|
||||
print(
|
||||
"No command provided to run in the virtual environment.",
|
||||
|
@@ -12,12 +12,12 @@ sensor:
|
||||
frequency: 60Hz
|
||||
phase_a:
|
||||
name: Channel A
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel A Voltage
|
||||
current: Channel A Current
|
||||
active_power: Channel A Active Power
|
||||
power_factor: Channel A Power Factor
|
||||
forward_active_energy: Channel A Forward Active Energy
|
||||
reverse_active_energy: Channel A Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3116628
|
||||
voltage_gain: -757178
|
||||
@@ -25,12 +25,12 @@ sensor:
|
||||
phase_angle: 188
|
||||
phase_b:
|
||||
name: Channel B
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel B Voltage
|
||||
current: Channel B Current
|
||||
active_power: Channel B Active Power
|
||||
power_factor: Channel B Power Factor
|
||||
forward_active_energy: Channel B Forward Active Energy
|
||||
reverse_active_energy: Channel B Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3133655
|
||||
voltage_gain: -755235
|
||||
@@ -38,12 +38,12 @@ sensor:
|
||||
phase_angle: 188
|
||||
phase_c:
|
||||
name: Channel C
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel C Voltage
|
||||
current: Channel C Current
|
||||
active_power: Channel C Active Power
|
||||
power_factor: Channel C Power Factor
|
||||
forward_active_energy: Channel C Forward Active Energy
|
||||
reverse_active_energy: Channel C Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3111158
|
||||
voltage_gain: -743813
|
||||
@@ -51,6 +51,6 @@ sensor:
|
||||
phase_angle: 180
|
||||
neutral:
|
||||
name: Neutral
|
||||
current: Current
|
||||
current: Neutral Current
|
||||
calibration:
|
||||
current_gain: 3189
|
||||
|
@@ -9,18 +9,6 @@ switch:
|
||||
name: "Template Switch"
|
||||
id: the_switch
|
||||
optimistic: true
|
||||
on_state:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return x;
|
||||
then:
|
||||
- logger.log: "Switch turned ON"
|
||||
else:
|
||||
- logger.log: "Switch turned OFF"
|
||||
on_turn_on:
|
||||
- logger.log: "Switch is now ON"
|
||||
on_turn_off:
|
||||
- logger.log: "Switch is now OFF"
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
|
@@ -105,6 +105,7 @@ logger:
|
||||
check=True,
|
||||
cwd=init_dir,
|
||||
env=env,
|
||||
close_fds=False,
|
||||
)
|
||||
|
||||
# Lock is held until here, ensuring cache is fully populated before any test proceeds
|
||||
@@ -245,6 +246,7 @@ async def compile_esphome(
|
||||
# Start in a new process group to isolate signal handling
|
||||
start_new_session=True,
|
||||
env=env,
|
||||
close_fds=False,
|
||||
)
|
||||
await proc.wait()
|
||||
|
||||
@@ -477,6 +479,7 @@ async def run_binary_and_wait_for_port(
|
||||
# Start in a new process group to isolate signal handling
|
||||
start_new_session=True,
|
||||
pass_fds=(device_fd,),
|
||||
close_fds=False,
|
||||
)
|
||||
|
||||
# Close the device end in the parent process
|
||||
|
Reference in New Issue
Block a user