Skip to content

Commit

Permalink
Add update entity and support multiple dynamic sensors
Browse files Browse the repository at this point in the history
  • Loading branch information
zhbjsh committed Dec 9, 2024
1 parent 55344a2 commit bd204ed
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 22 deletions.
27 changes: 25 additions & 2 deletions custom_components/ssh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,15 @@
CONF_ALLOW_TURN_OFF,
CONF_COMMAND_TIMEOUT,
CONF_DISCONNECT_MODE,
CONF_DYNAMIC,
CONF_HOST_KEYS_FILENAME,
CONF_INVOKE_SHELL,
CONF_KEY,
CONF_KEY_FILENAME,
CONF_LOAD_SYSTEM_HOST_KEYS,
CONF_SENSOR_COMMANDS,
CONF_SENSORS,
CONF_SEPARATOR,
CONF_UPDATE_INTERVAL,
DOMAIN,
SERVICE_EXECUTE_COMMAND,
Expand Down Expand Up @@ -81,6 +85,7 @@
Platform.SENSOR,
Platform.SWITCH,
Platform.TEXT,
Platform.UPDATE,
]

DEVICE_SENSOR_KEYS = [
Expand Down Expand Up @@ -117,6 +122,15 @@
)


def _update_sensor_commands(command_configs: list[dict[str, list[dict]]]):
for command_config in command_configs:
for sensor_config in reversed(command_config[CONF_SENSORS]):
if separator := sensor_config.get(CONF_SEPARATOR):
sensor_config.pop(CONF_SEPARATOR)
if sensor_config.get(CONF_DYNAMIC):
command_config[CONF_SEPARATOR] = separator


async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug(
Expand All @@ -125,7 +139,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry):
entry.minor_version,
)

if entry.version > 1:
if entry.version > 2:
return False

if entry.version == 1:
Expand All @@ -139,8 +153,17 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry):
if entry.minor_version < 3:
new_data[CONF_INVOKE_SHELL] = False

if entry.minor_version < 4:
for command_config in new_options[CONF_SENSOR_COMMANDS]:
for sensor_config in reversed(command_config[CONF_SENSORS]):
if not (separator := sensor_config.get(CONF_SEPARATOR)):
continue
sensor_config.pop(CONF_SEPARATOR)
if sensor_config.get(CONF_DYNAMIC):
command_config[CONF_SEPARATOR] = separator

hass.config_entries.async_update_entry(
entry, data=new_data, options=new_options, minor_version=3, version=1
entry, data=new_data, options=new_options, minor_version=1, version=2
)

_LOGGER.debug(
Expand Down
35 changes: 27 additions & 8 deletions custom_components/ssh/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
DEFAULT_LOAD_SYSTEM_HOST_KEYS,
DEFAULT_PORT,
Collection,
InvalidRequiredSensorError,
CommandLoopError,
InvalidSensorError,
NameKeyError,
OfflineError,
SSHAuthenticationError,
Expand Down Expand Up @@ -40,6 +41,9 @@
DEVICE_CLASSES_SCHEMA as SWITCH_DEVICE_CLASSES_SCHEMA,
)
from homeassistant.components.text import TextMode
from homeassistant.components.update import (
DEVICE_CLASSES_SCHEMA as UPDATE_DEVICE_CLASSES_SCHEMA,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_COMMAND,
Expand Down Expand Up @@ -92,6 +96,7 @@
CONF_INVOKE_SHELL,
CONF_KEY,
CONF_KEY_FILENAME,
CONF_LATEST,
CONF_LOAD_SYSTEM_HOST_KEYS,
CONF_OPTIONS,
CONF_PATTERN,
Expand Down Expand Up @@ -136,6 +141,11 @@ def validate_sensor(data: dict) -> dict:
return BINARY_SENSOR_SCHEMA(data)
return CONTROLLABLE_BINARY_SENSOR_SCHEMA(data)

if sensor_type == "version":
if not data.get(CONF_LATEST):
return TEXT_SENSOR_SCHEMA(data)
return UPDATE_SCHEMA(data)

if sensor_type == "none":
return data

Expand All @@ -162,17 +172,17 @@ def validate_sensor(data: dict) -> dict:
SENSOR_COMMAND_SCHEMA = COMMAND_SCHEMA.extend(
{
vol.Optional(CONF_SCAN_INTERVAL): int,
vol.Optional(CONF_SEPARATOR): str,
vol.Required(CONF_SENSORS): vol.Schema([validate_sensor]),
}
)

SENSOR_SCHEMA = vol.Schema(
{
vol.Required(CONF_TYPE): vol.Any("text", "number", "binary", "none"),
vol.Required(CONF_TYPE): vol.Any("text", "number", "binary", "version", "none"),
vol.Optional(CONF_NAME): str,
vol.Optional(CONF_KEY): str,
vol.Optional(CONF_DYNAMIC): bool,
vol.Optional(CONF_SEPARATOR): str,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): str,
vol.Optional(CONF_VALUE_TEMPLATE): str,
vol.Optional(CONF_COMMAND_SET): str,
Expand Down Expand Up @@ -230,6 +240,13 @@ def validate_sensor(data: dict) -> dict:
}
)

