Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
380 changes: 217 additions & 163 deletions custom_components/solax_modbus/__init__.py

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions custom_components/solax_modbus/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e
if button_info.value_function:
hub.computedEntities[button_info.key] = button_info
elif button_info.command is None:
_LOGGER.warning(f"button without command and without value_function found: {button_info.key}")
_LOGGER.warning("button without command and without value_function found: %s", button_info.key)

# register dependency chain
deplist = button_info.depends_on
Expand All @@ -60,13 +60,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e
tuple,
),
):
_LOGGER.debug(f"{hub.name}: {button_info.key} depends on entities {deplist}")
_LOGGER.debug("%s: %s depends on entities %s", hub.name, button_info.key, deplist)
for dep_on in deplist: # register inter-sensor dependencies (e.g. for value functions)
if dep_on != button_info.key:
hub.entity_dependencies.setdefault(dep_on, []).append(button_info.key) # can be more than one

async_add_entities(entities)
_LOGGER.info(f"hub.wakeuButton: {hub.wakeupButton}")
_LOGGER.info("hub.wakeuButton: %s", hub.wakeupButton)
return True


Expand Down Expand Up @@ -110,15 +110,15 @@ def unique_id(self) -> str | None:
async def async_press(self) -> None:
"""Write the button value."""
if self._write_method == WRITE_MULTISINGLE_MODBUS:
_LOGGER.info(f"writing {self._platform_name} button register {self._register} value {self._command}")
_LOGGER.info("writing %s button register %s value %s", self._platform_name, self._register, self._command)
await self._hub.async_write_registers_single(
unit=self._modbus_addr,
address=self._register,
payload=self._command,
register_data_type=getattr(self.button_info, "register_data_type", None),
)
elif self._write_method == WRITE_SINGLE_MODBUS:
_LOGGER.info(f"writing {self._platform_name} button register {self._register} value {self._command}")
_LOGGER.info("writing %s button register %s value %s", self._platform_name, self._register, self._command)
await self._hub.async_write_register(
unit=self._modbus_addr,
address=self._register,
Expand All @@ -137,8 +137,8 @@ async def async_press(self) -> None:
data = res.get("data", None)
action = res.get("action")
if not action:
_LOGGER.error(f"autorepeat value function for {self._key} must return dict containing action")
_LOGGER.info(f"writing {self._platform_name} button register {self._register} value {res}")
_LOGGER.error("autorepeat value function for %s must return dict containing action", self._key)
_LOGGER.info("writing %s button register %s value %s", self._platform_name, self._register, res)
if action == WRITE_MULTI_MODBUS:
await self._hub.async_write_registers_multi(unit=self._modbus_addr, address=reg, payload=data)
else:
Expand Down
20 changes: 10 additions & 10 deletions custom_components/solax_modbus/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def _configured_hub_names(handler: SchemaCommonFlowHandler) -> set[str]:


async def _validate_base(handler: SchemaCommonFlowHandler, user_input: dict[str, Any]) -> dict[str, Any]:
_LOGGER.info(f"validating base: {user_input}")
_LOGGER.info("validating base: %s", user_input)
"""Validate config."""
user_input[CONF_INTERFACE]
user_input[CONF_MODBUS_ADDR]
Expand All @@ -229,15 +229,15 @@ async def _validate_base(handler: SchemaCommonFlowHandler, user_input: dict[str,
# convert old style to new style plugin name here - Remove later after a breaking upgrade
if pluginconf_name.startswith("custom_components") or pluginconf_name.startswith("/config") or pluginconf_name.startswith("plugin_"):
newpluginname = pluginconf_name.split("plugin_", 1)[1][:-3] # getPluginName(pluginconf_name)
_LOGGER.warning(f"converting old style plugin name {pluginconf_name} to new style: {newpluginname} ")
_LOGGER.warning("converting old style plugin name %s to new style: %s ", pluginconf_name, newpluginname)
user_input[CONF_PLUGIN] = newpluginname
pluginconf_name = newpluginname
# end of conversion

_LOGGER.info(f"validating base config for {name}: pre: {user_input}")
_LOGGER.info("validating base config for %s: pre: %s", name, user_input)
# if getPlugin(name) or ((name == DEFAULT_NAME) and (pluginconf_name != DEFAULT_PLUGIN)):
if (name == DEFAULT_NAME) and (pluginconf_name != DEFAULT_PLUGIN):
_LOGGER.warning(f"instance name {name} already defined or default name for non-default inverter")
_LOGGER.warning("instance name %s already defined or default name for non-default inverter", name)
user_input[CONF_NAME] = user_input[CONF_PLUGIN] # getPluginName(user_input[CONF_PLUGIN])
raise SchemaFlowError("name_already_used")

Expand All @@ -261,7 +261,7 @@ async def _validate_host(handler: SchemaCommonFlowHandler, user_input: Any) -> A
res = all(x and not disallowed.search(x) for x in host.split("."))
if not res:
raise SchemaFlowError("invalid_host") from e
_LOGGER.info(f"validating host: returning data: {user_input}")
_LOGGER.info("validating host: returning data: %s", user_input)

pluginconf_name = handler.options[CONF_PLUGIN]
plugin = await handler.parent_handler.hass.async_add_executor_job(_load_plugin, pluginconf_name)
Expand All @@ -287,7 +287,7 @@ async def _next_step_modbus(user_input: Any) -> str:


async def _next_step_battery(user_input: Any) -> str | None:
_LOGGER.debug(f"_next_step_battery: returning data: {user_input}")
_LOGGER.debug("_next_step_battery: returning data: %s", user_input)
if user_input.get("support-battery", False):
return "battery"
return "duplicate_inverter"
Expand Down Expand Up @@ -329,7 +329,7 @@ def _load_plugin(plugin_name: str) -> ModuleType:


if (MAJOR_VERSION >= 2023) or ((MAJOR_VERSION == 2022) and (MINOR_VERSION >= 12)): # type: ignore[comparison-overlap] # backward compat
_LOGGER.info(f"detected HA core version {MAJOR_VERSION} {MINOR_VERSION}")
_LOGGER.info("detected HA core version %s %s", MAJOR_VERSION, MINOR_VERSION)
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"user": SchemaFlowFormStep(CONFIG_SCHEMA, validate_user_input=_validate_base, next_step=_next_step_modbus),
"serial": SchemaFlowFormStep(SERIAL_SCHEMA, next_step=_next_step_battery),
Expand All @@ -348,7 +348,7 @@ def _load_plugin(plugin_name: str) -> ModuleType:
}

else: # for older versions - REMOVE SOON
_LOGGER.error(f"detected old HA core version {MAJOR_VERSION} {MINOR_VERSION}")
_LOGGER.error("detected old HA core version %s %s", MAJOR_VERSION, MINOR_VERSION)


class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
Expand All @@ -358,11 +358,11 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con
"""Handle a flow initialized by the user."""
return await super().async_step_user(user_input)

_LOGGER.info(f"starting configflow - domain = {DOMAIN}")
_LOGGER.info("starting configflow - domain = %s", DOMAIN)
config_flow = CONFIG_FLOW
options_flow = OPTIONS_FLOW

def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
_LOGGER.info(f"title configflow {DOMAIN} {CONF_NAME}: {options}")
_LOGGER.info("title configflow %s %s: %s", DOMAIN, CONF_NAME, options)
# Return config entry title
return cast(str, options[CONF_NAME]) if CONF_NAME in options else ""
4 changes: 2 additions & 2 deletions custom_components/solax_modbus/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def load_debug_settings(config: dict[str, Any] | None, hass: Any = None) -> dict
list(yaml_debug_settings.keys()),
)
except Exception as e:
_LOGGER.debug(f"Error reading debug settings from YAML configuration: {e}")
_LOGGER.debug("Error reading debug settings from YAML configuration: %s", e)

return debug_settings

Expand Down Expand Up @@ -115,7 +115,7 @@ def get_debug_setting(
for key, settings in debug_settings.items():
if key.lower() == inverter_name.lower():
inverter_settings = settings
_LOGGER.debug(f"get_debug_setting: Matched '{inverter_name}' to '{key}' (case-insensitive)")
_LOGGER.debug("get_debug_setting: Matched '%s' to '%s' (case-insensitive)", inverter_name, key)
break

if inverter_settings and setting_name in inverter_settings:
Expand Down
28 changes: 15 additions & 13 deletions custom_components/solax_modbus/energy_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ def get_source_key(self, datadict: dict[str, float]) -> str:
# Prefer PM totals on Primary when available.
# Validate PM sensor exists before using it
if self.source_key_pm not in datadict:
_LOGGER.warning(f"Parallel Master detected but PM sensor {self.source_key_pm} not found, falling back to {self.source_key}")
_LOGGER.warning("Parallel Master detected but PM sensor %s not found, falling back to %s", self.source_key_pm, self.source_key)
return self.source_key
return self.source_key_pm # Use PM sensor on Master

Expand All @@ -327,7 +327,7 @@ def get_value(self, datadict: dict[str, float]) -> float | None:
# This avoids resetting total increasing sensors and unintentionally breaking energy statistics.
value = datadict.get(source_key, None)
if value is None:
_LOGGER.debug(f"Source sensor {source_key} not found or has no value, marking unavailable")
_LOGGER.debug("Source sensor %s not found or has no value, marking unavailable", source_key)
return None

# Apply filter function first (universal - applies to all sensor types)
Expand Down Expand Up @@ -708,7 +708,7 @@ def _create_sensor_from_mapping(
source_hub: Optional hub to read data from (if different from hub, e.g., for Slave sensors)
name_prefix: Optional prefix to add to sensor name (e.g., "All ", "Solax 1 ")
"""
_LOGGER.debug(f"_create_sensor_from_mapping: name_prefix='{name_prefix}', target_key={sensor_mapping.target_key}")
_LOGGER.debug("_create_sensor_from_mapping: name_prefix='%s', target_key=%s", name_prefix, sensor_mapping.target_key)
sensors = []

# Use source_hub if provided, otherwise use hub
Expand All @@ -728,7 +728,7 @@ def value_function(initval: Any, descr: Any, datadict: dict[str, Any]) -> Any:
return sensor_mapping.get_value(hub_data)
except Exception as e:
hub_name = getattr(captured_hub, "_name", "Unknown")
_LOGGER.error(f"Error getting value for {sensor_mapping.target_key} from hub {hub_name}: {e}")
_LOGGER.error("Error getting value for %s from hub %s: %s", sensor_mapping.target_key, hub_name, e)
return None

return value_function
Expand Down Expand Up @@ -862,23 +862,23 @@ def value_function(initval: Any, descr: Any, datadict: dict[str, Any]) -> Any:
master_value = sensor_mapping.get_value(master_data)
total = master_value if master_value is not None else 0
except Exception as e:
_LOGGER.debug(f"{master_name}: Error getting Master value for aggregation: {e}")
_LOGGER.debug("%s: Error getting Master value for aggregation: %s", master_name, e)
total = 0

# Sum all Slave values
for slave_name, slave_hub in slave_hubs:
try:
slave_data = getattr(slave_hub, "data", None) or getattr(slave_hub, "datadict", {})
if not slave_data:
_LOGGER.debug(f"{master_name}: Slave hub '{slave_name}' has no data, using 0 for aggregation")
_LOGGER.debug("%s: Slave hub '%s' has no data, using 0 for aggregation", master_name, slave_name)
continue

slave_value = sensor_mapping.get_value(slave_data)
if slave_value is not None:
total += slave_value
# If slave_value is None, treat as 0 (already handled by not adding)
except Exception as e:
_LOGGER.debug(f"{master_name}: Error getting Slave '{slave_name}' value for aggregation: {e}, using 0")
_LOGGER.debug("%s: Error getting Slave '%s' value for aggregation: %s, using 0", master_name, slave_name, e)
# Continue with other Slaves (treat this Slave as 0)

return total
Expand Down Expand Up @@ -929,14 +929,14 @@ async def create_energy_dashboard_sensors(hub: Any, mapping: EnergyDashboardMapp
default=False,
)
ed_is_master = is_master and not debug_standalone
_LOGGER.info(f"{hub_name}: Energy Dashboard sensor creation - parallel_setting={parallel_setting}, is_master={is_master}")
_LOGGER.info("%s: Energy Dashboard sensor creation - parallel_setting=%s, is_master=%s", hub_name, parallel_setting, is_master)

# Find Slave hubs if this is a Master
slave_hubs = []
if ed_is_master and hass:
slave_hubs = _find_slave_hubs(hass, hub)
if slave_hubs:
_LOGGER.info(f"Found {len(slave_hubs)} registered Slave hub(s) for Energy Dashboard")
_LOGGER.info("Found %s registered Slave hub(s) for Energy Dashboard", len(slave_hubs))
else:
_LOGGER.debug("No Slave hubs found for Energy Dashboard (Master mode but no Slaves)")
elif ed_is_master and not hass:
Expand Down Expand Up @@ -1147,7 +1147,7 @@ def _detect_variants(hub_obj: Any, mapping: Any, base_key: str) -> list[int]:
# Create "Solax 1" sensor (Master individual)
# Check if individual sensors should be skipped
_LOGGER.debug(
f"Master individual check: target_key={sensor_mapping.target_key}, skip_pm_individuals={sensor_mapping.skip_pm_individuals}"
"Master individual check: target_key=%s, skip_pm_individuals=%s", sensor_mapping.target_key, sensor_mapping.skip_pm_individuals
)
if not sensor_mapping.skip_pm_individuals:
# For Master individual, force use of non-PM sensor by setting source_key_pm=None
Expand Down Expand Up @@ -1176,7 +1176,9 @@ def _detect_variants(hub_obj: Any, mapping: Any, base_key: str) -> list[int]:

# Create "Solax 2/3" sensors from Slave hubs
# Check if individual sensors should be skipped
_LOGGER.debug(f"Slave individual check: target_key={sensor_mapping.target_key}, skip_pm_individuals={sensor_mapping.skip_pm_individuals}")
_LOGGER.debug(
"Slave individual check: target_key=%s, skip_pm_individuals=%s", sensor_mapping.target_key, sensor_mapping.skip_pm_individuals
)
if not sensor_mapping.skip_pm_individuals:
for slave_name, slave_hub in slave_hubs:
sensors.extend(
Expand Down Expand Up @@ -1254,12 +1256,12 @@ def validate_mapping(mapping: EnergyDashboardMapping) -> bool:
bool: True if mapping is valid, False otherwise
"""
if not mapping.mappings:
_LOGGER.error(f"Plugin {mapping.plugin_name}: No mappings defined")
_LOGGER.error("Plugin %s: No mappings defined", mapping.plugin_name)
return False

for sensor_mapping in mapping.mappings:
if not sensor_mapping.source_key or not sensor_mapping.target_key:
_LOGGER.error(f"Invalid mapping: missing source_key or target_key for {mapping.plugin_name}")
_LOGGER.error("Invalid mapping: missing source_key or target_key for %s", mapping.plugin_name)
return False

return True
Loading
Loading