Skip to content

Commit

Permalink
Merge pull request #18 from iprak/custom-domain
Browse files Browse the repository at this point in the history
Support for custom domain
  • Loading branch information
enkama authored Jul 25, 2022
2 parents 1a04426 + 4517a37 commit 9936c31
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 34 deletions.
25 changes: 25 additions & 0 deletions .devcontainer/configuration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#Limited configuration instead of default_config
#https://github.com/home-assistant/core/tree/dev/homeassistant/components/default_config
automation:
frontend:
history:
logbook:

homeassistant:
name: Home

variable:
test_sensor:
value: 0
restore: true
domain: sensor

test_counter:
value: 0
attributes:
icon: mdi:alarm

logger:
default: info
logs:
custom_components.variable: debug
34 changes: 34 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "Home Assistant Custom Component Dev",
"context": "..",
"image": "ghcr.io/ludeeus/devcontainer/integration:latest",
"appPort": "9123:8123",
"postCreateCommand": "container install && pip install --upgrade pip && pip install --ignore-installed -r requirements.txt",
"extensions": [
"ms-python.python",
"github.vscode-pull-request-github",
"ms-python.vscode-pylance",
"spmeesseman.vscode-taskexplorer"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/local/python/bin/python",
"python.analysis.autoSearchPaths": false,
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.linting.pylintArgs": [
"--disable",
"import-error"
],
"python.formatting.provider": "black",
"python.testing.pytestArgs": [
"--no-cov"
],
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}
}
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
* text eol=lf
*.py whitespace=error

*.ico binary
*.jpg binary
*.png binary
*.zip binary
*.mp3 binary
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.formatting.provider": "black",
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"files.trimTrailingWhitespace": true,
"git.ignoreLimitWarning": true,
}
29 changes: 29 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Run Home Assistant on port 9123",
"type": "shell",
"command": "container start",
"problemMatcher": []
},
{
"label": "Run Home Assistant configuration against /config",
"type": "shell",
"command": "container check",
"problemMatcher": []
},
{
"label": "Upgrade Home Assistant to latest dev",
"type": "shell",
"command": "container install",
"problemMatcher": []
},
{
"label": "Install a specific version of Home Assistant",
"type": "shell",
"command": "container set-version",
"problemMatcher": []
}
]
}
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,32 @@ variable:
restore: true
current_power_usage:
force_update: true