UPDATE_SCHEMA = TEXT_SENSOR_SCHEMA.extend(
{
vol.Required(CONF_LATEST): str,
vol.Optional(CONF_DEVICE_CLASS): UPDATE_DEVICE_CLASSES_SCHEMA,
}
)

DEFAULT_COMMANDS_SELECTOR = SelectSelector(
SelectSelectorConfig(
mode=SelectSelectorMode.DROPDOWN,
Expand Down Expand Up @@ -262,7 +279,7 @@ class OptionsFlow(config_entries.OptionsFlow):
logger = _LOGGER

def __init__(self, config_entry: ConfigEntry) -> None:
self.config_entry = config_entry
# self.config_entry = config_entry
self._data = config_entry.options.copy()

@property
Expand Down Expand Up @@ -350,8 +367,10 @@ async def async_step_init(
options = self.validate_init(user_input)
except NameKeyError:
errors["base"] = "name_key_error"
except InvalidRequiredSensorError:
errors["base"] = "invalid_required_sensor_error"
except CommandLoopError:
errors["base"] = "command_loop_error"
except InvalidSensorError:
errors["base"] = "invalid_sensor_error"
except Exception:
self.logger.exception("Unexpected exception")
errors["base"] = "unknown"
Expand Down Expand Up @@ -431,8 +450,8 @@ async def async_step_reset_commands(
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for SSH."""

VERSION = 1
MINOR_VERSION = 3
VERSION = 2
MINOR_VERSION = 1
logger = _LOGGER
domain = DOMAIN
_existing_entry: ConfigEntry | None = None
Expand Down
1 change: 1 addition & 0 deletions custom_components/ssh/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
CONF_INVOKE_SHELL = "invoke_shell"
CONF_KEY = "key"
CONF_KEY_FILENAME = "key_filename"
CONF_LATEST = "latest"
CONF_LOAD_SYSTEM_HOST_KEYS = "load_system_host_keys"
CONF_OPTIONS = "options"
CONF_PATTERN = "pattern"
Expand Down
25 changes: 23 additions & 2 deletions custom_components/ssh/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
SensorCommand,
SensorKey,
TextSensor,
VersionSensor,
)

from homeassistant.components.button import ButtonDeviceClass
Expand Down Expand Up @@ -43,6 +44,7 @@
CONF_ENTITY_REGISTRY_ENABLED_DEFAULT,
CONF_FLOAT,
CONF_KEY,
CONF_LATEST,
CONF_OPTIONS,
CONF_PATTERN,
CONF_SENSOR_COMMANDS,
Expand Down Expand Up @@ -135,7 +137,6 @@ def _get_sensor_config(self, sensor: Sensor) -> dict:
CONF_NAME: sensor.name,
CONF_KEY: sensor.key,
CONF_DYNAMIC: sensor.dynamic is True or None,
CONF_SEPARATOR: sensor.separator,
CONF_UNIT_OF_MEASUREMENT: sensor.unit,
CONF_COMMAND_SET: sensor.command_set.string
if sensor.command_set
Expand All @@ -148,7 +149,6 @@ def _get_sensor_kwargs(self, data: dict) -> dict:
"name": data.get(CONF_NAME),
"key": data.get(CONF_KEY),
"dynamic": data.get(CONF_DYNAMIC, False),
"separator": data.get(CONF_SEPARATOR),
"unit": data.get(CONF_UNIT_OF_MEASUREMENT),
"renderer": get_value_renderer(self._hass, value_template)
if (value_template := data.get(CONF_VALUE_TEMPLATE))
Expand Down Expand Up @@ -231,6 +231,21 @@ def _get_binary_sensor_kwargs(self, data: dict) -> dict:
"payload_off": data.get(CONF_PAYLOAD_OFF),
}

def _get_version_sensor_config(self, sensor: VersionSensor) -> dict:
return remove_none_items(
{
**self._get_text_sensor_config(sensor),
CONF_TYPE: "version",
CONF_LATEST: sensor.latest,
}
)

def _get_version_sensor_kwargs(self, data: dict) -> dict:
return {
**self._get_text_sensor_kwargs(data),
"latest": data.get(CONF_LATEST),
}

def _get_command_config(self, command: Command) -> dict:
return remove_none_items(
{
Expand Down Expand Up @@ -274,13 +289,16 @@ def get_sensor_command_config(self, command: SensorCommand) -> dict:
{
**self._get_command_config(command),
CONF_SCAN_INTERVAL: command.interval,
CONF_SEPARATOR: command.separator,
CONF_SENSORS: [
self._get_text_sensor_config(sensor)
if isinstance(sensor, TextSensor)
else self._get_number_sensor_config(sensor)
if isinstance(sensor, NumberSensor)
else self._get_binary_sensor_config(sensor)
if isinstance(sensor, BinarySensor)
else self._get_version_sensor_config(sensor)
if isinstance(sensor, VersionSensor)
else {CONF_TYPE: "none"}
for sensor in command.sensors
],
Expand All @@ -292,13 +310,16 @@ def get_sensor_command_kwargs(self, data: dict) -> dict:
return {
**self._get_command_kwargs(data),
"interval": data.get(CONF_SCAN_INTERVAL),
"separator": data.get(CONF_SEPARATOR),
"sensors": [
TextSensor(**self._get_text_sensor_kwargs(sensor_data))
if sensor_data[CONF_TYPE] == "text"
else NumberSensor(**self._get_number_sensor_kwargs(sensor_data))
if sensor_data[CONF_TYPE] == "number"
else BinarySensor(**self._get_binary_sensor_kwargs(sensor_data))
if sensor_data[CONF_TYPE] == "binary"
else VersionSensor(**self._get_version_sensor_kwargs(sensor_data))
if sensor_data[CONF_TYPE] == "version"
else Sensor(key=PLACEHOLDER_KEY)
for sensor_data in data[CONF_SENSORS]
],
Expand Down
4 changes: 2 additions & 2 deletions custom_components/ssh/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/zhbjsh/homeassistant-ssh/issues",
"requirements": [
"ssh-terminal-manager==1.1.4"
"ssh-terminal-manager==1.1.5"
],
"ssdp": [],
"version": "1.0.1",
"version": "1.2.0",
"zeroconf": []
}
21 changes: 18 additions & 3 deletions custom_components/ssh/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from datetime import date, datetime
from decimal import Decimal

from ssh_terminal_manager import BinarySensor, NumberSensor, TextSensor
from ssh_terminal_manager import BinarySensor, NumberSensor, TextSensor, VersionSensor

from homeassistant.components.sensor import (
ENTITY_ID_FORMAT,
Expand Down Expand Up @@ -46,8 +46,23 @@ async def async_get_entities(
ignored_keys = entry_data.ignored_sensor_keys
entities = []

for sensor in entry_data.manager.sensors_by_key.values():
if isinstance(sensor, BinarySensor) or sensor.controllable:
latest_keys = {
sensor.latest
for sensor in entry_data.manager.sensors_by_key.values()
if isinstance(sensor, VersionSensor)
}

for sensor in (sensors_by_key := entry_data.manager.sensors_by_key).values():
if (
isinstance(sensor, BinarySensor)
or (
isinstance(sensor, VersionSensor)
and sensor.latest
and sensors_by_key.get(sensor.latest)
)
or sensor.controllable
or sensor.key in latest_keys
):
continue
if ignored_keys and sensor.key in ignored_keys:
continue
Expand Down
3 changes: 2 additions & 1 deletion custom_components/ssh/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
}
},
"error": {
"invalid_required_sensor_error": "Invalid required sensor",
"command_loop_error": "Command loop detected",
"invalid_sensor_error": "Invalid sensor",
"name_key_error": "Name and key not defined",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
Expand Down
9 changes: 7 additions & 2 deletions custom_components/ssh/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from ssh_terminal_manager import TextSensor
from ssh_terminal_manager import TextSensor, VersionSensor

from homeassistant.components.text import ENTITY_ID_FORMAT, TextEntity, TextMode
from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -38,9 +38,14 @@ async def async_get_entities(
ignored_keys = entry_data.ignored_sensor_keys
entities = []

for sensor in entry_data.manager.sensors_by_key.values():
for sensor in (sensors_by_key := entry_data.manager.sensors_by_key).values():
if not (
isinstance(sensor, TextSensor)
and not (
isinstance(sensor, VersionSensor)
and sensor.latest
and sensors_by_key.get(sensor.latest)
)
and sensor.controllable
and not sensor.options
):
Expand Down
3 changes: 2 additions & 1 deletion custom_components/ssh/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
},
"options": {
"error": {
"invalid_required_sensor_error": "Invalid required sensor",
"command_loop_error": "Command loop detected",
"invalid_sensor_error": "Invalid sensor",
"name_key_error": "Name and key not defined",
"unknown": "Unexpected error"
},
Expand Down
3 changes: 2 additions & 1 deletion custom_components/ssh/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
},
"options": {
"error": {
"invalid_required_sensor_error": "必要なセンサーが無効です",
"command_loop_error": "Command loop detected",
"invalid_sensor_error": "Invalid sensor",
"name_key_error": "名前とキーが定義されていません",
"unknown": "予期せぬエラー"
},
Expand Down
Loading

0 comments on commit bd204ed

Please sign in to comment.