Skip to content

Commit

Permalink
Merge pull request #108 from mdeweerd/dev
Browse files Browse the repository at this point in the history
Add ha_set_state
  • Loading branch information
mdeweerd authored Nov 21, 2022
2 parents 06c31a6 + e2f5ac9 commit d28eb00
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 1 deletion.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ ZHA Toolkit can also:
- [`zha_devices`: Device List Information to Event or CSV](#zha_devices-device-list-information-to-event-or-csv)
- [`register_services`: Reregister ZHA-Toolkit services](#register_services-reregister-zha-toolkit-services)
- [User method](#user-method)
- [`ha_set_state` - Update HA state](#ha_set_state---update-ha-state)
- [Manufacturers](#manufacturers)
- [Tuya](#tuya)
- [`tuya_magic` - Tuya Magic spell](#tuya_magic---tuya-magic-spell)
Expand Down Expand Up @@ -1488,6 +1489,34 @@ This is a powerful tool to develop your own custom tool, and propose it for
inclusion in the `zha-toolkit` when it's ready and of potential use to
others.

### `ha_set_state` - Update HA state

Set/update any Home Assistant state.

```yaml
service: zha_toolkit.ha_set_state
data:
fail_exception: true
state_id: sensor.mysensor
attr_val: 10
# optional Parameters
state_attr: some_attribute
allow_create: true
state_value_template: value + 2
csvout: set_state.csv
csvlabel: Example reason
```

`state_value_template` is a template(-like) expression that is interpreted
where `attr_val` is available as `value` and `attr_value`. This a template
expression without the curly parentheses (`{{ }}`). Unfortunately
`{{ value }}` is expanded before the zha service code is entered disabling
the dynamic evaluation of value which is why the curly parenthesis are
omitted.

This is not strictly a `zha` specific tool, but useful in some scripting
situations.

## Manufacturers

### Tuya
Expand Down
1 change: 1 addition & 0 deletions STATS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Badges showing number of downloads per version

- ![badge latest](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/latest/total.svg)
- ![badge v0.8.24](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/v0.8.24/total.svg)
- ![badge v0.8.23](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/v0.8.23/total.svg)
- ![badge v0.8.22](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/v0.8.22/total.svg)
- ![badge v0.8.21](https://img.shields.io/github/downloads/mdeweerd/zha-toolkit/v0.8.21/total.svg)
Expand Down
28 changes: 28 additions & 0 deletions blueprints/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
- `backup.yaml`:\
Script for daily backup of supported zigbee coordinators.
- `backup_znp.yaml`:\
Script for daily backup of ZNP coordinator.
- `blueprint_danfoss_ally_configure_script.yaml`:\
Sample blueprint script
to configure Danfoss Ally (see other script example for a more complete
configuration)
- `danfoss_ally_remote_temperature.yaml`:\
Send temperature to Danfoss Ally
TRV at most every X minutes and at least every Y minutes. Uses restart to
interrupt long wait ("y minutes")
- `danfoss_ally_remote_temperature_min_delay.yaml`:\
Send temperature to
Danfoss Ally at most every X minutes. Uses single to block too fast
updates. In case the temperature is stable over a very long time, you
should ensure that HA considers it is updated on every change.
- `danfoss_ally_remote_temperature_min_delay_fake_change.yaml`:\
Same as
`..._min_delay.yaml`. Work in progress - needs update of
`home-assistant-variables`. Uses
[snarky-snark/home-assistant-variables](https://github.com/snarky-snark/home-assistant-variables)
to fake temperature update even when stable by applying slight change in
temperature at the end of the minimum delay. So if the temperature is
stable, it will still be seen as a change.
- `script_Thermometer_setReporting.yaml`:\
Blueprint Script to configure
reporting of a zigbee device with Temperature Measurement Cluster 0x0402.
1 change: 1 addition & 0 deletions blueprints/danfoss_ally_remote_temperature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ action:
cluster: 0x0201
attribute: 0x4015
attr_val: '{{ (((states(temp_sensor_id)|float) + temp_offset) * 100) | round(0) }}'
read_before_write: false
- alias: Wait until the maximum update delay expires (automation restarts
when temperature changes before)
delay:
Expand Down
78 changes: 78 additions & 0 deletions blueprints/danfoss_ally_remote_temperature_min_delay.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
blueprint:
domain: automation
name: Ally Temp Update Min Delay
description: Update Danfoss Ally TRV external temperature with min refresh rate
source_url: https://github.com/mdeweerd/zha-toolkit/blob/master/blueprints/danfoss_ally_remote_temperature_min_delay.yaml
input:
ally_device:
name: Ally TRV Device
description: Temperature reading will be sent to this device
selector:
device:
manufacturer: Danfoss
entity:
domain: climate
temp_sensor_id:
name: Temperature Sensor
description: External sensor from which the temperature will be read. Expects
data format 12.3 (corresponding to °C)
selector:
entity:
device_class: temperature
min_update_minutes:
name: Minimum update interval
description: >
Updates will not be sent if time from last update is less than minimum interval.
Normally 30 min for uncovered, 5 min for covered.
default: 5
selector:
number:
max: 299
min: 1
unit_of_measurement: minutes
mode: box
temperature_offset:
name: Temperature offset to apply to temperature measured by sensor
description: >
When the offset is -1.5 and the value measured by the sensor is 20 °C, then
the temperature provide to the TRV will be 18.5 °C.
default: 0
selector:
number:
max: 4.0
min: -4.0
step: 0.1
unit_of_measurement: °C
mode: box
variables:
device: !input ally_device
ieee: "{{(device_attr(device, 'identifiers')|list)[0][1]}}"
min_update_minutes: !input min_update_minutes
temp_sensor_id: !input temp_sensor_id
temp_offset: !input temperature_offset
trigger:
- platform: state
entity_id:
- !input temp_sensor_id
action:
- alias: Write remote temperature to Danfoss Ally
service: zha_toolkit.attr_write
data:
ieee: '{{ ieee }}'
cluster: 0x0201
attribute: 0x4015
attr_val: '{{ (((states(temp_sensor_id)|float) + temp_offset) * 100) | round(0)
}}'
read_before_write: false
- alias: Wait until the minimum update delay expires (automation restarts when temperature
changes before)
delay:
minutes: !input min_update_minutes
- alias: Fake small change in temperature so that the next sensor update triggers an update/change event
service: zha_toolkit.ha_set_state
data:
state_id: '{{ temp_sensor_id }}'
attr_val: '{{ (states(temp_sensor_id)|round(2)) + 0.001 }}'

mode: single
28 changes: 27 additions & 1 deletion custom_components/zha_toolkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
vol.Optional(P.ATTR_TYPE): vol.Any(
int, cv.string
), # String is for later
vol.Optional(P.ATTR_VAL): vol.Any(cv.string, int, list),
vol.Optional(P.ATTR_VAL): vol.Any(cv.string, float, int, list),
vol.Optional(P.CODE): vol.Any(
list, cv.string
), # list is for later
Expand All @@ -71,6 +71,10 @@
), # Arguments to command
vol.Optional(P.STATE_ID): cv.string,
vol.Optional(P.STATE_ATTR): cv.string,
# Template can't be used for check:
# vol.Optional(P.STATE_VALUE_TEMPLATE): cv.template,
vol.Optional(P.STATE_VALUE_TEMPLATE): cv.string,
vol.Optional(P.FORCE_UPDATE): cv.boolean,
vol.Optional(P.ALLOW_CREATE): cv.boolean,
vol.Optional(P.READ_BEFORE_WRITE): cv.boolean,
vol.Optional(P.READ_AFTER_WRITE): cv.boolean,
Expand Down Expand Up @@ -119,6 +123,8 @@
vol.Optional(P.EXPECT_REPLY): cv.boolean,
vol.Optional(P.STATE_ID): cv.string,
vol.Optional(P.STATE_ATTR): cv.string,
vol.Optional(P.STATE_VALUE_TEMPLATE): cv.string,
vol.Optional(P.FORCE_UPDATE): cv.boolean,
vol.Optional(P.ALLOW_CREATE): cv.boolean,
vol.Optional(P.OUTCSV): cv.string,
vol.Optional(P.CSVLABEL): cv.string,
Expand Down Expand Up @@ -147,6 +153,8 @@
vol.Optional(P.EXPECT_REPLY): cv.boolean,
vol.Optional(P.STATE_ID): cv.string,
vol.Optional(P.STATE_ATTR): cv.string,
vol.Optional(P.STATE_VALUE_TEMPLATE): cv.template,
vol.Optional(P.FORCE_UPDATE): cv.boolean,
vol.Optional(P.ALLOW_CREATE): cv.boolean,
vol.Optional(P.READ_BEFORE_WRITE): cv.boolean,
vol.Optional(P.READ_AFTER_WRITE): cv.boolean,
Expand Down Expand Up @@ -305,6 +313,24 @@
},
extra=vol.ALLOW_EXTRA,
),
S.HA_SET_STATE: vol.Schema(
{
vol.Required(P.ATTR_VAL): vol.Any(
list,
float,
int,
cv.string,
),
vol.Required(P.STATE_ID): cv.string,
vol.Optional(P.STATE_ATTR): cv.string,
vol.Optional(P.STATE_VALUE_TEMPLATE): cv.string,
vol.Optional(P.FORCE_UPDATE): cv.boolean,
vol.Optional(P.ALLOW_CREATE): cv.boolean,
vol.Optional(P.OUTCSV): cv.string,
vol.Optional(P.CSVLABEL): cv.string,
},
extra=vol.ALLOW_EXTRA,
),
S.ZHA_DEVICES: vol.Schema(
{},
extra=vol.ALLOW_EXTRA,
Expand Down
74 changes: 74 additions & 0 deletions custom_components/zha_toolkit/ha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from __future__ import annotations

import logging

from homeassistant.helpers.template import Template
from homeassistant.util import dt as dt_util

from . import utils as u
from .params import INTERNAL_PARAMS as p

LOGGER = logging.getLogger(__name__)


async def ha_set_state( # noqa: C901
app, listener, ieee, cmd, data, service, params, event_data
):
success = True

val = params[p.ATTR_VAL]
state_field = None

state_template_str = params[p.STATE_VALUE_TEMPLATE]
if state_template_str is not None:
template = Template("{{ " + state_template_str + " }}", listener._hass)
new_value = template.async_render(value=val, attr_val=val)
val = new_value

# Write value to provided state or state attribute
if params[p.STATE_ID] is None:
raise ValueError("'state_id' is required")

if params[p.STATE_ATTR] is not None:
state_field = f"{params[p.STATE_ID]}[{params[p.STATE_ATTR]}]"
else:
state_field = f"{params[p.STATE_ID]}"

LOGGER.debug(
"Set state '%s' -> %s",
state_field,
val,
)
u.set_state(
listener._hass,
params[p.STATE_ID],
val,
key=params[p.STATE_ATTR],
allow_create=params[p.ALLOW_CREATE],
)

event_data["success"] = success

if success and (params[p.CSV_FILE] is not None):
fields = []
label = params[p.CSV_LABEL]

fields.append(dt_util.utcnow().isoformat())
fields.append(state_field)
fields.append(val)
fields.append(label)

u.append_to_csvfile(
fields,
"csv",
params[p.CSV_FILE],
f"{state_field}={val}",
listener=listener,
)
LOGGER.debug(f"ha_set_state info Written to CSV {params[p.CSV_FILE]}")

if u.isJsonable(val):
val = repr(val)

# For internal use
return success
6 changes: 6 additions & 0 deletions custom_components/zha_toolkit/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ class USER_PARAMS_consts: # pylint: disable=too-few-public-methods
EVENT_SUCCESS = "event_success"
EVENT_FAIL = "event_fail"
EVENT_DONE = "event_done"
FORCE_UPDATE = "force_update"
FAIL_EXCEPTION = "fail_exception"
READ_BEFORE_WRITE = "read_before_write"
READ_AFTER_WRITE = "read_after_write"
STATE_VALUE_TEMPLATE = "state_value_template"
WRITE_IF_EQUAL = "write_if_equal"
OUTCSV = "csvout"
CSVLABEL = "csvlabel"
Expand Down Expand Up @@ -68,6 +70,7 @@ class SERVICE_consts: # pylint: disable=too-few-public-methods
GET_ZLL_GROUPS = "get_zll_groups"
ZHA_DEVICES = "zha_devices"
HANDLE_JOIN = "handle_join"
HA_SET_STATE = "ha_set_state"
IEEE_PING = "ieee_ping"
LEAVE = "leave"
MISC_REINITIALIZE = "misc_reinitialize"
Expand All @@ -78,6 +81,7 @@ class SERVICE_consts: # pylint: disable=too-few-public-methods
REMOVE_FROM_GROUP = "remove_from_group"
REMOVE_GROUP = "remove_group"
SCAN_DEVICE = "scan_device"
STATE_VALUE_TEMPLATE = "state_value_template"
UNBIND_COORDINATOR = "unbind_coordinator"
UNBIND_GROUP = "unbind_group"
ZCL_CMD = "zcl_cmd"
Expand Down Expand Up @@ -110,6 +114,7 @@ class INTERNAL_PARAMS_consts: # pylint: disable=too-few-public-methods
EVT_SUCCESS = "event_success"
EXPECT_REPLY = "expect_reply"
FAIL_EXCEPTION = "fail_exception"
FORCE_UPDATE = "force_update"
MANF = "manf"
MAX_INTERVAL = "max_interval"
MIN_INTERVAL = "min_interval"
Expand All @@ -118,6 +123,7 @@ class INTERNAL_PARAMS_consts: # pylint: disable=too-few-public-methods
REPORTABLE_CHANGE = "reportable_change"
STATE_ATTR = "state_attr"
STATE_ID = "state_id"
STATE_VALUE_TEMPLATE = "state_value_template"
TRIES = "tries"
WRITE_IF_EQUAL = "write_if_equal"
CSV_FILE = "csvfile"
Expand Down
Loading

0 comments on commit d28eb00

Please sign in to comment.