daily_download:
value: 0
restore: true
domain: sensor
attributes:
state_class: measurement
unit_of_measurement: GB
icon: mdi:download
```
A variable 'should' have a __value__ and can optionally have a __name__ and __attributes__, which can be used to specify additional values but can also be used to set internal attributes like icon, friendly_name etc.
A variable can accept an optional `domain` which results in the entity name to start with that domain instead of `variable`.

In case you want your variable to restore its value and attributes after restarting you can set __restore__ to true.

In case you want your variable to update (and add a history entry) even if the value has not changed, you can set __force_update__ to true.

## Set variables from automations

To update a variables value and/or its attributes you can use the service call `variable.set_variable`
The variable component exposes 2 services:
* `variable.set_variable` can be used to update a variables value and/or its attributes.
* `variable.set_entity` can be used to update an entity value and/or its attributes.

The following parameters can be used with this service:
The following parameters can be used with `variable.set_variable`:

- __variable: string (required)__
The name of the variable to update
Expand All @@ -65,7 +78,19 @@ Attributes to set or update
- __replace_attributes: boolean ( optional )__
Replace or merge current attributes (default false = merge)

### Example service calls

The following parameters can be used with `variable.set_entity`:

- __entity: string (required)__
The id of the entity to update
- __value: any (optional)__
New value to set
- __attributes: dictionary (optional)__
Attributes to set or update
- __replace_attributes: boolean ( optional )__
Replace or merge current attributes (default false = merge)

#### Example service calls

```yaml
action:
Expand All @@ -83,6 +108,12 @@ action:
history_1: "{{states('variable.last_motion')}}"
history_2: "{{state_attr('variable.last_motion','history_1')}}"
history_3: "{{state_attr('variable.last_motion','history_2')}}"
action:
- service: variable.set_entity
data:
variable: sensor.test_counter
value: 30
```

### Example timer automation
Expand Down
1 change: 1 addition & 0 deletions custom_components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Dummy __init__.py to make imports with homeassistant-custom-component work."""
89 changes: 59 additions & 30 deletions custom_components/variable/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""variable implementation for Home Assistant."""
import logging

import voluptuous as vol

from homeassistant.const import CONF_NAME, ATTR_ICON
from homeassistant.const import ATTR_ICON, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType
import voluptuous as vol

_LOGGER = logging.getLogger(__name__)

Expand All @@ -18,13 +18,26 @@
CONF_VALUE = "value"
CONF_RESTORE = "restore"
CONF_FORCE_UPDATE = "force_update"
CONF_DOMAIN = "domain"

ATTR_ENTITY = "entity"
ATTR_VARIABLE = "variable"
ATTR_VALUE = "value"
ATTR_ATTRIBUTES = "attributes"
ATTR_REPLACE_ATTRIBUTES = "replace_attributes"
ATTR_DOMAIN = "domain"

SERVICE_SET_ENTITY = "set_entity"
SERVICE_SET_VARIABLE = "set_variable"

SERVICE_SET_ENTITY_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY): cv.string,
vol.Optional(ATTR_VALUE): cv.match_all,
vol.Optional(ATTR_ATTRIBUTES): dict,
vol.Optional(ATTR_REPLACE_ATTRIBUTES): cv.boolean,
}
)
SERVICE_SET_VARIABLE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_VARIABLE): cv.string,
Expand All @@ -45,6 +58,7 @@
vol.Optional(CONF_ATTRIBUTES): dict,
vol.Optional(CONF_RESTORE): cv.boolean,
vol.Optional(CONF_FORCE_UPDATE): cv.boolean,
vol.Optional(ATTR_DOMAIN): cv.string,
},
None,
)
Expand All @@ -55,28 +69,12 @@
)


@bind_hass
def set_variable(
hass,
variable,
value,
attributes,
replace_attributes,
):
"""Set input_boolean to True."""
hass.services.call(
DOMAIN,
SERVICE_SET_VARIABLE,
{
ATTR_VARIABLE: variable,
ATTR_VALUE: value,
ATTR_ATTRIBUTES: attributes,
ATTR_REPLACE_ATTRIBUTES: replace_attributes,
},
)
def get_entity_id_format(domain: str) -> str:
"""Get the entity id format."""
return domain + ".{}"


async def async_setup(hass, config):
async def async_setup(hass: HomeAssistant, config: ConfigType):
"""Set up variables."""
component = EntityComponent(_LOGGER, DOMAIN, hass)

Expand All @@ -91,9 +89,12 @@ async def async_setup(hass, config):
attributes = variable_config.get(CONF_ATTRIBUTES)
restore = variable_config.get(CONF_RESTORE, False)
force_update = variable_config.get(CONF_FORCE_UPDATE, False)
domain = variable_config.get(CONF_DOMAIN, DOMAIN)

entities.append(
Variable(variable_id, name, value, attributes, restore, force_update)
Variable(
variable_id, name, value, attributes, restore, force_update, domain
)
)

async def async_set_variable_service(call):
Expand All @@ -108,14 +109,40 @@ async def async_set_variable_service(call):
call.data.get(ATTR_REPLACE_ATTRIBUTES, False),
)
else:
_LOGGER.warning(f"Failed to set unknown variable: {entity_id}")
_LOGGER.warning("Failed to set unknown variable: %s", entity_id)

async def async_set_entity_service(call):
"""Handle calls to the set_entity service."""

entity_id: str = call.data.get(ATTR_ENTITY)
state_value = call.data.get(ATTR_VALUE)
attributes = call.data.get(ATTR_ATTRIBUTES, {})
replace_attributes = call.data.get(ATTR_REPLACE_ATTRIBUTES, False)

if replace_attributes:
updated_attributes = attributes
else:
cur_state = hass.states.get(entity_id)
if cur_state is None or cur_state.attributes is None:
updated_attributes = attributes
else:
updated_attributes = dict(cur_state.attributes)
updated_attributes.update(attributes)

hass.states.async_set(entity_id, state_value, updated_attributes)

hass.services.async_register(
DOMAIN,
SERVICE_SET_VARIABLE,
async_set_variable_service,
schema=SERVICE_SET_VARIABLE_SCHEMA,
)
hass.services.async_register(
DOMAIN,
SERVICE_SET_ENTITY,
async_set_entity_service,
schema=SERVICE_SET_ENTITY_SCHEMA,
)

await component.async_add_entities(entities)
return True
Expand All @@ -124,9 +151,12 @@ async def async_set_variable_service(call):
class Variable(RestoreEntity):
"""Representation of a variable."""

def __init__(self, variable_id, name, value, attributes, restore, force_update):
def __init__(
self, variable_id, name, value, attributes, restore, force_update, domain
):
"""Initialize a variable."""
self.entity_id = ENTITY_ID_FORMAT.format(variable_id)

self.entity_id = get_entity_id_format(domain).format(variable_id)
self._name = name
self._value = value
self._attributes = attributes
Expand Down Expand Up @@ -174,7 +204,7 @@ def state_attributes(self):

@property
def force_update(self) -> bool:
"""Force update"""
"""Force an update."""
return self._force_update

async def async_set_variable(
Expand All @@ -184,7 +214,6 @@ async def async_set_variable(
replace_attributes,
):
"""Update variable."""
current_state = self.hass.states.get(self.entity_id)
updated_attributes = None
updated_value = None

Expand Down
18 changes: 17 additions & 1 deletion custom_components/variable/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,25 @@ set_variable:
# Key of the field
variable:
description: string (required) The name of the variable to update
example: test_counter
value:
description: any (optional) New value to set
example: 9
attributes:
description: dictionary (optional) Attributes to set or update
replace_attributes:
description: boolean ( optional ) Replace or merge current attributes (default false = merge)
description: boolean (optional) Replace or merge current attributes (default false = merge)

set_entity:
description: Update an entity value and/or its attributes.
fields:
entity:
description: string (required) The id of the entity to update
example: sensor.test_sensor
value:
description: any (optional) New value to set
example: 9
attributes:
description: dictionary (optional) Attributes to set or update
replace_attributes:
description: boolean (optional) Replace or merge current attributes (default false = merge)
Loading

0 comments on commit 9936c31

Please sign in to comment.