mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-11-21 16:18:14 +00:00
Compare commits
31 Commits
support-mo
...
f9253e8cf0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9253e8cf0 | ||
|
|
3528080ec3 | ||
|
|
cc321899f4 | ||
|
|
5a44765ac9 | ||
|
|
9eb791c68b | ||
|
|
2fca0ef3a3 | ||
|
|
fc0320aef6 | ||
|
|
0d3c76ef2e | ||
|
|
4c946d584a | ||
|
|
6e3cf73ab3 | ||
|
|
14186b7992 | ||
|
|
f64bed5058 | ||
|
|
619671de5d | ||
|
|
6d18406880 | ||
|
|
3a7676f4bf | ||
|
|
f19eb7c276 | ||
|
|
c617d2cad6 | ||
|
|
d1f6f6d9d2 | ||
|
|
35333f4d75 | ||
|
|
a5ddb65512 | ||
|
|
b0fa10b2c1 | ||
|
|
6a0ec34cdb | ||
|
|
2cd171637c | ||
|
|
264b160fdf | ||
|
|
81fa876449 | ||
|
|
b563ab7923 | ||
|
|
2ebf36a445 | ||
|
|
5bdab41d8b | ||
|
|
85080f5d46 | ||
|
|
35e0fe26d0 | ||
|
|
427c1834a8 |
@@ -1,4 +1,4 @@
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Numeric](examples/Numeric.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
|
||||
# Background Service
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Numeric](examples/Numeric.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
|
||||
# Version History
|
||||
|
||||
@@ -49,5 +49,6 @@
|
||||
| 3.1 | Added the ability for users to provide [custom HTTP headers](HTTP_Headers.md) for their HomeAssistant server. Improved German language translations. Thanks to [@tispokes](https://github.com/tispokes) for assisting with both of those. Removed all groups in settings as the SDK is buggy. Fixed a bug with templates in glances causing application crash on startup. |
|
||||
| 3.2 | Only enable or disable sensors on HomeAssistant when the background service options is changed, i.e. do not call the API to enable on start up every time. |
|
||||
| 3.3 | Providing automatic detection for menu definition updates, but still requires an application restart. |
|
||||
| 3.4 | Fixed a bug where templates failed to display in toggle menu items (at least on some devices). Fixed a bug where a menu item requesting to exit on completion appeared to indicate failure when using Wi-Fi or LTE. The fix uses a delay in exiting the application modelled as sufficient for a Venu 2 device, so this might need tweaking for other devices. Attempt to fixed an "Out of Memory" bug caused by v3.3 by making automatic checking for menu updates both optional and automatically turned off when insufficient memory is available. This last bug is device dependent and may require another attempt. Internationalisation improvements with thanks to @krzys_h for a new automated translations script. |
|
||||
| 3.4 | Fixed a bug where templates failed to display in toggle menu items (at least on some devices). Fixed a bug where a menu item requesting to exit on completion appeared to indicate failure when using Wi-Fi or LTE. The fix uses a delay in exiting the application modelled as sufficient for a Venu 2 device, so this might need tweaking for other devices. Attempt to fixed an "Out of Memory" bug caused by v3.3 by making automatic checking for menu updates both optional and automatically turned off when insufficient memory is available. This last bug is device dependent and may require another attempt. Internationalisation improvements with thanks to [@krzys_h](https://github.com/krzys-h) for a new automated translations script. |
|
||||
| 3.5 | Added support for Edge 550, 850 & MTB, Fenix 8 Pro 47mm, GPSMAP H1, Instinct Crossover AMOLED, Venu 4 41mm & 45mm, & Venu X1 devices which also required an SDK update to 8.3.0. The simulation of the Edge 850 device was off, as it failed to update the display and text was the wrong colour, but the buttons menu items operated HA correctly. The assumption is the simulation model is buggy until someone [reports](https://github.com/house-of-abbey/GarminHomeAssistant/issues) otherwise. |
|
||||
| 3.6 | Added `numeric` menu item type thanks to [@thmichel](https://github.com/thmichel). This allows you to select a numeric value to set for an entity. Confirmations can now display a user supplied message. |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Numeric](examples/Numeric.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
|
||||
# User Specified Custom HTTP Headers
|
||||
|
||||
|
||||
63
README.md
63
README.md
@@ -1,4 +1,4 @@
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Numeric](examples/Numeric.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
|
||||
# GarminHomeAssistant
|
||||
|
||||
@@ -145,22 +145,21 @@ Example schema:
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Heating",
|
||||
"content": "{{ ' %.1f' | format(state_attr('climate.myheating','temperature')) }}",
|
||||
"type": "numeric",
|
||||
"entity": "climate.myheating",
|
||||
"tap_action": {
|
||||
"service": "climate.set_temperature",
|
||||
"data": {
|
||||
"step": "0.5",
|
||||
"start": "10",
|
||||
"stop": "30",
|
||||
"valueLabel": "temperature",
|
||||
"formatString": "%.1f"
|
||||
}
|
||||
},
|
||||
"pin": false
|
||||
} ,
|
||||
"name": "Heating",
|
||||
"content": "{{ ' %.1f' | format(state_attr('climate.room','temperature')) }}",
|
||||
"type": "numeric",
|
||||
"entity": "climate.room",
|
||||
"tap_action": {
|
||||
"service": "climate.set_temperature",
|
||||
"picker": {
|
||||
"step": 0.5,
|
||||
"start": 10,
|
||||
"stop": 30,
|
||||
"attribute": "temperature",
|
||||
"data_attribute": "temperature"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -174,25 +173,28 @@ The example above illustrates how to configure:
|
||||
* Script invocation (`tap`)
|
||||
* Service invocation, e.g. Scene setting, (`tap`)
|
||||
* A sub-menu to open (`group`)
|
||||
* A numeric item (`numeric`), which allows you to set a numeric value e.g. for heating or a dimmer. ValueLabel defines the variable to return. You can optionally set the minimum (start) and maximum (stop) value as well as the step to increase/decrease and a tepmlate how to format the value.
|
||||
* A numeric item (`numeric`), which allows you to set a numeric value e.g. for heating or a dimmer. This is [explained more fully](examples/Numeric.md) in its own examples page.
|
||||
* You can also display the status of devices (`info`) which is essentially a `tap` with no action
|
||||
* All menu items can display the results of evaluating [templates](examples/Templates.md).
|
||||
|
||||
The following table indicates how HomeAssistant entity types can map to the Garmin applications menu types. Presently, an automation is the only one that can be either a `tap` or a `toggle`.
|
||||
|
||||
| HA Entity Type | Tap | Toggle | Info (status)|
|
||||
|------------------|:---:|:------:|:------------:|
|
||||
| Switch | ❌ | ✅ | ✅ |
|
||||
| Light | ❌ | ✅ | ✅ |
|
||||
| Automation | ✅ | ✅ | ❌ |
|
||||
| Script | ✅ | ❌ | ❌ |
|
||||
| Scene | ✅ | ❌ | ❌ |
|
||||
| Sensor | ❌ | ❌ | ✅ |
|
||||
| Binary Sensor | ❌ | ❌ | ✅ |
|
||||
| Any other entity | ❌ | ❌ | ✅ |
|
||||
| Any service | ✅ | ❌ | ❌ |
|
||||
| HA Entity Type | Tap | Toggle | Info (status)| Numeric |
|
||||
|------------------|:---:|:------:|:------------:|:-------:|
|
||||
| Switch | ❌ | ✅ | ✅ | ❌ |
|
||||
| Switched Light | ❌ | ✅ | ✅ | ❌ |
|
||||
| Dimmer Light | ❌ | ❌ | ✅ | ✅ |
|
||||
| Automation | ✅ | ✅ | ❌ | ❌ |
|
||||
| Script | ✅ | ❌ | ❌ | ❌ |
|
||||
| Scene | ✅ | ❌ | ❌ | ❌ |
|
||||
| Sensor | ❌ | ❌ | ✅ | ❌ |
|
||||
| Binary Sensor | ❌ | ❌ | ✅ | ❌ |
|
||||
| Thermostat | ❌ | ❌ | ✅ | ✅ |
|
||||
| Amplifier | ❌ | ❌ | ✅ | ✅ |
|
||||
| Any other entity | ❌ | ❌ | ✅ | ❌ |
|
||||
| Any service | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
Multiple templates are evaluated in a single HTTP request to update their status. Only the toggle items have the on/off <img src="images/toggle_icon.png" height="20"> icon. NB. All `tap` items must specify a `service` tag in the `tap_action` object (see example below).
|
||||
Multiple templates are evaluated in a single HTTP request to update their status. Only the toggle items have the on/off <img src="images/toggle_icon.png" height="20"> icon. NB. All `tap` and `numeric` items must specify a `service` tag in the `tap_action` object (see example below).
|
||||
|
||||
You can now specify alternative texts to use instead of "On" and "Off", e.g. "Locked" and "Unlocked" or "Open" and "Closed" through the use of a [template menu item](examples/Templates.md). But wouldn't having locks operated from your watch be a security concern ;-) ?
|
||||
|
||||
@@ -231,6 +233,7 @@ This allows the `confirm` and `pin` fields to be accommodated in the `tap_action
|
||||
* [Switches](examples/Switches.md)
|
||||
* [Actions](examples/Actions.md)
|
||||
* [Templates](examples/Templates.md)
|
||||
* [Numeric](examples/Numeric.md)
|
||||
|
||||
## Editing the JSON file
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Numeric](examples/Numeric.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
|
||||
# Troubleshooting Guides
|
||||
|
||||
|
||||
2
Wi-Fi.md
2
Wi-Fi.md
@@ -1,4 +1,4 @@
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Numeric](examples/Numeric.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Wi-Fi](Wi-Fi.md) | [HTTP Headers](HTTP_Headers.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
|
||||
|
||||
# Wi-Fi & LTE
|
||||
|
||||
|
||||
@@ -25,13 +25,6 @@
|
||||
"$defs": {
|
||||
"toggle": {
|
||||
"type": "object",
|
||||
"examples": [
|
||||
{
|
||||
"type": "toggle",
|
||||
"name": "Example",
|
||||
"entity": "switch.example"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"entity": {
|
||||
"$ref": "#/$defs/entity"
|
||||
@@ -177,18 +170,6 @@
|
||||
},
|
||||
"tap": {
|
||||
"type": "object",
|
||||
"examples": [
|
||||
{
|
||||
"type": "tap",
|
||||
"name": "Example",
|
||||
"tap_action": {
|
||||
"service": "notify.notify",
|
||||
"data": {
|
||||
"message": "Example"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"entity": {
|
||||
"$ref": "#/$defs/entity"
|
||||
@@ -240,14 +221,6 @@
|
||||
},
|
||||
"group": {
|
||||
"type": "object",
|
||||
"examples": [
|
||||
{
|
||||
"type": "group",
|
||||
"name": "Example",
|
||||
"title": "Example",
|
||||
"items": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"entity": {
|
||||
"$ref": "#/$defs/entity",
|
||||
@@ -288,105 +261,6 @@
|
||||
},
|
||||
"numeric": {
|
||||
"type": "object",
|
||||
"examples": [
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "light.example",
|
||||
"attribute": "brightness",
|
||||
"service": "light.turn_on",
|
||||
"service_attribute": "brightness",
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"step": 1
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "input_number.example",
|
||||
"service": "input_number.set_value",
|
||||
"service_attribute": "value",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "number.example",
|
||||
"service": "number.set_value",
|
||||
"service_attribute": "value",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "fan.example",
|
||||
"attribute": "percentage",
|
||||
"service": "fan.set_percentage",
|
||||
"service_attribute": "percentage",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "valve.example",
|
||||
"attribute": "position",
|
||||
"service": "valve.set_valve_position",
|
||||
"service_attribute": "position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "cover.example",
|
||||
"attribute": "position",
|
||||
"service": "cover.set_position",
|
||||
"service_attribute": "position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "cover.example",
|
||||
"attribute": "tilt_position",
|
||||
"service": "cover.set_tilt_position",
|
||||
"service_attribute": "tilt_position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "media_player.example",
|
||||
"attribute": "volume_level",
|
||||
"service": "media_player.volume_set",
|
||||
"service_attribute": "volume_level",
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.01
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "climate.example",
|
||||
"attribute": "temperature",
|
||||
"service": "climate.set_temperature",
|
||||
"service_attribute": "temperature",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
}
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
@@ -401,7 +275,51 @@
|
||||
"$ref": "#/$defs/content"
|
||||
},
|
||||
"tap_action": {
|
||||
"$ref": "#/$defs/tap_action"
|
||||
"$ref": "#/$defs/tap_action",
|
||||
"properties": {
|
||||
"service": {
|
||||
"$ref": "#/$defs/service"
|
||||
},
|
||||
"picker": {
|
||||
"type": "object",
|
||||
"title": "Number picker configuration",
|
||||
"description": "'attribute' field is optional.",
|
||||
"properties": {
|
||||
"min": {
|
||||
"type": "number",
|
||||
"title": "Minimum Value"
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"title": "Maximum Value"
|
||||
},
|
||||
"step": {
|
||||
"type": "number",
|
||||
"title": "Step Size"
|
||||
},
|
||||
"attribute": {
|
||||
"type": "string",
|
||||
"title": "Attribute on the entity",
|
||||
"description": "Attribute on the entity with the current numeric value. To use the state of the entity, do not specify."
|
||||
},
|
||||
"data_attribute": {
|
||||
"type": "string",
|
||||
"title": "Attribute on the service data",
|
||||
"description": "Attribute on the service data for the value to set."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"min",
|
||||
"max",
|
||||
"step",
|
||||
"data_attribute"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"service",
|
||||
"picker"
|
||||
]
|
||||
},
|
||||
"enabled": {
|
||||
"$ref": "#/$defs/enabled"
|
||||
@@ -409,49 +327,15 @@
|
||||
"exit": {
|
||||
"$ref": "#/$defs/exit"
|
||||
},
|
||||
"min": {
|
||||
"type": "number",
|
||||
"title": "Minimum value"
|
||||
},
|
||||
"max": {
|
||||
"type": "number",
|
||||
"title": "Maximum value"
|
||||
},
|
||||
"step": {
|
||||
"type": "number",
|
||||
"title": "Step size"
|
||||
},
|
||||
"decimals": {
|
||||
"type": "number",
|
||||
"title": "Number of decimals to display",
|
||||
"minimum": 0,
|
||||
"maximum": 10,
|
||||
"default": 1
|
||||
},
|
||||
"entity": {
|
||||
"$ref": "#/$defs/entity"
|
||||
},
|
||||
"attribute": {
|
||||
"type": "string",
|
||||
"title": "Attribute on the entity with the current numeric value. To use the state of the entity, do not specify."
|
||||
},
|
||||
"service": {
|
||||
"$ref": "#/$defs/service"
|
||||
},
|
||||
"service_attribute": {
|
||||
"type": "string",
|
||||
"title": "Attribute on the service data for the value to set."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type",
|
||||
"min",
|
||||
"max",
|
||||
"step",
|
||||
"entity",
|
||||
"service",
|
||||
"service_attribute"
|
||||
"tap_action"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
@@ -470,21 +354,29 @@
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "brightness"
|
||||
},
|
||||
"service": {
|
||||
"const": "light.turn_on"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "brightness"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 255,
|
||||
"description": "Lights are not a percentage."
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "light.turn_on"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "brightness"
|
||||
},
|
||||
"data_attribute": {
|
||||
"const": "brightness"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 255,
|
||||
"description": "Lights are not a percentage."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -498,20 +390,25 @@
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "value"
|
||||
},
|
||||
"service": {
|
||||
"const": "input_number.set_value"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "value"
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "input_number.set_value"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"data_attribute": {
|
||||
"const": "value"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"required": [
|
||||
"attribute"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"required": [
|
||||
"attribute"
|
||||
]
|
||||
}
|
||||
},
|
||||
"else": {
|
||||
@@ -524,20 +421,25 @@
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "value"
|
||||
},
|
||||
"service": {
|
||||
"const": "number.set_value"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "value"
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "number.set_value"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"data_attribute": {
|
||||
"const": "value"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"required": [
|
||||
"attribute"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"required": [
|
||||
"attribute"
|
||||
]
|
||||
}
|
||||
},
|
||||
"else": {
|
||||
@@ -550,20 +452,28 @@
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "percentage"
|
||||
},
|
||||
"service": {
|
||||
"const": "fan.set_percentage"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "percentage"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "fan.set_percentage"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "percentage"
|
||||
},
|
||||
"data_attribute": {
|
||||
"const": "percentage"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -577,20 +487,28 @@
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"service": {
|
||||
"const": "valve.set_valve_position"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "valve.set_valve_position"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"data_attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -603,48 +521,122 @@
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "position"
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"enum": [
|
||||
"cover.set_position",
|
||||
"cover.set_tilt_position"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"const": "cover.set_position"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "cover.set_tilt_position"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "cover.set_tilt_position"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "tilt_position"
|
||||
},
|
||||
"data_attribute": {
|
||||
"const": "tilt_position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"else": {
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "cover.set_position"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"data_attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"else": {
|
||||
"if": {
|
||||
"properties": {
|
||||
"entity": {
|
||||
"pattern": "^cover\\.[^.]+$"
|
||||
"pattern": "^media_player\\.[^.]+$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "tilt_position"
|
||||
},
|
||||
"service": {
|
||||
"const": "cover.set_tilt_position"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "tilt_position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "media_player.volume_set"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "volume_level"
|
||||
},
|
||||
"data_attribute": {
|
||||
"const": "volume_level"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -652,47 +644,32 @@
|
||||
"if": {
|
||||
"properties": {
|
||||
"entity": {
|
||||
"pattern": "^media_player\\.[^.]+$"
|
||||
"pattern": "^climate\\.[^.]+$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "volume_level"
|
||||
},
|
||||
"service": {
|
||||
"const": "media_player.volume_set"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "volume_level"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"else": {
|
||||
"if": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"entity": {
|
||||
"pattern": "^climate\\.[^.]+$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "temperature"
|
||||
},
|
||||
"service": {
|
||||
"const": "climate.set_temperature"
|
||||
},
|
||||
"service_attribute": {
|
||||
"const": "temperature"
|
||||
"properties": {
|
||||
"service": {
|
||||
"const": "climate.set_temperature"
|
||||
},
|
||||
"picker": {
|
||||
"properties": {
|
||||
"attribute": {
|
||||
"const": "temperature"
|
||||
},
|
||||
"data_attribute": {
|
||||
"const": "temperature"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"required": [
|
||||
"attribute"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,24 +691,246 @@
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
"examples": [
|
||||
{
|
||||
"$ref": "#/$defs/toggle"
|
||||
"type": "tap",
|
||||
"name": "Example",
|
||||
"tap_action": {
|
||||
"service": "notify.notify",
|
||||
"data": {
|
||||
"message": "Example"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/template"
|
||||
"type": "toggle",
|
||||
"name": "Example",
|
||||
"entity": "switch.example"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/tap"
|
||||
"type": "group",
|
||||
"name": "Example",
|
||||
"title": "Example",
|
||||
"items": []
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/info"
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "light.example",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"picker": {
|
||||
"attribute": "brightness",
|
||||
"data_attribute": "brightness",
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/group"
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "input_number.example",
|
||||
"tap_action": {
|
||||
"service": "input_number.set_value",
|
||||
"picker": {
|
||||
"data_attribute": "value",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/numeric"
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "number.example",
|
||||
"tap_action": {
|
||||
"service": "number.set_value",
|
||||
"picker": {
|
||||
"data_attribute": "value",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "fan.example",
|
||||
"tap_action": {
|
||||
"service": "fan.set_percentage",
|
||||
"picker": {
|
||||
"attribute": "percentage",
|
||||
"data_attribute": "percentage",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1,
|
||||
"display_format": "%.0f%%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "valve.example",
|
||||
"tap_action": {
|
||||
"service": "valve.set_valve_position",
|
||||
"picker": {
|
||||
"attribute": "position",
|
||||
"data_attribute": "position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "cover.example",
|
||||
"tap_action": {
|
||||
"service": "cover.set_position",
|
||||
"picker": {
|
||||
"attribute": "position",
|
||||
"data_attribute": "position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "cover.example",
|
||||
"tap_action": {
|
||||
"service": "cover.set_tilt_position",
|
||||
"picker": {
|
||||
"attribute": "tilt_position",
|
||||
"data_attribute": "tilt_position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "media_player.example",
|
||||
"tap_action": {
|
||||
"service": "media_player.volume_set",
|
||||
"picker": {
|
||||
"attribute": "volume_level",
|
||||
"data_attribute": "volume_level",
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.01
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "climate.example",
|
||||
"tap_action": {
|
||||
"service": "climate.set_temperature",
|
||||
"picker": {
|
||||
"attribute": "temperature",
|
||||
"data_attribute": "temperature"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"toggle",
|
||||
"template",
|
||||
"tap",
|
||||
"info",
|
||||
"group",
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "toggle"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"$ref": "#/$defs/toggle"
|
||||
},
|
||||
"else": {
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "template"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"$ref": "#/$defs/template"
|
||||
},
|
||||
"else": {
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "tap"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"$ref": "#/$defs/tap"
|
||||
},
|
||||
"else": {
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"$ref": "#/$defs/info"
|
||||
},
|
||||
"else": {
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "group"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"$ref": "#/$defs/group"
|
||||
},
|
||||
"else": {
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "numeric"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"$ref": "#/$defs/numeric"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -753,7 +952,7 @@
|
||||
"tap_action": {
|
||||
"type": "object",
|
||||
"title": "Action",
|
||||
"description": "'confirm' field is optional.",
|
||||
"description": "'confirm' and 'pin' fields are optional.",
|
||||
"properties": {
|
||||
"confirm": {
|
||||
"$ref": "#/$defs/confirm"
|
||||
@@ -769,10 +968,10 @@
|
||||
"type": "string"
|
||||
},
|
||||
"confirm": {
|
||||
"type": "boolean",
|
||||
"type": ["boolean", "string"],
|
||||
"default": false,
|
||||
"title": "Confirmation",
|
||||
"description": "Optional confirmation of the action before execution as a precaution."
|
||||
"description": "Optional confirmation of the action before execution as a precaution. Use a Boolean for the default message. Specify a string to display a specific confirmation message."
|
||||
},
|
||||
"pin": {
|
||||
"type": "boolean",
|
||||
@@ -827,4 +1026,4 @@
|
||||
"description": "Choose to exit the application after this item has been selected. Disabled (false) by default. N.B. Only actionable menu items can have this field added."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Numeric](Numeric.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
|
||||
|
||||
# Actions
|
||||
@@ -37,6 +37,14 @@ For example:
|
||||
"confirm": true
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
The `confirm` field may contain a string instead of a Boolean in order to provide a custom message to display instead of the default "Sure?" text.
|
||||
|
||||
```json
|
||||
"tap_action": {
|
||||
"confirm": "Toggle the cover?"
|
||||
}
|
||||
```
|
||||
|
||||
**The authors do not advise the use of this application for security sensitive devices. But we suspect users are taking that risk anyway, hence a PIN confirmation is provided that can be used for additional menu item security.**
|
||||
@@ -108,3 +116,64 @@ If you would like to temporarily disable an item in your menu, e.g. for seasonal
|
||||
"enabled": false
|
||||
}
|
||||
```
|
||||
|
||||
# Selects
|
||||
|
||||
Here is an example of how to make a light effect selector:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "group",
|
||||
"name": "Example",
|
||||
"title": "Light Effect",
|
||||
"content": "{{ state_attr('light.moon', 'effect') }}",
|
||||
"items": [
|
||||
{
|
||||
"type": "tap",
|
||||
"name": "None",
|
||||
"entity": "light.example",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"data": {
|
||||
"effect": "None"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tap",
|
||||
"name": "Rainbow",
|
||||
"entity": "light.example",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"data": {
|
||||
"effect": "Rainbow"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tap",
|
||||
"name": "Glimmer",
|
||||
"entity": "light.example",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"data": {
|
||||
"effect": "Glimmer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tap",
|
||||
"name": "Twinkle",
|
||||
"entity": "light.example",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"data": {
|
||||
"effect": "Twinkle"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The same pattern works for any selector (`input_select.*`, `select.*`, `climate.*` mode).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Numeric](Numeric.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
|
||||
# Glance
|
||||
|
||||
|
||||
163
examples/Numeric.md
Normal file
163
examples/Numeric.md
Normal file
@@ -0,0 +1,163 @@
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Numeric](Numeric.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
|
||||
# Numeric
|
||||
|
||||
Provides a number picker in order to adjust a numeric value of an entity.
|
||||
|
||||
## Thermostat
|
||||
|
||||
An example using a thermostat as a `numeric` menu item.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Heating",
|
||||
"content": "{{ ' %.1f' | format(state_attr('climate.room','temperature')) }}",
|
||||
"type": "numeric",
|
||||
"entity": "climate.room",
|
||||
"tap_action": {
|
||||
"service": "climate.set_temperature",
|
||||
"picker": {
|
||||
"step": 0.5,
|
||||
"min": 10,
|
||||
"max": 30,
|
||||
"attribute": "temperature",
|
||||
"data_attribute": "temperature"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This needs some explanation. The `tap_action` object needs a `picker` object to specify the numeric menu item's behaviour. The `picker` object is described in the table below.
|
||||
|
||||
Field | Purpose | Mandatory |
|
||||
-----------------|----------------------------------------------------------------|-----------|
|
||||
`step` | The increment or decrement step size. | Yes |
|
||||
`min` | The minimum value the numeric entity can take. | Yes |
|
||||
`max` | The maximum value the numeric entity can take. | Yes |
|
||||
`attribute` | The attribute on the `entity` that holds the state to be read. | No |
|
||||
`data_attribute` | The attribute on the `service` call that sets the state. | Yes |
|
||||
|
||||
It may well be the case that often `attribute` and `data_attribute` are the same attribute, as with this example.
|
||||
|
||||
## Helper
|
||||
|
||||
You might define a "helper" entity as follows in Home Assistant:
|
||||
|
||||
<img src="../images/my_float.png" width="400" title="Home Assistant Helper definition for an 'input_number'." style="margin:5px"/>
|
||||
|
||||
In this case, the state is the actual value, so the template uses `states(..)` instead of `state_attr(..)`, you must not set the optional `attribute` value in the JSON definition so that the application uses the correct template internally for querying the HA server for its present value. Your own template definition in the `content` field will need to follow suit too. The `data_attribute` must be set to `value` for the service call that sets the chosen value from the number carousel.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Float",
|
||||
"content": "Currently {{ states('input_number.my_float') }}",
|
||||
"type": "numeric",
|
||||
"entity": "input_number.my_float",
|
||||
"tap_action": {
|
||||
"service": "input_number.set_value",
|
||||
"picker": {
|
||||
"step": 0.5,
|
||||
"min": -10.0,
|
||||
"max": 10.0,
|
||||
"data_attribute": "value"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Amplifier
|
||||
|
||||
The complication here is this amplifier uses one scale for changing the value, a range 0.0 to 1.0, and another to render the volume on the display, dB. So the template does some scale conversion, but the number picker has to use the 0.0 to 1.0 range which is annoying.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Amplifer Volume",
|
||||
"content": "{{ '%.1f' | format(state_attr('media_player.amplifier','volume_level') * 100 -80) }} dB ({{ state_attr('media_player.amplifier','volume_level') }})",
|
||||
"type": "numeric",
|
||||
"entity": "media_player.amplifier",
|
||||
"tap_action": {
|
||||
"service": "media_player.volume_set",
|
||||
"picker": {
|
||||
"step": 0.005,
|
||||
"min": 0.2,
|
||||
"max": 0.6,
|
||||
"attribute": "volume_level",
|
||||
"data_attribute": "volume_level"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The above is a little awkward to change the volume as the picker's scale is unfamiliar. To make life easier you might choose to implement a "Template number" in Home Assistant as defined in the following dialogue box.
|
||||
|
||||
<img src="../images/template_number.png" width="500" title="Home Assistant Helper definition for an 'input_number'." style="margin:5px"/>
|
||||
|
||||
For copy and paste, the Jinja2 fields are as follows:
|
||||
|
||||
1. Template rendering with conversion to dB:
|
||||
|
||||
```
|
||||
{{ state_attr('media_player.amplifier','volume_level') * 100 -80 }}
|
||||
```
|
||||
|
||||
2. Conversion from dB to range 0.0 to 1.0:
|
||||
|
||||
```
|
||||
{{ (value+80)/100 }}
|
||||
```
|
||||
|
||||
3. Availability template:
|
||||
|
||||
```
|
||||
{{ not is_state('media_player.amplifier','unavailable') }}
|
||||
```
|
||||
|
||||
As an alternative to using the GUI, the following can be pasted into Home Assistant's `configuration.yaml`:
|
||||
|
||||
```yaml
|
||||
template:
|
||||
- number:
|
||||
- name: "Amplifier dB"
|
||||
unique_id: "<Generate Unique ID>"
|
||||
unit_of_measurement: "dB"
|
||||
state: "{{ state_attr('media_player.amplifier','volume_level') * 100 -80 }}"
|
||||
availability: "{{ not is_state('media_player.amplifier','unavailable') }}"
|
||||
set_value:
|
||||
- action: media_player.volume_set
|
||||
target:
|
||||
entity_id: media_player.amplifier
|
||||
data:
|
||||
volume_level: "{{ (value+80)/100 }}"
|
||||
step: 0.5
|
||||
min: -60
|
||||
max: -15
|
||||
icon: mdi:audio-video
|
||||
```
|
||||
|
||||
We noticed some schema checking errors when we tried this, but the YAML above is consistent with the HA [Template](https://www.home-assistant.io/integrations/template/#number) support pages, and this code does correctly create the number template as required.
|
||||
|
||||
The JSON menu definition can now use dB with the new template number as follows.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Amplifier Volume",
|
||||
"content": "{% if is_state('media_player.amplifier','unavailable') %}Off{% else %}{{ '%.1f' | format(states('number.amplifier_db') | float) }} dB{% endif %}",
|
||||
"type": "numeric",
|
||||
"entity": "number.amplifier_db",
|
||||
"tap_action": {
|
||||
"service": "number.set_value",
|
||||
"picker": {
|
||||
"step": 0.5,
|
||||
"min": -60.0,
|
||||
"max": -15.0,
|
||||
"data_attribute": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
## Trouble Shooting
|
||||
|
||||
Specific to this menu item:
|
||||
|
||||
1. If the number picker does not initialise with the correct value, amend the `attribute` field. Just because your template renders does not mean the application has extracted the numeric value as the `content` template is rendered on the Home Assistant server.
|
||||
@@ -1,4 +1,4 @@
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Numeric](Numeric.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
|
||||
# Switches
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Numeric](Numeric.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Wi-Fi](../Wi-Fi.md) | [HTTP Headers](../HTTP_Headers.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
|
||||
|
||||
# Templates
|
||||
|
||||
|
||||
BIN
images/my_float.png
Normal file
BIN
images/my_float.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
images/template_number.png
Normal file
BIN
images/template_number.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
@@ -24,7 +24,7 @@
|
||||
Use "Monkey C: Edit Application" from the Visual Studio Code command palette
|
||||
to update the application attributes.
|
||||
-->
|
||||
<iq:application id="971834c4-e4fc-4825-801f-7ac9db0e3044" type="watch-app" name="@Strings.AppName" entry="HomeAssistantApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="3.1.0">
|
||||
<iq:application id="98c36259-498a-4458-9cef-74a273ad2bc3" type="watch-app" name="@Strings.AppName" entry="HomeAssistantApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="3.1.0">
|
||||
<!--
|
||||
Use the following from the Visual Studio Code command palette to edit
|
||||
the build targets:
|
||||
|
||||
@@ -133,20 +133,20 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
|
||||
var data = { "gps_accuracy" => accuracy };
|
||||
// Only add the non-null fields as all the values are optional in Home Assistant, and it avoid submitting fake values.
|
||||
if (position.position != null) {
|
||||
data.put("gps", position.position.toDegrees());
|
||||
data["gps"] = position.position.toDegrees();
|
||||
}
|
||||
if (position.speed != null) {
|
||||
data.put("speed", Math.round(position.speed));
|
||||
data["speed"] = Math.round(position.speed);
|
||||
}
|
||||
if (position.heading != null) {
|
||||
var heading = Math.round(position.heading * 180 / Math.PI);
|
||||
while (heading < 0) {
|
||||
heading += 360;
|
||||
}
|
||||
data.put("course", heading);
|
||||
data["course"] = heading;
|
||||
}
|
||||
if (position.altitude != null) {
|
||||
data.put("altitude", Math.round(position.altitude));
|
||||
data["altitude"] = Math.round(position.altitude);
|
||||
}
|
||||
// System.println("BackgroundServiceDelegate onTemporalEvent(): data = " + data.toString());
|
||||
|
||||
|
||||
@@ -629,6 +629,12 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||
(item as HomeAssistantToggleMenuItem).updateToggleState(data[i.toString() + "t"]);
|
||||
}
|
||||
if (item instanceof HomeAssistantNumericMenuItem) {
|
||||
var s = data[i.toString() + "n"];
|
||||
if ((s instanceof Lang.Number) or (s instanceof Lang.Float)) {
|
||||
(item as HomeAssistantNumericMenuItem).setValue(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Settings.getMenuCheck() && Settings.getCacheConfig() && !mIsCacheChecked) {
|
||||
// We are caching the menu configuration, so let's fetch it and check if its been updated.
|
||||
@@ -714,14 +720,13 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
var item = mItemsToUpdate[i];
|
||||
var template = item.getTemplate();
|
||||
if (template != null) {
|
||||
mTemplates.put(i.toString(), {
|
||||
"template" => template
|
||||
});
|
||||
mTemplates[i.toString()] = { "template" => template };
|
||||
}
|
||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||
mTemplates.put(i.toString() + "t", {
|
||||
"template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate()
|
||||
});
|
||||
mTemplates[i.toString() + "t"] = { "template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate() };
|
||||
}
|
||||
if (item instanceof HomeAssistantNumericMenuItem) {
|
||||
mTemplates[i.toString() + "n"] = { "template" => (item as HomeAssistantNumericMenuItem).getNumericTemplate() };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -822,7 +827,7 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
var phoneConnected = System.getDeviceSettings().phoneConnected;
|
||||
var connectionAvailable = System.getDeviceSettings().connectionAvailable;
|
||||
|
||||
// System.println("API URL = " + Settings.getApiUrl());
|
||||
// System.println("HomeAssistantApp fetchApiStatus(): API URL = " + Settings.getApiUrl());
|
||||
if (Settings.getApiUrl().equals("")) {
|
||||
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
|
||||
WatchUi.requestUpdate();
|
||||
|
||||
@@ -24,10 +24,13 @@ class HomeAssistantConfirmation extends WatchUi.Confirmation {
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
|
||||
function initialize(message as Lang.String?) {
|
||||
if (message == null) {
|
||||
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
|
||||
} else {
|
||||
WatchUi.Confirmation.initialize(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//! Delegate to respond to the confirmation request.
|
||||
|
||||
@@ -33,7 +33,7 @@ class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
|
||||
}?
|
||||
) {
|
||||
if (options != null) {
|
||||
options.put(:icon, icon);
|
||||
options[:icon] = icon;
|
||||
} else {
|
||||
options = { :icon => icon };
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ class HomeAssistantMenuItemFactory {
|
||||
//! Class Constructor
|
||||
//
|
||||
private function initialize() {
|
||||
mMenuItemOptions = {
|
||||
:alignment => Settings.getMenuAlignment()
|
||||
};
|
||||
mMenuItemOptions = { :alignment => Settings.getMenuAlignment() };
|
||||
|
||||
mTapTypeIcon = new WatchUi.Bitmap({
|
||||
:rezId => $.Rez.Drawables.TapTypeIcon,
|
||||
@@ -84,7 +82,7 @@ class HomeAssistantMenuItemFactory {
|
||||
) as WatchUi.MenuItem {
|
||||
var keys = mMenuItemOptions.keys();
|
||||
for (var i = 0; i < keys.size(); i++) {
|
||||
options.put(keys[i], mMenuItemOptions.get(keys[i]));
|
||||
options[keys[i]] = mMenuItemOptions.get(keys[i]);
|
||||
}
|
||||
return new HomeAssistantToggleMenuItem(
|
||||
label,
|
||||
@@ -119,15 +117,15 @@ class HomeAssistantMenuItemFactory {
|
||||
if (data == null) {
|
||||
data = { "entity_id" => entity_id };
|
||||
} else {
|
||||
data.put("entity_id", entity_id);
|
||||
data["entity_id"] = entity_id;
|
||||
}
|
||||
}
|
||||
var keys = mMenuItemOptions.keys();
|
||||
for (var i = 0; i < keys.size(); i++) {
|
||||
options.put(keys[i], mMenuItemOptions.get(keys[i]));
|
||||
options[keys[i]] = mMenuItemOptions.get(keys[i]);
|
||||
}
|
||||
if (service != null) {
|
||||
options.put(:icon, mTapTypeIcon);
|
||||
options[:icon] = mTapTypeIcon;
|
||||
return new HomeAssistantTapMenuItem(
|
||||
label,
|
||||
template,
|
||||
@@ -137,7 +135,7 @@ class HomeAssistantMenuItemFactory {
|
||||
mHomeAssistantService
|
||||
);
|
||||
} else {
|
||||
options.put(:icon, mInfoTypeIcon);
|
||||
options[:icon] = mInfoTypeIcon;
|
||||
return new HomeAssistantTapMenuItem(
|
||||
label,
|
||||
template,
|
||||
@@ -158,7 +156,7 @@ class HomeAssistantMenuItemFactory {
|
||||
entity_id as Lang.String?,
|
||||
template as Lang.String?,
|
||||
service as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
picker as Lang.Dictionary,
|
||||
options as {
|
||||
:exit as Lang.Boolean,
|
||||
:confirm as Lang.Boolean,
|
||||
@@ -166,25 +164,21 @@ class HomeAssistantMenuItemFactory {
|
||||
:icon as WatchUi.Bitmap
|
||||
}
|
||||
) as WatchUi.MenuItem {
|
||||
var data = null;
|
||||
if (entity_id != null) {
|
||||
if (data == null) {
|
||||
data = { "entity_id" => entity_id };
|
||||
|
||||
} else {
|
||||
data.put("entity_id", entity_id);
|
||||
}
|
||||
}
|
||||
data = { "entity_id" => entity_id };
|
||||
}
|
||||
var keys = mMenuItemOptions.keys();
|
||||
for (var i = 0; i < keys.size(); i++) {
|
||||
options.put(keys[i], mMenuItemOptions.get(keys[i]));
|
||||
options[keys[i]] = mMenuItemOptions.get(keys[i]);
|
||||
}
|
||||
options.put(:icon, mTapTypeIcon);
|
||||
|
||||
options[:icon] = mTapTypeIcon;
|
||||
return new HomeAssistantNumericMenuItem(
|
||||
label,
|
||||
template,
|
||||
service,
|
||||
data,
|
||||
picker,
|
||||
options,
|
||||
mHomeAssistantService
|
||||
);
|
||||
|
||||
@@ -9,80 +9,92 @@
|
||||
// tested on a Venu 2 device. The source code is provided at:
|
||||
// https://github.com/house-of-abbey/GarminHomeAssistant.
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
||||
// P A Abbey & J D Abbey & @thmichel, 13 October 2025
|
||||
//
|
||||
//------------------------------------------------------------
|
||||
|
||||
import Toybox.Graphics;
|
||||
import Toybox.Lang;
|
||||
import Toybox.WatchUi;
|
||||
using Toybox.Graphics;
|
||||
using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
|
||||
//! Factory that controls which numbers can be picked
|
||||
class HomeAssistantNumericFactory extends WatchUi.PickerFactory {
|
||||
// define default values in case not contained in data
|
||||
private var mStart as Lang.Float = 0.0;
|
||||
private var mStop as Lang.Float = 100.0;
|
||||
private var mStep as Lang.Float = 1.0;
|
||||
private var mFormatString as Lang.String = "%.2f";
|
||||
private var mStart as Lang.Float = 0.0;
|
||||
private var mStop as Lang.Float = 100.0;
|
||||
private var mStep as Lang.Float = 1.0;
|
||||
private var mFormatString as Lang.String = "%d";
|
||||
|
||||
//! Class Constructor
|
||||
//!
|
||||
public function initialize(data as Lang.Dictionary) {
|
||||
//
|
||||
public function initialize(picker as Lang.Dictionary) {
|
||||
PickerFactory.initialize();
|
||||
|
||||
// Get values from data
|
||||
|
||||
var val = data.get("start");
|
||||
var val = picker["min"];
|
||||
if (val != null) {
|
||||
mStart = val.toString().toFloat();
|
||||
}
|
||||
val = data.get("stop");
|
||||
val = picker["max"];
|
||||
if (val != null) {
|
||||
mStop = val.toString().toFloat();
|
||||
}
|
||||
val = data.get("step");
|
||||
val = picker["step"];
|
||||
if (val != null) {
|
||||
mStep = val.toString().toFloat();
|
||||
}
|
||||
val = data.get("formatString");
|
||||
if (val != null) {
|
||||
mFormatString = val.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//! Get the index of a number item
|
||||
//! @param value The number to get the index of
|
||||
//! @return The index of the number
|
||||
public function getIndex(value as Float) as Number {
|
||||
return ((value / mStep) - mStart).toNumber();
|
||||
if (mStep > 0.0) {
|
||||
var s = mStep;
|
||||
var dp = 0;
|
||||
while (s < 1.0) {
|
||||
s *= 10;
|
||||
dp++;
|
||||
// Assigned inside the loop and in each iteration to avoid clobbering the default '%d'.
|
||||
mFormatString = "%." + dp.toString() + "f";
|
||||
}
|
||||
} else {
|
||||
// The JSON menu definition defined a step size of 0, revert to the default.
|
||||
mStep = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
//! Generate a Drawable instance for an item
|
||||
//!
|
||||
//! @param index The item index
|
||||
//! @param selected true if the current item is selected, false otherwise
|
||||
//! @return Drawable for the item
|
||||
public function getDrawable(index as Number, selected as Boolean) as Drawable? {
|
||||
//
|
||||
public function getDrawable(
|
||||
index as Lang.Number,
|
||||
selected as Lang.Boolean
|
||||
) as WatchUi.Drawable? {
|
||||
var value = getValue(index);
|
||||
var text = "No item";
|
||||
if (value instanceof Lang.Float) {
|
||||
text = value.format(mFormatString);
|
||||
}
|
||||
return new WatchUi.Text({:text=>text, :color=>Graphics.COLOR_WHITE,
|
||||
:locX=>WatchUi.LAYOUT_HALIGN_CENTER, :locY=>WatchUi.LAYOUT_VALIGN_CENTER});
|
||||
return new WatchUi.Text({
|
||||
:text => text,
|
||||
:color => Graphics.COLOR_WHITE,
|
||||
:locX => WatchUi.LAYOUT_HALIGN_CENTER,
|
||||
:locY => WatchUi.LAYOUT_VALIGN_CENTER
|
||||
});
|
||||
}
|
||||
|
||||
//! Get the value of the item at the given index
|
||||
//!
|
||||
//! @param index Index of the item to get the value of
|
||||
//! @return Value of the item
|
||||
public function getValue(index as Number) as Object? {
|
||||
//
|
||||
public function getValue(index as Lang.Number) as Lang.Object? {
|
||||
return mStart + (index * mStep);
|
||||
}
|
||||
|
||||
//! Get the number of picker items
|
||||
//!
|
||||
//! @return Number of items
|
||||
public function getSize() as Number {
|
||||
//
|
||||
public function getSize() as Lang.Number {
|
||||
return ((mStop - mStart) / mStep).toNumber() + 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
// tested on a Venu 2 device. The source code is provided at:
|
||||
// https://github.com/house-of-abbey/GarminHomeAssistant.
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
||||
// P A Abbey & J D Abbey & @thmichel, 13 October 2025
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
@@ -17,20 +17,19 @@ using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
using Toybox.Graphics;
|
||||
|
||||
|
||||
//! Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders
|
||||
//! a Home Assistant Template.
|
||||
//
|
||||
class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
private var mHomeAssistantService as HomeAssistantService?;
|
||||
private var mService as Lang.String?;
|
||||
private var mConfirm as Lang.Boolean;
|
||||
private var mConfirm as Lang.Boolean or Lang.String or Null;
|
||||
private var mExit as Lang.Boolean;
|
||||
private var mPin as Lang.Boolean;
|
||||
private var mData as Lang.Dictionary?;
|
||||
private var mValue as Lang.String?;
|
||||
private var mFormatString as Lang.String="%.1f";
|
||||
|
||||
private var mPicker as Lang.Dictionary?;
|
||||
private var mValue as Lang.Number or Lang.Float = 0;
|
||||
private var mFormatString as Lang.String = "%d";
|
||||
|
||||
//! Class Constructor
|
||||
//!
|
||||
@@ -51,6 +50,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
template as Lang.String,
|
||||
service as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
picker as Lang.Dictionary,
|
||||
options as {
|
||||
:alignment as WatchUi.MenuItem.Alignment,
|
||||
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol,
|
||||
@@ -62,6 +62,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
) {
|
||||
mService = service;
|
||||
mData = data;
|
||||
mPicker = picker;
|
||||
mExit = options[:exit];
|
||||
mConfirm = options[:confirm];
|
||||
mPin = options[:pin];
|
||||
@@ -74,12 +75,24 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
{
|
||||
:alignment => options[:alignment],
|
||||
:icon => options[:icon]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (picker != null) {
|
||||
var s = picker["step"];
|
||||
if (s != null) {
|
||||
var step = s.toFloat() as Lang.Float;
|
||||
var dp = 0;
|
||||
while (step < 1.0) {
|
||||
step *= 10;
|
||||
dp++;
|
||||
// Assigned inside the loop and in each iteration to avoid clobbering the default '%d'.
|
||||
mFormatString = "%." + dp.toString() + "f";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function callService() as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
@@ -89,10 +102,10 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
WatchUi.pushView(
|
||||
pinConfirmationView,
|
||||
new HomeAssistantPinConfirmationDelegate({
|
||||
:callback => method(:onConfirm),
|
||||
:pin => pin,
|
||||
:state => false,
|
||||
:view => pinConfirmationView,
|
||||
:callback => method(:onConfirm),
|
||||
:pin => pin,
|
||||
:state => false,
|
||||
:view => pinConfirmationView,
|
||||
}),
|
||||
WatchUi.SLIDE_IMMEDIATE
|
||||
);
|
||||
@@ -114,7 +127,12 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
WatchUi.SLIDE_LEFT
|
||||
);
|
||||
} else {
|
||||
var view = new HomeAssistantConfirmation();
|
||||
var view;
|
||||
if (mConfirm instanceof Lang.String) {
|
||||
view = new HomeAssistantConfirmation(mConfirm as Lang.String?);
|
||||
} else {
|
||||
view = new HomeAssistantConfirmation(null);
|
||||
}
|
||||
WatchUi.pushView(
|
||||
view,
|
||||
new HomeAssistantConfirmationDelegate({
|
||||
@@ -130,25 +148,58 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//! Callback function after the menu items selection has been (optionally) confirmed.
|
||||
//!
|
||||
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
|
||||
//
|
||||
function onConfirm(b as Lang.Boolean) as Void {
|
||||
mHomeAssistantService.call(mService, {"entity_id" => mData.get("entity_id").toString(),mData.get("valueLabel").toString() => mValue}, mExit);
|
||||
|
||||
var dataAttribute = mPicker["data_attribute"];
|
||||
if (dataAttribute == null) {
|
||||
//return without call service if no data attribute is set to avoid crash
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
return;
|
||||
}
|
||||
var entity_id = mData["entity_id"];
|
||||
if (entity_id == null) {
|
||||
//return without call service if no entity_id is set to avoid crash
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
return;
|
||||
}
|
||||
mHomeAssistantService.call(
|
||||
mService,
|
||||
{
|
||||
"entity_id" => entity_id.toString(),
|
||||
dataAttribute.toString() => mValue
|
||||
},
|
||||
mExit
|
||||
);
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
}
|
||||
|
||||
//! Return a numeric menu item's fetch state template.
|
||||
//!
|
||||
//! @return A string with the menu item's template definition (or null).
|
||||
//
|
||||
function getNumericTemplate() as Lang.String? {
|
||||
var entity_id = mData["entity_id"];
|
||||
var attribute = mPicker["attribute"] as Lang.String?;
|
||||
if (entity_id == null) {
|
||||
return null;
|
||||
} else {
|
||||
if (attribute == null) {
|
||||
return "{{states('" + entity_id.toString() + "')}}";
|
||||
} else {
|
||||
return "{{state_attr('" + entity_id.toString() + "','" + attribute + "')}}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! Update the menu item's sub label to display the template rendered by Home Assistant.
|
||||
//!
|
||||
//! @param data The rendered template (typically a string) to be placed in the sub label. This may
|
||||
//! unusually be a number if the SDK interprets the JSON returned by Home Assistant as such.
|
||||
//
|
||||
function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
|
||||
public function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
|
||||
if (data == null) {
|
||||
setSubLabel($.Rez.Strings.Empty);
|
||||
} else if(data instanceof Lang.Float) {
|
||||
@@ -157,26 +208,45 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
} else if(data instanceof Lang.Number) {
|
||||
var f = data.toFloat() as Lang.Float;
|
||||
setSubLabel(f.format(mFormatString));
|
||||
} else if (data instanceof Lang.String){
|
||||
} else if (data instanceof Lang.String) {
|
||||
// This should not happen
|
||||
setSubLabel(data);
|
||||
}
|
||||
else {
|
||||
// The template must return a Float, or the item cannot be formatted locally without error.
|
||||
} else {
|
||||
// The template must return a Float on Numeric value, or the item cannot be formatted locally without error.
|
||||
setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String);
|
||||
}
|
||||
WatchUi.requestUpdate();
|
||||
}
|
||||
|
||||
//! Set the mValue value.
|
||||
//! Set the Picker's value. Needed to set new value via the Service call
|
||||
//!
|
||||
//! Needed to set new value via the Service call
|
||||
//! @param value New value to set.
|
||||
//
|
||||
function setValue(value as Lang.String) as Void {
|
||||
public function setValue(value as Lang.Number or Lang.Float) as Void {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
function getData() as Lang.Dictionary {
|
||||
//! Get the Picker's value.
|
||||
//!
|
||||
//! Needed to set new value via the Service call
|
||||
//
|
||||
public function getValue() as Lang.Number or Lang.Float {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
//! Get the original 'data' field supplied by the JSON menu.
|
||||
//!
|
||||
//! @return Dictionary containing the 'data' field.
|
||||
//
|
||||
public function getData() as Lang.Dictionary {
|
||||
return mData;
|
||||
}
|
||||
|
||||
|
||||
// Get the original 'picker' field supplied by the JSON menu.
|
||||
//!
|
||||
//! @return Dictionary containing the 'picker' field.
|
||||
//
|
||||
public function getPicker() as Lang.Dictionary {
|
||||
return mPicker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// tested on a Venu 2 device. The source code is provided at:
|
||||
// https://github.com/house-of-abbey/GarminHomeAssistant.
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
||||
// P A Abbey & J D Abbey & @thmichel, 13 October 2025
|
||||
//
|
||||
//------------------------------------------------------------
|
||||
|
||||
@@ -20,83 +20,79 @@ using Toybox.System;
|
||||
using Toybox.WatchUi;
|
||||
|
||||
//! Picker that allows the user to choose a float value
|
||||
//
|
||||
class HomeAssistantNumericPicker extends WatchUi.Picker {
|
||||
|
||||
private var mFactory as HomeAssistantNumericFactory;
|
||||
private var mItem as HomeAssistantNumericMenuItem;
|
||||
|
||||
//! Constructor
|
||||
public function initialize(factory as HomeAssistantNumericFactory, haItem as HomeAssistantNumericMenuItem) {
|
||||
//
|
||||
public function initialize(
|
||||
factory as HomeAssistantNumericFactory,
|
||||
haItem as HomeAssistantNumericMenuItem
|
||||
) {
|
||||
mItem = haItem;
|
||||
var picker = mItem.getPicker();
|
||||
var min = (picker.get("min") as Lang.String).toFloat();
|
||||
var step = (picker.get("step") as Lang.String).toFloat();
|
||||
var val = haItem.getValue();
|
||||
|
||||
mFactory = factory;
|
||||
|
||||
|
||||
var pickerOptions = {:pattern=>[mFactory]};
|
||||
mItem=haItem;
|
||||
|
||||
|
||||
var data = mItem.getData();
|
||||
|
||||
var start = 0.0;
|
||||
var val = data.get("start");
|
||||
if (val != null) {
|
||||
start = val.toString().toFloat();
|
||||
}
|
||||
var step = 1.0;
|
||||
val = data.get("step");
|
||||
if (val != null) {
|
||||
step = val.toString().toFloat();
|
||||
}
|
||||
val = haItem.getSubLabel().toFloat();
|
||||
var index = ((val -start) / step).toNumber();
|
||||
|
||||
pickerOptions[:defaults] =[index];
|
||||
|
||||
var title = new WatchUi.Text({:text=>haItem.getLabel(), :locX=>WatchUi.LAYOUT_HALIGN_CENTER,
|
||||
:locY=>WatchUi.LAYOUT_VALIGN_BOTTOM});
|
||||
pickerOptions[:title] = title;
|
||||
|
||||
|
||||
Picker.initialize(pickerOptions);
|
||||
if (min == null) {
|
||||
min = 0.0;
|
||||
}
|
||||
if (step == null) {
|
||||
step = 1.0;
|
||||
}
|
||||
|
||||
WatchUi.Picker.initialize({
|
||||
:title => new WatchUi.Text({
|
||||
:text => haItem.getLabel(),
|
||||
:locX => WatchUi.LAYOUT_HALIGN_CENTER,
|
||||
:locY => WatchUi.LAYOUT_VALIGN_BOTTOM
|
||||
}),
|
||||
:pattern => [factory],
|
||||
:defaults => [((val - min) / step).toNumber()]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//! Get whether the user is done picking
|
||||
//! Called when the user has completed picking.
|
||||
//!
|
||||
//! @param value Value user selected
|
||||
//! @return true if user is done, false otherwise
|
||||
public function onConfirm(value as Lang.String) as Void {
|
||||
//
|
||||
public function onConfirm(value as Lang.Number or Lang.Float) as Void {
|
||||
mItem.setValue(value);
|
||||
mItem.callService();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//! Responds to a numeric picker selection or cancellation
|
||||
//! Responds to a numeric picker selection or cancellation.
|
||||
//
|
||||
class HomeAssistantNumericPickerDelegate extends WatchUi.PickerDelegate {
|
||||
private var mPicker as HomeAssistantNumericPicker;
|
||||
|
||||
//! Constructor
|
||||
//
|
||||
public function initialize(picker as HomeAssistantNumericPicker) {
|
||||
PickerDelegate.initialize();
|
||||
mPicker = picker;
|
||||
}
|
||||
|
||||
//! Handle a cancel event from the picker
|
||||
//!
|
||||
//! @return true if handled, false otherwise
|
||||
//
|
||||
public function onCancel() as Lang.Boolean {
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Handle a confirm event from the picker
|
||||
//!
|
||||
//! @param values The values chosen in the picker
|
||||
//! @return true if handled, false otherwise
|
||||
//
|
||||
public function onAccept(values as Lang.Array) as Lang.Boolean {
|
||||
var chosenValue = values[0].toString();
|
||||
mPicker.onConfirm(chosenValue);
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
mPicker.onConfirm(values[0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ using Toybox.Graphics;
|
||||
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
private var mHomeAssistantService as HomeAssistantService;
|
||||
private var mService as Lang.String?;
|
||||
private var mConfirm as Lang.Boolean;
|
||||
private var mConfirm as Lang.Boolean or Lang.String or Null;
|
||||
private var mExit as Lang.Boolean;
|
||||
private var mPin as Lang.Boolean;
|
||||
private var mData as Lang.Dictionary?;
|
||||
@@ -95,8 +95,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
if ((! System.getDeviceSettings().phoneConnected ||
|
||||
! System.getDeviceSettings().connectionAvailable) &&
|
||||
Settings.getWifiLteExecutionEnabled()) {
|
||||
var dialogMsg = WatchUi.loadResource($.Rez.Strings.WifiLtePrompt) as Lang.String;
|
||||
var dialog = new WatchUi.Confirmation(dialogMsg);
|
||||
var dialog = new WatchUi.Confirmation(WatchUi.loadResource($.Rez.Strings.WifiLtePrompt) as Lang.String);
|
||||
WatchUi.pushView(
|
||||
dialog,
|
||||
new WifiLteExecutionConfirmDelegate({
|
||||
@@ -108,7 +107,12 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
WatchUi.SLIDE_LEFT
|
||||
);
|
||||
} else {
|
||||
var view = new HomeAssistantConfirmation();
|
||||
var view;
|
||||
if (mConfirm instanceof Lang.String) {
|
||||
view = new HomeAssistantConfirmation(mConfirm as Lang.String?);
|
||||
} else {
|
||||
view = new HomeAssistantConfirmation(null);
|
||||
}
|
||||
WatchUi.pushView(
|
||||
view,
|
||||
new HomeAssistantConfirmationDelegate({
|
||||
@@ -128,7 +132,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
//!
|
||||
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
|
||||
//
|
||||
function onConfirm(b as Lang.Boolean) as Void {
|
||||
public function onConfirm(b as Lang.Boolean) as Void {
|
||||
if (mService != null) {
|
||||
mHomeAssistantService.call(mService, mData, mExit);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
private var mData as Lang.Dictionary;
|
||||
private var mTemplate as Lang.String?;
|
||||
private var mExit as Lang.Boolean;
|
||||
private var mConfirm as Lang.Boolean;
|
||||
private var mConfirm as Lang.Boolean or Lang.String or Null;
|
||||
private var mPin as Lang.Boolean;
|
||||
private var mHasVibrate as Lang.Boolean = false;
|
||||
|
||||
@@ -324,7 +324,12 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
Settings.getWifiLteExecutionEnabled()) {
|
||||
wifiPrompt(b);
|
||||
} else {
|
||||
var confirmationView = new HomeAssistantConfirmation();
|
||||
var confirmationView;
|
||||
if (mConfirm instanceof Lang.String) {
|
||||
confirmationView = new HomeAssistantConfirmation(mConfirm as Lang.String?);
|
||||
} else {
|
||||
confirmationView = new HomeAssistantConfirmation(null);
|
||||
}
|
||||
WatchUi.pushView(
|
||||
confirmationView,
|
||||
new HomeAssistantConfirmationDelegate({
|
||||
|
||||
@@ -35,7 +35,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
if (options == null) {
|
||||
options = { :title => definition.get("title") as Lang.String };
|
||||
} else {
|
||||
options.put(:title, definition.get("title") as Lang.String);
|
||||
options[:title] = definition.get("title") as Lang.String;
|
||||
}
|
||||
WatchUi.Menu2.initialize(options);
|
||||
|
||||
@@ -48,7 +48,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
var entity = items[i].get("entity") as Lang.String?;
|
||||
var tap_action = items[i].get("tap_action") as Lang.Dictionary?;
|
||||
var service = items[i].get("service") as Lang.String?; // Deprecated schema
|
||||
var confirm = false as Lang.Boolean?;
|
||||
var confirm = false as Lang.Boolean or Lang.String or Null;
|
||||
var pin = false as Lang.Boolean?;
|
||||
var data = null as Lang.Dictionary?;
|
||||
var enabled = true as Lang.Boolean?;
|
||||
@@ -127,18 +127,23 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
));
|
||||
}
|
||||
} else if (type.equals("numeric") && service != null) {
|
||||
addItem(HomeAssistantMenuItemFactory.create().numeric(
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
data,
|
||||
{
|
||||
:exit => exit,
|
||||
:confirm => confirm,
|
||||
:pin => pin
|
||||
if (tap_action != null) {
|
||||
var picker = tap_action.get("picker") as Lang.Dictionary?;
|
||||
if (picker != null) {
|
||||
addItem(HomeAssistantMenuItemFactory.create().numeric(
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
picker,
|
||||
{
|
||||
:exit => exit,
|
||||
:confirm => confirm,
|
||||
:pin => pin
|
||||
}
|
||||
));
|
||||
}
|
||||
));
|
||||
}
|
||||
} else if (type.equals("info") && content != null) {
|
||||
// Cannot exit from a non-actionable information only menu item.
|
||||
addItem(HomeAssistantMenuItemFactory.create().tap(
|
||||
@@ -167,7 +172,6 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
//!
|
||||
//! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state.
|
||||
//
|
||||
|
||||
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or HomeAssistantNumericMenuItem or Null> {
|
||||
var fullList = [];
|
||||
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
|
||||
@@ -203,8 +207,8 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
//! Called when this View is brought to the foreground. Restore
|
||||
//! the state of this View and prepare it to be shown. This includes
|
||||
//! loading resources into memory.
|
||||
//
|
||||
function onShow() as Void {}
|
||||
|
||||
}
|
||||
|
||||
//! Delegate for the HomeAssistantView.
|
||||
@@ -272,18 +276,13 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
||||
var haItem = item as HomeAssistantNumericMenuItem;
|
||||
// System.println(haItem.getLabel() + " " + haItem.getId());
|
||||
// create new view to select new value
|
||||
|
||||
var mPickerFactory = new HomeAssistantNumericFactory(haItem.getData());
|
||||
|
||||
var mPicker = new HomeAssistantNumericPicker(mPickerFactory,haItem);
|
||||
var mPickerFactory = new HomeAssistantNumericFactory((haItem as HomeAssistantNumericMenuItem).getPicker());
|
||||
var mPicker = new HomeAssistantNumericPicker(mPickerFactory,haItem);
|
||||
var mPickerDelegate = new HomeAssistantNumericPickerDelegate(mPicker);
|
||||
WatchUi.pushView(mPicker,mPickerDelegate,WatchUi.SLIDE_LEFT);
|
||||
} else if (item instanceof HomeAssistantGroupMenuItem) {
|
||||
var haMenuItem = item as HomeAssistantGroupMenuItem;
|
||||
// System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());
|
||||
WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT);
|
||||
// } else {
|
||||
// System.println(item.getLabel() + " " + item.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-name="vs/editor/editor.main"
|
||||
href="https://www.unpkg.com/monaco-editor@0.45.0/min/vs/editor/editor.main.css" />
|
||||
href="https://www.unpkg.com/monaco-editor@0.52.2/min/vs/editor/editor.main.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
|
||||
30
web/main.js
30
web/main.js
@@ -141,10 +141,32 @@ async function get_services() {
|
||||
* @returns {Promise<{}>}
|
||||
*/
|
||||
async function get_schema() {
|
||||
const res = await fetch(
|
||||
'https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json'
|
||||
);
|
||||
return res.json();
|
||||
const searchParams = new URL(window.location).searchParams;
|
||||
|
||||
const url = searchParams.get('schema');
|
||||
if (url) return (await fetch(url)).json();
|
||||
|
||||
const branch = searchParams.get('branch');
|
||||
if (branch)
|
||||
return (
|
||||
await fetch(
|
||||
`https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/refs/heads/${branch}/config.schema.json`
|
||||
)
|
||||
).json();
|
||||
|
||||
const version = searchParams.get('version');
|
||||
if (version)
|
||||
return (
|
||||
await fetch(
|
||||
`https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/refs/tags/${version}/config.schema.json`
|
||||
)
|
||||
).json();
|
||||
|
||||
return (
|
||||
await fetch(
|
||||
`https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json`
|
||||
)
|
||||
).json();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/toastify-js": "^1.12.4",
|
||||
"@types/toastify-js": "1.12.0",
|
||||
"@vscode/webview-ui-toolkit": "1.4.0",
|
||||
"json-ast-comments": "1.1.1",
|
||||
"monaco-editor": "0.52.2",
|
||||
|
||||
8
web/pnpm-lock.yaml
generated
8
web/pnpm-lock.yaml
generated
@@ -6,8 +6,8 @@ settings:
|
||||
|
||||
devDependencies:
|
||||
'@types/toastify-js':
|
||||
specifier: ^1.12.4
|
||||
version: 1.12.4
|
||||
specifier: 1.12.0
|
||||
version: 1.12.0
|
||||
'@vscode/webview-ui-toolkit':
|
||||
specifier: 1.4.0
|
||||
version: 1.4.0(react@18.2.0)
|
||||
@@ -55,8 +55,8 @@ packages:
|
||||
exenv-es6: 1.1.1
|
||||
dev: true
|
||||
|
||||
/@types/toastify-js@1.12.4:
|
||||
resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==}
|
||||
/@types/toastify-js@1.12.0:
|
||||
resolution: {integrity: sha512-fqpDHaKhFukN9KRm24bbH0wozvHmSwjvkaLjBUrWcSfSS4zysIwTYqNLG3XbSNhRlsTNRNLGS23tp/VhPwsfHQ==}
|
||||
dev: true
|
||||
|
||||
/@vscode/webview-ui-toolkit@1.4.0(react@18.2.0):
|
||||
|
||||
Reference in New Issue
Block a user