mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-11-07 18:18:14 +00:00
Rename service to action (#308)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
# Background Service
|
||||
|
||||
The background service can report the following statuses from your device to your Home Assistant:
|
||||
The background service can report the following statuses from your device to your HomeAssistant:
|
||||
|
||||
- Battery Level with charging status.
|
||||
- Location and location accuracy.
|
||||
@@ -12,13 +12,13 @@ If your device does not support the background service, the application will cle
|
||||
|
||||
## Limits
|
||||
|
||||
The values are merely samples of your device's current status. They are sent by a single background service at the repetition frequency you chose in the settings. The samples are sent at that one rate only, they _do not vary_ for example on in activity, on charge, time of day. You get one refresh interval and that is it. If you want to change the refresh interval, you change your settings. We do appreciate that may not be what you would ideally like to trigger actions on Home Assistant. Messing with the repeat interval of the background service requires more code, more settings and more complexity. That means older devices using widgets would have to be taken out of support to achieve it.
|
||||
The values are merely samples of your device's current status. They are sent by a single background service at the repetition frequency you chose in the settings. The samples are sent at that one rate only, they _do not vary_ for example on in activity, on charge, time of day. You get one refresh interval and that is it. If you want to change the refresh interval, you change your settings. We do appreciate that may not be what you would ideally like to trigger actions on HomeAssistant. Messing with the repeat interval of the background service requires more code, more settings and more complexity. That means older devices using widgets would have to be taken out of support to achieve it.
|
||||
|
||||
**Please do not ask for these to be made 'events'.** Garmin's [Connect IQ background service](https://developer.garmin.com/connect-iq/api-docs/Toybox/System/ServiceDelegate.html) is limited in that while it does provide an `onActivityCompleted()` method, it does not provide an `onActivityStarted()` method, so you would not have the complete activity life cycle anyway. So we're keeping this implementation simple, you just get a sampling at one refresh rate. This probably limits you to updating a status on a Home Assistant Dashboard only.
|
||||
**Please do not ask for these to be made 'events'.** Garmin's [Connect IQ background service](https://developer.garmin.com/connect-iq/api-docs/Toybox/System/ServiceDelegate.html) is limited in that while it does provide an `onActivityCompleted()` method, it does not provide an `onActivityStarted()` method, so you would not have the complete activity life cycle anyway. So we're keeping this implementation simple, you just get a sampling at one refresh rate. This probably limits you to updating a status on a HomeAssistant Dashboard only.
|
||||
|
||||
## Battery Reporting
|
||||
|
||||
From version 2.1 the application includes a background service to report the current device battery level and charging status back to Home Assistant. This is a feature that Garmin omitted to include with the Bluetooth connection.
|
||||
From version 2.1 the application includes a background service to report the current device battery level and charging status back to HomeAssistant. This is a feature that Garmin omitted to include with the Bluetooth connection.
|
||||
|
||||
## Location Reporting
|
||||
|
||||
@@ -47,7 +47,7 @@ From version 2.6 the application includes reporting your activity. The activity
|
||||
- Activity - This is an integer as defined by [Toybox.Activity `SPORT`](https://developer.garmin.com/connect-iq/api-docs/Toybox/Activity.html#Sport-module)
|
||||
- Sub-activity - This is an integer as defined by [Toybox.Activity `SUB_SPORT`](https://developer.garmin.com/connect-iq/api-docs/Toybox/Activity.html#SubSport-module)
|
||||
|
||||
The application only provides the integers without translation. When using the values in Home Assistant, you will need to provide you own mapping from the `Activity` enumerated type to the human readable text. As developers of the application we are pushing this translation to the server to keep the Garmin application code 'lean'. You will also need to add to both the list of activities (sports) and sub-activities (sub-sports) an interpretation of integer `-1` for no activity/sub-activity at present.
|
||||
The application only provides the integers without translation. When using the values in HomeAssistant, you will need to provide you own mapping from the `Activity` enumerated type to the human readable text. As developers of the application we are pushing this translation to the server to keep the Garmin application code 'lean'. You will also need to add to both the list of activities (sports) and sub-activities (sub-sports) an interpretation of integer `-1` for no activity/sub-activity at present.
|
||||
|
||||
## Start Reporting
|
||||
|
||||
@@ -55,7 +55,7 @@ The main drawback of this solution is that the Garmin application must be run on
|
||||
|
||||
It should be as simple as starting the application (or widget). There should be a new device in the mobile app integration called `Garmin Watch` with the battery level and charging status.
|
||||
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=mobile_app)
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=mobile_app)
|
||||
|
||||
If this is not the case, head over to the [troubleshooting page](Troubleshooting.md#watch-battery-level-reporting).
|
||||
|
||||
@@ -67,7 +67,7 @@ To stop the reporting, the option must be turned off in the settings and then th
|
||||
|
||||
When the device is first created, it will be called `Garmin Watch`. This can be changed in the mobile app integration settings (button below).
|
||||
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=mobile_app)
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=mobile_app)
|
||||
|
||||
Select the device called `Garmin Watch` and then click on the edit icon in the top right corner. You can then change the name of the device to whatever you like, then press `UPDATE` and then `RENAME`.
|
||||
|
||||
@@ -91,7 +91,7 @@ template:
|
||||
icon: "mdi:battery{% if is_state('binary_sensor.<device>_battery_is_charging', 'on') %}-charging{% endif %}{% if 0 < (states('sensor.<device>_battery_level') | float / 10 ) | round(0) * 10 < 100 %}-{{ (states('sensor.<device>_battery_level') | float / 10 ) | round(0) * 10 }}{% else %}{% if (states('sensor.<device>_battery_level') | float / 10 ) | round(0) * 10 == 0 %}-outline{% else %}{% if is_state('binary_sensor.<device>_battery_is_charging', 'on') %}-100{% endif %}{% endif %}{% endif %}"
|
||||
```
|
||||
|
||||
## Adding a sample Home Assistant UI widget
|
||||
## Adding a sample HomeAssistant UI widget
|
||||
|
||||
A gauge for battery level with a charging icon making use of [mushroom cards](https://github.com/piitaya/lovelace-mushroom), [card_mod](https://github.com/thomasloven/lovelace-card-mod) and [stack-in-card](https://github.com/custom-cards/stack-in-card):
|
||||
|
||||
@@ -161,7 +161,7 @@ N.B. `sensor.<device>_battery_level` will likely need to be changed to `sensor.<
|
||||
|
||||
## Migrating
|
||||
|
||||
You should remove your old template sensors before migrating to the new integration. You can do this by removing the `sensor.<device>_battery_level` and `binary_sensor.<device>_battery_is_charging` entities from `configuration.yaml` and then restarting Home Assistant or reloading the YAML.
|
||||
You should remove your old template sensors before migrating to the new integration. You can do this by removing the `sensor.<device>_battery_level` and `binary_sensor.<device>_battery_is_charging` entities from `configuration.yaml` and then restarting HomeAssistant or reloading the YAML.
|
||||
|
||||
[Here is the old configuration method for reference.](https://github.com/house-of-abbey/GarminHomeAssistant/blob/b51e2aa2a4afbc58ad466f3b81667d1cd252d091/BatteryReporting.md)
|
||||
|
||||
|
||||
194
Devices.md
Normal file
194
Devices.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Device Support & Characterisation
|
||||
|
||||
A page just to note a practical limit on support for some older devices.
|
||||
|
||||
## Application Memory Usage
|
||||
|
||||
On an `instinct2x` device:
|
||||
|
||||
| Version | Free Memory (bytes) on `instinct2x`| Free Memory (bytes) on `venu2`|
|
||||
|:-------:|-----------------------------------:|------------------------------:|
|
||||
| 3.5 | 62,360 | - |
|
||||
| 3.6 | 65,696 | 53,832 |
|
||||
|
||||
A user has reported a maximum of 26 items with Ver 3.5. This measurement has shown that each menu item requires about 1.0~1.2 kB. Using the worked example below it is possible to predict how many menu items your particular device might be able to support by using indicative figures.
|
||||
|
||||
## Worked Example
|
||||
|
||||
As a worked example, for Ver 3.6 working on an `instinct2x` device:
|
||||
|
||||
| Feature | Memory (bytes) | Cost (bytes) |
|
||||
|--------------------------------------|---------------:|-------------:|
|
||||
| Declared available to application | 98,304 | |
|
||||
| Measured available to application | 94,112 | (4,192 less) |
|
||||
| Application used | 65,696 | |
|
||||
| Free before fetching menu definition | 28,416 | |
|
||||
| Free after fetching menu definition | 15,792 | 12,624 |
|
||||
| Free after construction | 936 | 14,856 |
|
||||
|
||||
Our test menu presently contains a mix of 28 items, consisting of nested group, toggle, tap, info and numeric items with templates. So each item requires (12,624 + 14,856) / 28 = 982 bytes.
|
||||
|
||||
## Garmin Devices
|
||||
|
||||
The following table details all the devices as at 1 October 2025 and whether they are supported by Garmin HomeAssistant. The available application memory is also detailed so that it can be compared to an application version listed above. Of particular concern are the 'Instinct' range of devices, being the smallest we currently support. New feature requests are now being vetted against how they might affect our ability to support the 'Instinct' range of devices. At some point support may have to be withdrawn in order to allow the Garmin HomeAssistant application to grow further.
|
||||
|
||||
| Device | Supported | Application Memory |
|
||||
|----------------------------|:---------:|--------------------:|
|
||||
| d2bravo | N | 65,536 |
|
||||
| d2bravo_titanium | N | 65,536 |
|
||||
| fenix3 | N | 65,536 |
|
||||
| fenix3_hr | N | 65,536 |
|
||||
| fr230 | N | 65,536 |
|
||||
| fr235 | N | 65,536 |
|
||||
| fr630 | N | 65,536 |
|
||||
| fr920xt | N | 65,536 |
|
||||
| vivoactive | N | 65,536 |
|
||||
| descentg1 | Y | 98,304 |
|
||||
| instinct2 | Y | 98,304 |
|
||||
| instinct2s | Y | 98,304 |
|
||||
| instinct2x | Y | 98,304 |
|
||||
| instinctcrossover | Y | 98,304 |
|
||||
| approachs60 | N | 131,072 |
|
||||
| enduro | Y | 131,072 |
|
||||
| fenix5 | Y | 131,072 |
|
||||
| fenix5s | Y | 131,072 |
|
||||
| fenix6 | Y | 131,072 |
|
||||
| fenix6s | Y | 131,072 |
|
||||
| fenixchronos | Y | 131,072 |
|
||||
| fr245 | Y | 131,072 |
|
||||
| fr55 | Y | 131,072 |
|
||||
| fr645 | Y | 131,072 |
|
||||
| fr735xt | N | 131,072 |
|
||||
| fr935 | Y | 131,072 |
|
||||
| instinct3solar45mm | Y | 131,072 |
|
||||
| instincte40mm | Y | 131,072 |
|
||||
| instincte45mm | Y | 131,072 |
|
||||
| venusq | Y | 131,072 |
|
||||
| vivoactive3 | Y | 131,072 |
|
||||
| vivoactive3d | N | 131,072 |
|
||||
| vivoactive_hr | N | 131,072 |
|
||||
| edge_520 | N | 262,144 |
|
||||
| fr255 | Y | 524,288 |
|
||||
| fr255s | Y | 524,288 |
|
||||
| approachs50 | Y | 786,432 |
|
||||
| approachs7042mm | Y | 786,432 |
|
||||
| approachs7047mm | Y | 786,432 |
|
||||
| d2airx10 | Y | 786,432 |
|
||||
| d2mach1 | Y | 786,432 |
|
||||
| descentg2 | Y | 786,432 |
|
||||
| descentmk343mm | Y | 786,432 |
|
||||
| descentmk351mm | Y | 786,432 |
|
||||
| enduro3 | Y | 786,432 |
|
||||
| epix2 | Y | 786,432 |
|
||||
| epix2pro42mm | Y | 786,432 |
|
||||
| epix2pro47mm | Y | 786,432 |
|
||||
| epix2pro47mmsystem7preview | Y | 786,432 |
|
||||
| epix2pro51mm | Y | 786,432 |
|
||||
| fenix7 | Y | 786,432 |
|
||||
| fenix7pro | Y | 786,432 |
|
||||
| fenix7pronowifi | Y | 786,432 |
|
||||
| fenix7s | Y | 786,432 |
|
||||
| fenix7spro | Y | 786,432 |
|
||||
| fenix7x | Y | 786,432 |
|
||||
| fenix7xpro | Y | 786,432 |
|
||||
| fenix7xpronowifi | Y | 786,432 |
|
||||
| fenix843mm | Y | 786,432 |
|
||||
| fenix847mm | Y | 786,432 |
|
||||
| fenix8pro47mm | Y | 786,432 |
|
||||
| fenix8solar47mm | Y | 786,432 |
|
||||
| fenix8solar51mm | Y | 786,432 |
|
||||
| fenixe | Y | 786,432 |
|
||||
| fr165 | Y | 786,432 |
|
||||
| fr165m | Y | 786,432 |
|
||||
| fr255m | Y | 786,432 |
|
||||
| fr255sm | Y | 786,432 |
|
||||
| fr265 | Y | 786,432 |
|
||||
| fr265s | Y | 786,432 |
|
||||
| fr57042mm | Y | 786,432 |
|
||||
| fr57047mm | Y | 786,432 |
|
||||
| fr955 | Y | 786,432 |
|
||||
| fr965 | Y | 786,432 |
|
||||
| fr970 | Y | 786,432 |
|
||||
| instinct3amoled45mm | Y | 786,432 |
|
||||
| instinct3amoled50mm | Y | 786,432 |
|
||||
| instinctcrossoveramoled | Y | 786,432 |
|
||||
| marq2 | Y | 786,432 |
|
||||
| marq2aviator | Y | 786,432 |
|
||||
| system8preview | N | 786,432 |
|
||||
| venu2 | Y | 786,432 |
|
||||
| venu2plus | Y | 786,432 |
|
||||
| venu2s | Y | 786,432 |
|
||||
| venu3 | Y | 786,432 |
|
||||
| venu3s | Y | 786,432 |
|
||||
| venu441mm | Y | 786,432 |
|
||||
| venu445mm | Y | 786,432 |
|
||||
| venusq2 | Y | 786,432 |
|
||||
| venusq2m | Y | 786,432 |
|
||||
| venux1 | Y | 786,432 |
|
||||
| vivoactive5 | Y | 786,432 |
|
||||
| vivoactive6 | Y | 786,432 |
|
||||
| approachs62 | N | 1,048,576 |
|
||||
| d2air | Y | 1,048,576 |
|
||||
| edge1030 | Y | 1,048,576 |
|
||||
| edge1030bontrager | Y | 1,048,576 |
|
||||
| edge1030plus | Y | 1,048,576 |
|
||||
| edge1040 | Y | 1,048,576 |
|
||||
| edge1050 | Y | 1,048,576 |
|
||||
| edge520plus | Y | 1,048,576 |
|
||||
| edge530 | Y | 1,048,576 |
|
||||
| edge540 | Y | 1,048,576 |
|
||||
| edge550 | Y | 1,048,576 |
|
||||
| edge820 | Y | 1,048,576 |
|
||||
| edge830 | Y | 1,048,576 |
|
||||
| edge840 | Y | 1,048,576 |
|
||||
| edge850 | Y | 1,048,576 |
|
||||
| edgeexplore | Y | 1,048,576 |
|
||||
| edgeexplore2 | Y | 1,048,576 |
|
||||
| edgemtb | Y | 1,048,576 |
|
||||
| edge_1000 | N | 1,048,576 |
|
||||
| epix | N | 1,048,576 |
|
||||
| fr645m | Y | 1,048,576 |
|
||||
| legacyherocaptainmarvel | Y | 1,048,576 |
|
||||
| legacyherofirstavenger | Y | 1,048,576 |
|
||||
| legacysagadarthvader | Y | 1,048,576 |
|
||||
| legacysagarey | Y | 1,048,576 |
|
||||
| venu | Y | 1,048,576 |
|
||||
| venud | Y | 1,048,576 |
|
||||
| venusqm | Y | 1,048,576 |
|
||||
| vivoactive3m | Y | 1,048,576 |
|
||||
| vivoactive3mlte | Y | 1,048,576 |
|
||||
| vivoactive4 | Y | 1,048,576 |
|
||||
| vivoactive4s | Y | 1,048,576 |
|
||||
| d2charlie | N | 1,310,720 |
|
||||
| d2delta | Y | 1,310,720 |
|
||||
| d2deltapx | Y | 1,310,720 |
|
||||
| d2deltas | Y | 1,310,720 |
|
||||
| descentmk1 | N | 1,310,720 |
|
||||
| descentmk2 | Y | 1,310,720 |
|
||||
| descentmk2s | Y | 1,310,720 |
|
||||
| fenix5plus | Y | 1,310,720 |
|
||||
| fenix5splus | Y | 1,310,720 |
|
||||
| fenix5x | Y | 1,310,720 |
|
||||
| fenix5xplus | Y | 1,310,720 |
|
||||
| fenix6pro | Y | 1,310,720 |
|
||||
| fenix6spro | Y | 1,310,720 |
|
||||
| fenix6xpro | Y | 1,310,720 |
|
||||
| fr245m | Y | 1,310,720 |
|
||||
| fr745 | Y | 1,310,720 |
|
||||
| fr945 | Y | 1,310,720 |
|
||||
| fr945lte | Y | 1,310,720 |
|
||||
| marqadventurer | Y | 1,310,720 |
|
||||
| marqathlete | Y | 1,310,720 |
|
||||
| marqaviator | Y | 1,310,720 |
|
||||
| marqcaptain | Y | 1,310,720 |
|
||||
| marqcommander | Y | 1,310,720 |
|
||||
| marqdriver | Y | 1,310,720 |
|
||||
| marqexpedition | Y | 1,310,720 |
|
||||
| marqgolfer | Y | 1,310,720 |
|
||||
| gpsmap66 | Y | 2,359,296 |
|
||||
| gpsmap67 | Y | 2,359,296 |
|
||||
| gpsmap86 | N | 2,359,296 |
|
||||
| gpsmaph1 | Y | 2,359,296 |
|
||||
| montana7xx | Y | 2,359,296 |
|
||||
| oregon7xx | N | 2,359,296 |
|
||||
| rino7xx | N | 2,359,296 |
|
||||
94
README.md
94
README.md
@@ -81,7 +81,7 @@ Example schema:
|
||||
"name": "Food is Ready!",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "script.turn_on",
|
||||
"action": "script.turn_on",
|
||||
"confirm": true
|
||||
}
|
||||
},
|
||||
@@ -132,7 +132,7 @@ Example schema:
|
||||
"name": "Turn off USBs",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "automation.trigger"
|
||||
"action": "automation.trigger"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -140,7 +140,7 @@ Example schema:
|
||||
"name": "TV Lights Scene",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "scene.turn_on",
|
||||
"action": "scene.turn_on",
|
||||
"pin": true
|
||||
}
|
||||
},
|
||||
@@ -150,13 +150,13 @@ Example schema:
|
||||
"type": "numeric",
|
||||
"entity": "climate.room",
|
||||
"tap_action": {
|
||||
"service": "climate.set_temperature",
|
||||
"action": "climate.set_temperature",
|
||||
"picker": {
|
||||
"step": 0.5,
|
||||
"start": 10,
|
||||
"stop": 30,
|
||||
"attribute": "temperature",
|
||||
"data_attribute": "temperature"
|
||||
"step": 0.5,
|
||||
"start": 10,
|
||||
"stop": 30,
|
||||
"attribute": "temperature",
|
||||
"data_attribute": "temperature"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,7 @@ The example above illustrates how to configure:
|
||||
* Lights or switches (`toggle`), <img src="images/toggle_icon.png" height="20">
|
||||
* Enables for automations (`toggle`), <img src="images/toggle_icon.png" height="20">
|
||||
* Script invocation (`tap`)
|
||||
* Service invocation, e.g. Scene setting, (`tap`)
|
||||
* Action 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. 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
|
||||
@@ -192,17 +192,26 @@ The following table indicates how HomeAssistant entity types can map to the Garm
|
||||
| Thermostat | ❌ | ❌ | ✅ | ✅ |
|
||||
| Amplifier | ❌ | ❌ | ✅ | ✅ |
|
||||
| Any other entity | ❌ | ❌ | ✅ | ❌ |
|
||||
| Any service | ✅ | ❌ | ❌ | ❌ |
|
||||
| Any action | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
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).
|
||||
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 `action` 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 ;-) ?
|
||||
|
||||
The [schema](https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json) is checked by using a URL directly back to this GitHub source repository, so you do not need to install that file. You can just copy & paste your entity names from the YAML configuration files used to configure HomeAssistant. With a submenu, there's a difference between `title` and `name`. The `name` goes on the menu item, and the `title` at the head of the submenu. If your dashboard definition fails to meet the schema, the application will simply drop items with the wrong field names without warning to protect itself.
|
||||
|
||||
### Old deprecated format
|
||||
### Old Deprecated Formats
|
||||
|
||||
Version 1.5 brought in a change to the JSON schema so the following old format remains useable but is no longer favoured. The schema now marks it as 'deprecated' to nudge people over.
|
||||
There are two reasons for the changes to the schema:
|
||||
|
||||
1. HomeAssistant made changes we feel we should track for consistency.
|
||||
2. Retrospectively we decided there was a better way, just like HomeAssistant did. For these changes we apologise.
|
||||
|
||||
#### Service Field
|
||||
|
||||
Version 1.5 brought in a change to the JSON schema so the following old format remains useable but is no longer favoured.
|
||||
|
||||
> [!IMPORTANT] Deprecated:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -213,7 +222,9 @@ Version 1.5 brought in a change to the JSON schema so the following old format r
|
||||
}
|
||||
```
|
||||
|
||||
The above should be replaced by the following:
|
||||
Version 3.6 brought another change to the JSON schema to follow HomeAssistant's renaming of `service` to `action`.
|
||||
|
||||
> [!IMPORTANT] Deprecated:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -226,7 +237,54 @@ The above should be replaced by the following:
|
||||
}
|
||||
```
|
||||
|
||||
This allows the `confirm` and `pin` fields to be accommodated in the `tap_action` along side the `service` tag, and follows the HomeAssistant YAML format more closely.
|
||||
The above should be replaced by the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"entity": "scene.tv_light",
|
||||
"name": "TV Lights Scene",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"action": "scene.turn_on"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows the `confirm` and `pin` fields to be accommodated in the `tap_action` along side the `action` tag, and follows the HomeAssistant YAML format more closely.
|
||||
|
||||
#### Exit Field
|
||||
|
||||
Version 2.31 added an "exit on tap" feature. In retrospect this field should have been nested inside the `tap_action` object.
|
||||
|
||||
> [!IMPORTANT] Deprecated:
|
||||
|
||||
```json
|
||||
{
|
||||
"entity": "automation.turn_off_stuff",
|
||||
"name": "Turn off Stuff",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"action": "automation.trigger"
|
||||
},
|
||||
"exit": true
|
||||
}
|
||||
```
|
||||
|
||||
The above should be replaced by the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"entity": "automation.turn_off_stuff",
|
||||
"name": "Turn off Stuff",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"action": "automation.trigger",
|
||||
"exit": true
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
A future move to v3.x will remove support for all deprecated JSON elements to simplify code. **Please ensure you track the schema changes in readiness.**
|
||||
|
||||
### More Examples
|
||||
|
||||
@@ -238,7 +296,7 @@ This allows the `confirm` and `pin` fields to be accommodated in the `tap_action
|
||||
## Editing the JSON file
|
||||
|
||||
You have options. The first is what we use.
|
||||
1. **Best!** Use the GarminHomeAssistant [Web-based Editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) which includes `entity` and `service` name completion and validation by fetching data from your own HomeAssistant instance. _Pretty nifty eh?_ The other method listed below do not add this convenience and checking.
|
||||
1. **Best!** Use the GarminHomeAssistant [Web-based Editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) which includes `entity` and `action` name completion and validation by fetching data from your own HomeAssistant instance. _Pretty nifty eh?_ The other method listed below do not add this convenience and checking.
|
||||
2. Use the [Studio Code Server](https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863) addon for HomeAssistant. You can then edit your JSON file in place.
|
||||
3. Locally installed VSCode, or if not installed, try
|
||||
4. The on-line version at https://vscode.dev/, which works really well.
|
||||
@@ -385,6 +443,8 @@ Check the latest unresolved [issues](https://github.com/house-of-abbey/GarminHom
|
||||
|
||||
9. When using Wi-Fi or LTE to toggle a light, the `toggle` will fail when the default or current state of the application's menu does not match the state of the light. The same applies to a cover or other thing that can be toggled. This is because the application is unable to initialise the menu with the current state without Bluetooth. Hence the Wi-Fi/LTE functionality is best used with `tap` items only.
|
||||
|
||||
10. There are memory limits, particularly for older devices. Please see the [explanation of the memory limits](Devices.md) and device support.
|
||||
|
||||
# Authors & Contributors
|
||||
|
||||
For an up to date list of all authors and contributors, please check the [contributor's page](https://github.com/house-of-abbey/GarminHomeAssistant/graphs/contributors). Thank you all for improving this application.
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
"$ref": "#/$defs/enabled"
|
||||
},
|
||||
"exit": {
|
||||
"$ref": "#/$defs/exit"
|
||||
"$ref": "#/$defs/exit",
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -58,6 +59,7 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"template": {
|
||||
"deprecated": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
@@ -112,26 +114,14 @@
|
||||
"description": "Use 'info' or 'tap' instead."
|
||||
},
|
||||
"tap_action": {
|
||||
"$ref": "#/$defs/tap_action",
|
||||
"properties": {
|
||||
"service": {
|
||||
"$ref": "#/$defs/service"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"title": "Your services's parameters",
|
||||
"description": "The object containing the parameters and their values to be passed to the entity. No schema checking can be done here, you are on your own! On application crash, remove the parameters."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"service"
|
||||
]
|
||||
"$ref": "#/$defs/tap_action_tap"
|
||||
},
|
||||
"enabled": {
|
||||
"$ref": "#/$defs/enabled"
|
||||
},
|
||||
"exit": {
|
||||
"$ref": "#/$defs/exit"
|
||||
"$ref": "#/$defs/exit",
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -185,32 +175,20 @@
|
||||
"$ref": "#/$defs/content"
|
||||
},
|
||||
"service": {
|
||||
"$ref": "#/$defs/service",
|
||||
"$ref": "#/$defs/action",
|
||||
"deprecated": true,
|
||||
"title": "Schema change:",
|
||||
"description": "Use 'tap_action' instead to mirror Home Assistant."
|
||||
},
|
||||
"tap_action": {
|
||||
"$ref": "#/$defs/tap_action",
|
||||
"properties": {
|
||||
"service": {
|
||||
"$ref": "#/$defs/service"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"title": "Your services's parameters",
|
||||
"description": "The object containing the parameters and their values to be passed to the entity. No schema checking can be done here, you are on your own! On application crash, remove the parameters."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"service"
|
||||
]
|
||||
"$ref": "#/$defs/tap_action_tap"
|
||||
},
|
||||
"enabled": {
|
||||
"$ref": "#/$defs/enabled"
|
||||
},
|
||||
"exit": {
|
||||
"$ref": "#/$defs/exit"
|
||||
"$ref": "#/$defs/exit",
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -277,8 +255,8 @@
|
||||
"tap_action": {
|
||||
"$ref": "#/$defs/tap_action",
|
||||
"properties": {
|
||||
"service": {
|
||||
"$ref": "#/$defs/service"
|
||||
"action": {
|
||||
"$ref": "#/$defs/action"
|
||||
},
|
||||
"picker": {
|
||||
"type": "object",
|
||||
@@ -304,8 +282,8 @@
|
||||
},
|
||||
"data_attribute": {
|
||||
"type": "string",
|
||||
"title": "Attribute on the service data",
|
||||
"description": "Attribute on the service data for the value to set."
|
||||
"title": "Attribute on the action data",
|
||||
"description": "Attribute on the action data for the value to set."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -317,7 +295,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"service",
|
||||
"action",
|
||||
"picker"
|
||||
]
|
||||
},
|
||||
@@ -356,7 +334,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "light.turn_on"
|
||||
},
|
||||
"picker": {
|
||||
@@ -368,10 +346,12 @@
|
||||
"const": "brightness"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 255,
|
||||
"type": "integer",
|
||||
"max": 255,
|
||||
"description": "Lights are not a percentage."
|
||||
}
|
||||
}
|
||||
@@ -392,7 +372,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "input_number.set_value"
|
||||
},
|
||||
"picker": {
|
||||
@@ -423,7 +403,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "number.set_value"
|
||||
},
|
||||
"picker": {
|
||||
@@ -454,7 +434,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "fan.set_percentage"
|
||||
},
|
||||
"picker": {
|
||||
@@ -466,10 +446,12 @@
|
||||
"const": "percentage"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
"type": "integer",
|
||||
"maximum": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,7 +471,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "valve.set_valve_position"
|
||||
},
|
||||
"picker": {
|
||||
@@ -501,10 +483,12 @@
|
||||
"const": "position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
"type": "integer",
|
||||
"maximum": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -526,7 +510,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"enum": [
|
||||
"cover.set_position",
|
||||
"cover.set_tilt_position"
|
||||
@@ -541,7 +525,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "cover.set_tilt_position"
|
||||
}
|
||||
}
|
||||
@@ -552,7 +536,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "cover.set_tilt_position"
|
||||
},
|
||||
"picker": {
|
||||
@@ -564,10 +548,12 @@
|
||||
"const": "tilt_position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
"type": "integer",
|
||||
"maximum": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -579,7 +565,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "cover.set_position"
|
||||
},
|
||||
"picker": {
|
||||
@@ -591,10 +577,12 @@
|
||||
"const": "position"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 100
|
||||
"type": "integer",
|
||||
"maximum": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -617,7 +605,7 @@
|
||||
"properties": {
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "media_player.volume_set"
|
||||
},
|
||||
"picker": {
|
||||
@@ -629,10 +617,13 @@
|
||||
"const": "volume_level"
|
||||
},
|
||||
"min": {
|
||||
"const": 0
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"max": {
|
||||
"const": 1
|
||||
"type": "number",
|
||||
"maximum": 1,
|
||||
"description": "Fraction [0,1], not percentage."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -652,7 +643,7 @@
|
||||
"tap_action": {
|
||||
"properties": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "climate.set_temperature"
|
||||
},
|
||||
"picker": {
|
||||
@@ -686,7 +677,7 @@
|
||||
},
|
||||
"type": {
|
||||
"title": "Menu item type",
|
||||
"description": "One of 'info', 'tap', 'toggle' or 'group'."
|
||||
"description": "One of 'info', 'tap', 'toggle', 'group' or 'numeric'."
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
@@ -696,7 +687,7 @@
|
||||
"type": "tap",
|
||||
"name": "Example",
|
||||
"tap_action": {
|
||||
"service": "notify.notify",
|
||||
"action": "notify.notify",
|
||||
"data": {
|
||||
"message": "Example"
|
||||
}
|
||||
@@ -718,7 +709,7 @@
|
||||
"name": "Example",
|
||||
"entity": "light.example",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"picker": {
|
||||
"attribute": "brightness",
|
||||
"data_attribute": "brightness",
|
||||
@@ -733,7 +724,7 @@
|
||||
"name": "Example",
|
||||
"entity": "input_number.example",
|
||||
"tap_action": {
|
||||
"service": "input_number.set_value",
|
||||
"action": "input_number.set_value",
|
||||
"picker": {
|
||||
"data_attribute": "value",
|
||||
"min": 0,
|
||||
@@ -747,7 +738,7 @@
|
||||
"name": "Example",
|
||||
"entity": "number.example",
|
||||
"tap_action": {
|
||||
"service": "number.set_value",
|
||||
"action": "number.set_value",
|
||||
"picker": {
|
||||
"data_attribute": "value",
|
||||
"min": 0,
|
||||
@@ -761,14 +752,13 @@
|
||||
"name": "Example",
|
||||
"entity": "fan.example",
|
||||
"tap_action": {
|
||||
"service": "fan.set_percentage",
|
||||
"action": "fan.set_percentage",
|
||||
"picker": {
|
||||
"attribute": "percentage",
|
||||
"data_attribute": "percentage",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1,
|
||||
"display_format": "%.0f%%"
|
||||
"step": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -777,7 +767,7 @@
|
||||
"name": "Example",
|
||||
"entity": "valve.example",
|
||||
"tap_action": {
|
||||
"service": "valve.set_valve_position",
|
||||
"action": "valve.set_valve_position",
|
||||
"picker": {
|
||||
"attribute": "position",
|
||||
"data_attribute": "position",
|
||||
@@ -792,7 +782,7 @@
|
||||
"name": "Example",
|
||||
"entity": "cover.example",
|
||||
"tap_action": {
|
||||
"service": "cover.set_position",
|
||||
"action": "cover.set_position",
|
||||
"picker": {
|
||||
"attribute": "position",
|
||||
"data_attribute": "position",
|
||||
@@ -807,7 +797,7 @@
|
||||
"name": "Example",
|
||||
"entity": "cover.example",
|
||||
"tap_action": {
|
||||
"service": "cover.set_tilt_position",
|
||||
"action": "cover.set_tilt_position",
|
||||
"picker": {
|
||||
"attribute": "tilt_position",
|
||||
"data_attribute": "tilt_position",
|
||||
@@ -822,7 +812,7 @@
|
||||
"name": "Example",
|
||||
"entity": "media_player.example",
|
||||
"tap_action": {
|
||||
"service": "media_player.volume_set",
|
||||
"action": "media_player.volume_set",
|
||||
"picker": {
|
||||
"attribute": "volume_level",
|
||||
"data_attribute": "volume_level",
|
||||
@@ -837,7 +827,7 @@
|
||||
"name": "Example",
|
||||
"entity": "climate.example",
|
||||
"tap_action": {
|
||||
"service": "climate.set_temperature",
|
||||
"action": "climate.set_temperature",
|
||||
"picker": {
|
||||
"attribute": "temperature",
|
||||
"data_attribute": "temperature"
|
||||
@@ -944,14 +934,56 @@
|
||||
"title": "Home Assistant entity name",
|
||||
"pattern": "^[^.]+\\.[^.]+$"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"title": "Home Assistant service name",
|
||||
"title": "Home Assistant action name",
|
||||
"pattern": "^[^.]+\\.[^.]+$"
|
||||
},
|
||||
"tap_action_tap": {
|
||||
"allOf": [
|
||||
{
|
||||
"title": "Tap Action",
|
||||
"description": "'confirm' and 'pin' fields are optional. 'action' is required.",
|
||||
"properties": {
|
||||
"service": {
|
||||
"$ref": "#/$defs/action",
|
||||
"deprecated": true
|
||||
},
|
||||
"action": {
|
||||
"$ref": "#/$defs/action"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"title": "Your actions's parameters",
|
||||
"description": "The object containing the parameters and their values to be passed to the entity. No schema checking can be done here, you are on your own! On application crash, remove the parameters."
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [
|
||||
"action"
|
||||
],
|
||||
"not": {
|
||||
"required": [
|
||||
"service"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"service"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/tap_action"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tap_action": {
|
||||
"type": "object",
|
||||
"title": "Action",
|
||||
"title": "Tap Action",
|
||||
"description": "'confirm' and 'pin' fields are optional.",
|
||||
"properties": {
|
||||
"confirm": {
|
||||
@@ -959,16 +991,22 @@
|
||||
},
|
||||
"pin": {
|
||||
"$ref": "#/$defs/pin"
|
||||
},
|
||||
"exit": {
|
||||
"$ref": "#/$defs/exit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"title": "Home Assistant Template",
|
||||
"description": "Jinja2 template defining the text to display. Must be included in an 'info'. Optional in a 'toggle', 'tap' and 'group'. Special characters may not render in the glance context.",
|
||||
"description": "Jinja2 template defining the text to display. Must be included in an 'info'. Optional in a 'toggle', 'tap', 'numeric' and 'group'. Special characters may not render in the glance context.",
|
||||
"type": "string"
|
||||
},
|
||||
"confirm": {
|
||||
"type": ["boolean", "string"],
|
||||
"type": [
|
||||
"boolean",
|
||||
"string"
|
||||
],
|
||||
"default": false,
|
||||
"title": "Confirmation",
|
||||
"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."
|
||||
|
||||
@@ -11,7 +11,7 @@ A simple example using a scene as a `tap` menu item.
|
||||
"name": "Telly Scene",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "scene.turn_on"
|
||||
"action": "scene.turn_on"
|
||||
}
|
||||
},
|
||||
```
|
||||
@@ -70,7 +70,7 @@ Note that for notify events, you _must_ not supply an `entity_id` or the API cal
|
||||
"name": "Message",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "notify.mobile_app_on_phone",
|
||||
"action": "notify.mobile_app_on_phone",
|
||||
"data": {
|
||||
"title": "This is a title",
|
||||
"message": "This is the message"
|
||||
@@ -81,9 +81,9 @@ Note that for notify events, you _must_ not supply an `entity_id` or the API cal
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Be careful with the value of the `service` field.
|
||||
> Be careful with the value of the `action` field.
|
||||
|
||||
Note that the `service` field will need to be a locally custom `script.<something>` as soon as any `data` fields are populated and not something more generic like `script.turn_on`. If the `service` field is wrong, the application will fail with a [`Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE`](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html) error in the response from your HomeAssistant and show the error message as _"No JSON returned from HTTP request"_ on your device. In the [web-based editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) you can use the standard developer tools to observe an `HTTP 400` error which the application does not see. Here we are limited by the [Garmin Connect IQ](https://developer.garmin.com/connect-iq/overview/) software development kit (SDK). We do not have enough information at the point of execution in the application to determine the cause of the error. Nor is there an immediately obvious way of identifying this issue using the JSON schema checks.
|
||||
Note that the `action` field will need to be a locally custom `script.<something>` as soon as any `data` fields are populated and not something more generic like `script.turn_on`. If the `action` field is wrong, the application will fail with a [`Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE`](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html) error in the response from your HomeAssistant and show the error message as _"No JSON returned from HTTP request"_ on your device. In the [web-based editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) you can use the standard developer tools to observe an `HTTP 400` error which the application does not see. Here we are limited by the [Garmin Connect IQ](https://developer.garmin.com/connect-iq/overview/) software development kit (SDK). We do not have enough information at the point of execution in the application to determine the cause of the error. Nor is there an immediately obvious way of identifying this issue using the JSON schema checks.
|
||||
|
||||
## Exit on Tap
|
||||
|
||||
@@ -95,9 +95,9 @@ You can choose individual items that will quit after they have completed their a
|
||||
"name": "Turn off Stuff",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "automation.trigger"
|
||||
"action": "automation.trigger",
|
||||
"exit": true
|
||||
},
|
||||
"exit": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -111,7 +111,7 @@ If you would like to temporarily disable an item in your menu, e.g. for seasonal
|
||||
"name": "Turn off Stuff",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "automation.trigger"
|
||||
"action": "automation.trigger"
|
||||
},
|
||||
"enabled": false
|
||||
}
|
||||
|
||||
@@ -75,6 +75,6 @@ The following shows the default glance when the menu file is not available at th
|
||||
|
||||
<img src="../images/Venu2_glance_no_menu.png" width="200" title="Venu 2 Glance showing errors"/>
|
||||
|
||||
Once the custom glance template has been retrieved and evaluated the display will change. Should the connectivity to your Home Assistant then be lost, e.g. you move out of range of your phone, the glance reflects this in the colour of the residual two rectangles. The top one remains an indicator for the API, and the bottom rectangle remains an indicator for the menu availability, reflecting the original placement in the default glance view that has now been replaced.
|
||||
Once the custom glance template has been retrieved and evaluated the display will change. Should the connectivity to your HomeAssistant then be lost, e.g. you move out of range of your phone, the glance reflects this in the colour of the residual two rectangles. The top one remains an indicator for the API, and the bottom rectangle remains an indicator for the menu availability, reflecting the original placement in the default glance view that has now been replaced.
|
||||
|
||||
<img src="../images/Venu2_glance_no_bt.png" width="200" title="Venu 2 Glance showing lost connectivity"/>
|
||||
|
||||
@@ -41,9 +41,9 @@ It may well be the case that often `attribute` and `data_attribute` are the same
|
||||
|
||||
## Helper
|
||||
|
||||
You might define a "helper" entity as follows in Home Assistant:
|
||||
You might define a "helper" entity as follows in HomeAssistant:
|
||||
|
||||
<img src="../images/my_float.png" width="400" title="Home Assistant Helper definition for an 'input_number'." style="margin:5px"/>
|
||||
<img src="../images/my_float.png" width="400" title="HomeAssistant 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.
|
||||
|
||||
@@ -88,9 +88,9 @@ The complication here is this amplifier uses one scale for changing the value, a
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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 HomeAssistant 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"/>
|
||||
<img src="../images/template_number.png" width="500" title="HomeAssistant Helper definition for an 'input_number'." style="margin:5px"/>
|
||||
|
||||
For copy and paste, the Jinja2 fields are as follows:
|
||||
|
||||
@@ -112,7 +112,7 @@ For copy and paste, the Jinja2 fields are as follows:
|
||||
{{ 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`:
|
||||
As an alternative to using the GUI, the following can be pasted into HomeAssistant's `configuration.yaml`:
|
||||
|
||||
```yaml
|
||||
template:
|
||||
@@ -160,4 +160,4 @@ The JSON menu definition can now use dB with the new template number as follows.
|
||||
|
||||
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. 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 HomeAssistant server.
|
||||
|
||||
@@ -48,12 +48,12 @@ switch:
|
||||
friendly_name: <name>
|
||||
value_template: <value>
|
||||
turn_on:
|
||||
service: <service>
|
||||
action: <action>
|
||||
data:
|
||||
entity_id: <entity>
|
||||
<attribute>: <value>
|
||||
turn_off:
|
||||
service: <service>
|
||||
action: <action>
|
||||
data:
|
||||
entity_id: <entity>
|
||||
<attribute>: <value>
|
||||
@@ -90,11 +90,11 @@ switch:
|
||||
friendly_name: Cover
|
||||
value_template: "{{ is_state('cover.cover', 'open') }}"
|
||||
turn_on:
|
||||
service: cover.open_cover
|
||||
action: cover.open_cover
|
||||
data:
|
||||
entity_id: cover.cover
|
||||
turn_off:
|
||||
service: cover.close_cover
|
||||
action: cover.close_cover
|
||||
data:
|
||||
entity_id: cover.cover
|
||||
```
|
||||
@@ -118,7 +118,9 @@ You can choose individual items that will quit after they have completed their a
|
||||
"entity": "light.hall_light",
|
||||
"name": "Hall Light & Quit",
|
||||
"type": "toggle",
|
||||
"exit": true
|
||||
"tap_action" {
|
||||
"exit": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ Note: Only when you use the `tap_action` field do you also need to include the `
|
||||
"type": "tap",
|
||||
"content": "{% if is_state('binary_sensor.garage_connected', 'on') %}{{state_translated('cover.garage_door')}} - {{state_attr('cover.garage_door', 'current_position')}}%{%else%}Unconnected{% endif %}",
|
||||
"tap_action": {
|
||||
"service": "cover.toggle",
|
||||
"action": "cover.toggle",
|
||||
"pin": true
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
||||
"type": "tap",
|
||||
"content": "{% if not (is_state('light.green_house', 'off') or is_state('light.green_house', 'unavailable')) %}{{ (((state_attr('light.green_house', 'brightness') | float) / 255 * 100) | round(0)) | int }}%{% else %}Off{% endif %}",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"data": {
|
||||
"brightness_pct": 12
|
||||
}
|
||||
@@ -184,7 +184,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
||||
"name": "LEDs 1",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"data": {
|
||||
"brightness_pct": 37
|
||||
}
|
||||
@@ -196,7 +196,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
||||
"type": "tap",
|
||||
"content": "{% if not (is_state('light.green_house', 'off') or is_state('light.green_house', 'unavailable')) %}{{ (((state_attr('light.green_house', 'brightness') | float) / 255 * 100) | round(0)) | int }}%{% else %}Off{% endif %}",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"data": {
|
||||
"brightness_pct": 62
|
||||
}
|
||||
@@ -208,7 +208,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
||||
"type": "tap",
|
||||
"content": "{% if not (is_state('light.green_house', 'off') or is_state('light.green_house', 'unavailable')) %}{{ (((state_attr('light.green_house', 'brightness') | float) / 255 * 100) | round(0))| int }}%{% else %}Off{% endif %}",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"data": {
|
||||
"brightness_pct": 87
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ class HomeAssistantMenuItemFactory {
|
||||
//! @param label Menu item label.
|
||||
//! @param entity_id Home Assistant Entity ID (optional)
|
||||
//! @param template Template for Home Assistant to render (optional)
|
||||
//! @param service Template for Home Assistant to render (optional)
|
||||
//! @param action Action to run on Home Assistant (optional)
|
||||
//! @param data Sourced from the menu JSON, this is the `data` field from the `tap_action` field.
|
||||
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||
//
|
||||
@@ -105,7 +105,7 @@ class HomeAssistantMenuItemFactory {
|
||||
label as Lang.String or Lang.Symbol,
|
||||
entity_id as Lang.String?,
|
||||
template as Lang.String?,
|
||||
service as Lang.String?,
|
||||
action as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
options as {
|
||||
:exit as Lang.Boolean,
|
||||
@@ -124,12 +124,12 @@ class HomeAssistantMenuItemFactory {
|
||||
for (var i = 0; i < keys.size(); i++) {
|
||||
options[keys[i]] = mMenuItemOptions.get(keys[i]);
|
||||
}
|
||||
if (service != null) {
|
||||
options[:icon] = mTapTypeIcon;
|
||||
if (action != null) {
|
||||
options.put(:icon, mTapTypeIcon);
|
||||
return new HomeAssistantTapMenuItem(
|
||||
label,
|
||||
template,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
options,
|
||||
mHomeAssistantService
|
||||
@@ -155,7 +155,7 @@ class HomeAssistantMenuItemFactory {
|
||||
label as Lang.String or Lang.Symbol,
|
||||
entity_id as Lang.String?,
|
||||
template as Lang.String?,
|
||||
service as Lang.String?,
|
||||
action as Lang.String?,
|
||||
picker as Lang.Dictionary,
|
||||
options as {
|
||||
:exit as Lang.Boolean,
|
||||
@@ -176,7 +176,7 @@ class HomeAssistantMenuItemFactory {
|
||||
return new HomeAssistantNumericMenuItem(
|
||||
label,
|
||||
template,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
picker,
|
||||
options,
|
||||
|
||||
@@ -22,7 +22,7 @@ using Toybox.Graphics;
|
||||
//
|
||||
class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
private var mHomeAssistantService as HomeAssistantService?;
|
||||
private var mService as Lang.String?;
|
||||
private var mAction as Lang.String?;
|
||||
private var mConfirm as Lang.Boolean or Lang.String or Null;
|
||||
private var mExit as Lang.Boolean;
|
||||
private var mPin as Lang.Boolean;
|
||||
@@ -35,11 +35,11 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param template Menu item template.
|
||||
//! @param service Menu item service.
|
||||
//! @param data Data to supply to the service call.
|
||||
//! @param exit Should the service call complete and then exit?
|
||||
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the service call be protected with a PIN for some low level of security?
|
||||
//! @param action Menu item action.
|
||||
//! @param data Data to supply to the action call.
|
||||
//! @param exit Should the action call complete and then exit?
|
||||
//! @param confirm Should the action call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the action call be protected with a PIN for some low level of security?
|
||||
//! @param icon Icon to use for the menu item.
|
||||
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||
//! @param haService Shared Home Assistant service object that will perform the required call. Only
|
||||
@@ -48,7 +48,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
function initialize(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
template as Lang.String,
|
||||
service as Lang.String?,
|
||||
action as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
picker as Lang.Dictionary,
|
||||
options as {
|
||||
@@ -60,7 +60,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
}?,
|
||||
haService as HomeAssistantService
|
||||
) {
|
||||
mService = service;
|
||||
mAction = action;
|
||||
mData = data;
|
||||
mPicker = picker;
|
||||
mExit = options[:exit];
|
||||
@@ -93,7 +93,8 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
function callService() as Void {
|
||||
|
||||
function callAction() as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
var pin = Settings.getPin();
|
||||
@@ -119,8 +120,8 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
WatchUi.pushView(
|
||||
dialog,
|
||||
new WifiLteExecutionConfirmDelegate({
|
||||
:type => "service",
|
||||
:service => mService,
|
||||
:type => "action",
|
||||
:action => mAction,
|
||||
:data => mData,
|
||||
:exit => mExit,
|
||||
}, dialog),
|
||||
@@ -155,18 +156,18 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
function onConfirm(b as Lang.Boolean) as Void {
|
||||
var dataAttribute = mPicker["data_attribute"];
|
||||
if (dataAttribute == null) {
|
||||
//return without call service if no data attribute is set to avoid crash
|
||||
//return without call action 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
|
||||
//return without call action if no entity_id is set to avoid crash
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
return;
|
||||
}
|
||||
mHomeAssistantService.call(
|
||||
mService,
|
||||
mAction,
|
||||
{
|
||||
"entity_id" => entity_id.toString(),
|
||||
dataAttribute.toString() => mValue
|
||||
@@ -218,7 +219,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
WatchUi.requestUpdate();
|
||||
}
|
||||
|
||||
//! Set the Picker's value. Needed to set new value via the Service call
|
||||
//! Set the Picker's value. Needed to set new value via the Action call
|
||||
//!
|
||||
//! @param value New value to set.
|
||||
//
|
||||
@@ -228,7 +229,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
|
||||
//! Get the Picker's value.
|
||||
//!
|
||||
//! Needed to set new value via the Service call
|
||||
//! Needed to set new value via the Action call
|
||||
//
|
||||
public function getValue() as Lang.Number or Lang.Float {
|
||||
return mValue;
|
||||
|
||||
@@ -61,7 +61,7 @@ class HomeAssistantNumericPicker extends WatchUi.Picker {
|
||||
//
|
||||
public function onConfirm(value as Lang.Number or Lang.Float) as Void {
|
||||
mItem.setValue(value);
|
||||
mItem.callService();
|
||||
mItem.callAction();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class HomeAssistantService {
|
||||
}
|
||||
}
|
||||
|
||||
//! Callback function after completing the POST request to call a service.
|
||||
//! Callback function after completing the POST request to call an action.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
@@ -87,7 +87,7 @@ class HomeAssistantService {
|
||||
break;
|
||||
|
||||
case 200:
|
||||
// System.println("HomeAssistantService onReturnCall(): Service executed.");
|
||||
// System.println("HomeAssistantService onReturnCall(): Action executed.");
|
||||
getApp().forceStatusUpdates();
|
||||
var d = data as Lang.Array;
|
||||
var toast = WatchUi.loadResource($.Rez.Strings.Executed) as Lang.String;
|
||||
@@ -118,13 +118,13 @@ class HomeAssistantService {
|
||||
}
|
||||
}
|
||||
|
||||
//! Invoke a service call for a menu item.
|
||||
//! Invoke a action call for a menu item.
|
||||
//!
|
||||
//! @param service The Home Assistant service to be run, e.g. from the JSON `service` field.
|
||||
//! @param data Data to be supplied to the service call.
|
||||
//! @param action The Home Assistant action to be run, e.g. from the JSON `action` field.
|
||||
//! @param data Data to be supplied to the action call.
|
||||
//
|
||||
function call(
|
||||
service as Lang.String,
|
||||
action as Lang.String,
|
||||
data as Lang.Dictionary?,
|
||||
exit as Lang.Boolean
|
||||
) as Void {
|
||||
@@ -136,8 +136,8 @@ class HomeAssistantService {
|
||||
WatchUi.pushView(
|
||||
dialog,
|
||||
new WifiLteExecutionConfirmDelegate({
|
||||
:type => "service",
|
||||
:service => service,
|
||||
:type => "action",
|
||||
:action => action,
|
||||
:data => data,
|
||||
:exit => exit,
|
||||
}, dialog),
|
||||
@@ -151,9 +151,9 @@ class HomeAssistantService {
|
||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
|
||||
} else {
|
||||
// Can't use null for substring() parameters due to API version level.
|
||||
var url = Settings.getApiUrl() + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, service.length());
|
||||
var url = Settings.getApiUrl() + "/services/" + action.substring(0, action.find(".")) + "/" + action.substring(action.find(".")+1, action.length());
|
||||
// System.println("HomeAssistantService call() URL=" + url);
|
||||
// System.println("HomeAssistantService call() service=" + service);
|
||||
// System.println("HomeAssistantService call() action=" + action);
|
||||
|
||||
var entity_id = "";
|
||||
if (data != null) {
|
||||
|
||||
@@ -50,9 +50,9 @@ class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
|
||||
var url;
|
||||
|
||||
switch (type) {
|
||||
case "service":
|
||||
var service = WifiLteExecutionConfirmDelegate.mCommandData[:service];
|
||||
url = Settings.getApiUrl() + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, service.length());
|
||||
case "action":
|
||||
var action = WifiLteExecutionConfirmDelegate.mCommandData[:action];
|
||||
url = Settings.getApiUrl() + "/services/" + action.substring(0, action.find(".")) + "/" + action.substring(action.find(".")+1, action.length());
|
||||
var entity_id = "";
|
||||
if (data != null) {
|
||||
entity_id = data.get("entity_id");
|
||||
@@ -78,7 +78,7 @@ class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
|
||||
private function performRequest(url as Lang.String, data as Lang.Dictionary?) {
|
||||
Communications.makeWebRequest(
|
||||
url,
|
||||
data, // May include {"entity_id": xxxx} for service calls
|
||||
data, // May include {"entity_id": xxxx} for action calls
|
||||
{
|
||||
:method => Communications.HTTP_REQUEST_METHOD_POST,
|
||||
:headers => Settings.augmentHttpHeaders({
|
||||
|
||||
@@ -17,11 +17,11 @@ using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
using Toybox.Graphics;
|
||||
|
||||
//! Menu button that triggers a service.
|
||||
//! Menu button that triggers an action.
|
||||
//
|
||||
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
private var mHomeAssistantService as HomeAssistantService;
|
||||
private var mService as Lang.String?;
|
||||
private var mAction as Lang.String?;
|
||||
private var mConfirm as Lang.Boolean or Lang.String or Null;
|
||||
private var mExit as Lang.Boolean;
|
||||
private var mPin as Lang.Boolean;
|
||||
@@ -31,11 +31,11 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param template Menu item template.
|
||||
//! @param service Menu item service.
|
||||
//! @param data Data to supply to the service call.
|
||||
//! @param exit Should the service call complete and then exit?
|
||||
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the service call be protected with a PIN for some low level of security?
|
||||
//! @param action Menu item action.
|
||||
//! @param data Data to supply to the action call.
|
||||
//! @param exit Should the action call complete and then exit?
|
||||
//! @param confirm Should the action call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the action call be protected with a PIN for some low level of security?
|
||||
//! @param icon Icon to use for the menu item.
|
||||
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||
//! @param haService Shared Home Assistant service object that will perform the required call. Only
|
||||
@@ -44,7 +44,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
function initialize(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
template as Lang.String,
|
||||
service as Lang.String?,
|
||||
action as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
options as {
|
||||
:alignment as WatchUi.MenuItem.Alignment,
|
||||
@@ -65,16 +65,16 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
);
|
||||
|
||||
mHomeAssistantService = haService;
|
||||
mService = service;
|
||||
mAction = action;
|
||||
mData = data;
|
||||
mExit = options[:exit];
|
||||
mConfirm = options[:confirm];
|
||||
mPin = options[:pin];
|
||||
}
|
||||
|
||||
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
|
||||
//! Call a Home Assistant action only after checks have been done for confirmation or PIN entry.
|
||||
//
|
||||
function callService() as Void {
|
||||
function callAction() as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
var pin = Settings.getPin();
|
||||
@@ -99,10 +99,10 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
WatchUi.pushView(
|
||||
dialog,
|
||||
new WifiLteExecutionConfirmDelegate({
|
||||
:type => "service",
|
||||
:service => mService,
|
||||
:data => mData,
|
||||
:exit => mExit,
|
||||
:type => "action",
|
||||
:action => mAction,
|
||||
:data => mData,
|
||||
:exit => mExit,
|
||||
}, dialog),
|
||||
WatchUi.SLIDE_LEFT
|
||||
);
|
||||
@@ -133,8 +133,8 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
|
||||
//
|
||||
public function onConfirm(b as Lang.Boolean) as Void {
|
||||
if (mService != null) {
|
||||
mHomeAssistantService.call(mService, mData, mExit);
|
||||
if (mAction != null) {
|
||||
mHomeAssistantService.call(mAction, mData, mExit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param template Menu item template.
|
||||
//! @param data Data to supply to the service call.
|
||||
//! @param data Data to supply to the action call.
|
||||
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||
//
|
||||
function initialize(
|
||||
@@ -220,7 +220,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
//! Handles the response from a Home Assistant service or state call and updates the toggle UI.
|
||||
//! Handles the response from a Home Assistant action or state call and updates the toggle UI.
|
||||
//!
|
||||
//! @param data An array of dictionaries, each representing a Home Assistant entity state.
|
||||
//
|
||||
@@ -293,9 +293,9 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
|
||||
//! Call a Home Assistant action only after checks have been done for confirmation or PIN entry.
|
||||
//
|
||||
function callService(b as Lang.Boolean) as Void {
|
||||
function callAction(b as Lang.Boolean) as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
// Undo the toggle
|
||||
@@ -354,7 +354,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
setState(b);
|
||||
}
|
||||
|
||||
//! Displays a confirmation dialog before executing a service call via Wi-Fi/LTE.
|
||||
//! Displays a confirmation dialog before executing an action call via Wi-Fi/LTE.
|
||||
//!
|
||||
//! @param s Desired state: `true` to turn on, `false` to turn off.
|
||||
//
|
||||
@@ -382,7 +382,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
//! @param id The entity ID, e.g., `"switch.kitchen"`.
|
||||
//! @param s Desired state: `true` for "turn_on", `false` for "turn_off".
|
||||
//!
|
||||
//! @return Full service URL string.
|
||||
//! @return Full action URL string.
|
||||
//
|
||||
private static function getUrl(id as Lang.String, s as Lang.Boolean) as Lang.String {
|
||||
var url = Settings.getApiUrl() + "/services/";
|
||||
|
||||
@@ -47,7 +47,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
var content = items[i].get("content") as Lang.String?;
|
||||
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 action = items[i].get("service") as Lang.String?; // Deprecated schema
|
||||
var confirm = false as Lang.Boolean or Lang.String or Null;
|
||||
var pin = false as Lang.Boolean?;
|
||||
var data = null as Lang.Dictionary?;
|
||||
@@ -57,10 +57,13 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
enabled = items[i].get("enabled"); // Optional
|
||||
}
|
||||
if (items[i].get("exit") != null) {
|
||||
exit = items[i].get("exit"); // Optional
|
||||
exit = items[i].get("exit"); // Deprecated
|
||||
}
|
||||
if (tap_action != null) {
|
||||
service = tap_action.get("service");
|
||||
action = tap_action.get("service"); // Deprecated
|
||||
if (tap_action.get("action") != null) {
|
||||
action = tap_action.get("action"); // Optional
|
||||
}
|
||||
data = tap_action.get("data"); // Optional
|
||||
if (tap_action.get("confirm") != null) {
|
||||
confirm = tap_action.get("confirm"); // Optional
|
||||
@@ -68,6 +71,9 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
if (tap_action.get("pin") != null) {
|
||||
pin = tap_action.get("pin"); // Optional
|
||||
}
|
||||
if (tap_action.get("exit") != null) {
|
||||
exit = tap_action.get("exit"); // Optional
|
||||
}
|
||||
}
|
||||
if (type != null && name != null && enabled) {
|
||||
if (type.equals("toggle") && entity != null) {
|
||||
@@ -81,12 +87,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
:pin => pin
|
||||
}
|
||||
));
|
||||
} else if (type.equals("tap") && service != null) {
|
||||
} else if (type.equals("tap") && action != null) {
|
||||
addItem(HomeAssistantMenuItemFactory.create().tap(
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => exit,
|
||||
@@ -103,7 +109,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => false,
|
||||
@@ -117,7 +123,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => exit,
|
||||
@@ -126,7 +132,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
}
|
||||
));
|
||||
}
|
||||
} else if (type.equals("numeric") && service != null) {
|
||||
} else if (type.equals("numeric") && action != null) {
|
||||
if (tap_action != null) {
|
||||
var picker = tap_action.get("picker") as Lang.Dictionary?;
|
||||
if (picker != null) {
|
||||
@@ -134,7 +140,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
picker,
|
||||
{
|
||||
:exit => exit,
|
||||
@@ -150,7 +156,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => false,
|
||||
@@ -267,11 +273,11 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||
var haToggleItem = item as HomeAssistantToggleMenuItem;
|
||||
// System.println(haToggleItem.getLabel() + " " + haToggleItem.getId() + " " + haToggleItem.isEnabled());
|
||||
haToggleItem.callService(haToggleItem.isEnabled());
|
||||
haToggleItem.callAction(haToggleItem.isEnabled());
|
||||
} else if (item instanceof HomeAssistantTapMenuItem) {
|
||||
var haItem = item as HomeAssistantTapMenuItem;
|
||||
// System.println(haItem.getLabel() + " " + haItem.getId());
|
||||
haItem.callService();
|
||||
haItem.callAction();
|
||||
} else if (item instanceof HomeAssistantNumericMenuItem) {
|
||||
var haItem = item as HomeAssistantNumericMenuItem;
|
||||
// System.println(haItem.getLabel() + " " + haItem.getId());
|
||||
|
||||
@@ -25,7 +25,7 @@ using Toybox.Timer;
|
||||
class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||
public static var mCommandData as {
|
||||
:type as Lang.String,
|
||||
:service as Lang.String?,
|
||||
:action as Lang.String?,
|
||||
:data as Lang.Dictionary?,
|
||||
:url as Lang.String?,
|
||||
:id as Lang.Number?,
|
||||
@@ -40,8 +40,8 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||
//!
|
||||
//! @param options A dictionary describing the command to be executed:<br>
|
||||
//! `{`<br>
|
||||
//!   `:type: as Lang.String,` // The command type, either `"service"` or `"entity"`.<br>
|
||||
//!   `:service: as Lang.String?,` // (For type `"service"`) The Home Assistant service to call (e.g., "light.turn_on").<br>
|
||||
//!   `:type: as Lang.String,` // The command type, either `"action"` or `"entity"`.<br>
|
||||
//!   `:action: as Lang.String?,` // (For type `"action"`) The Home Assistant action to call (e.g., "light.turn_on").<br>
|
||||
//!   `:url: as Lang.Dictionary?,` // (For type `"entity"`) The full Home Assistant entity API URL.<br>
|
||||
//!   `:callback: as Lang.String?,` // (For type `"entity"`) A callback method (Method<data as Dictionary>) to handle the response.<br>
|
||||
//!   `:data: as Lang.Method?,` // (Optional) A dictionary of data to send with the request.<br>
|
||||
@@ -52,7 +52,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||
function initialize(
|
||||
cOptions as {
|
||||
:type as Lang.String,
|
||||
:service as Lang.String?,
|
||||
:action as Lang.String?,
|
||||
:data as Lang.Dictionary?,
|
||||
:url as Lang.String?,
|
||||
:callback as Lang.Method?,
|
||||
@@ -73,7 +73,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||
mConfirmationView = view;
|
||||
mCommandData = {
|
||||
:type => cOptions[:type],
|
||||
:service => cOptions[:service],
|
||||
:action => cOptions[:action],
|
||||
:data => cOptions[:data],
|
||||
:url => cOptions[:url],
|
||||
:callback => cOptions[:callback],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -7,7 +8,7 @@
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-name="vs/editor/editor.main"
|
||||
href="https://www.unpkg.com/monaco-editor@0.52.2/min/vs/editor/editor.main.css" />
|
||||
href="https://www.unpkg.com/monaco-editor@0.54.0/min/vs/editor/editor.main.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
@@ -453,7 +454,7 @@ http:
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script src="https://www.unpkg.com/monaco-editor@0.52.2/min/vs/loader.js"></script>
|
||||
<script src="https://www.unpkg.com/monaco-editor@0.54.0/min/vs/loader.js"></script>
|
||||
<script src="https://www.unpkg.com/json-ast-comments@1.1.1/lib/json.js"></script>
|
||||
<script src="https://www.unpkg.com/toastify-js@1.12.0/src/toastify.js"></script>
|
||||
<script src="https://code.iconify.design/1/1.0.6/iconify.min.js"></script>
|
||||
|
||||
68
web/main.js
68
web/main.js
@@ -101,12 +101,12 @@ async function get_areas() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all services in HomeAssistant.
|
||||
* Get all actions in HomeAssistant.
|
||||
* @returns {Promise<[string, { name: string; description: string; fields:
|
||||
* Record<string, { name: string; description: string; example: string;
|
||||
* selector: unknown; required?: boolean }> }][]>} [id, data]
|
||||
*/
|
||||
async function get_services() {
|
||||
async function get_actions() {
|
||||
try {
|
||||
const res = await fetch(api_url + '/services', {
|
||||
method: 'GET',
|
||||
@@ -122,15 +122,15 @@ async function get_services() {
|
||||
document.querySelector('#api_url').classList.remove('invalid');
|
||||
document.querySelector('#api_token').classList.remove('invalid');
|
||||
const data = await res.json();
|
||||
const services = [];
|
||||
const actions = [];
|
||||
for (const d of data) {
|
||||
for (const service in d.services) {
|
||||
services.push([`${d.domain}.${service}`, d.services[service]]);
|
||||
for (const action in d.services) {
|
||||
actions.push([`${d.domain}.${action}`, d.services[action]]);
|
||||
}
|
||||
}
|
||||
return services;
|
||||
return actions;
|
||||
} catch (e) {
|
||||
console.error('Error fetching services:', e);
|
||||
console.error('Error fetching actions:', e);
|
||||
document.querySelector('#api_url').classList.add('invalid');
|
||||
return [];
|
||||
}
|
||||
@@ -176,11 +176,11 @@ async function get_schema() {
|
||||
* @param {Record<string, string>} areas
|
||||
* @param {[string, { name: string; description: string; fields:
|
||||
* Record<string, { name: string; description: string; example: string;
|
||||
* selector: unknown; required?: boolean }> }][]} services
|
||||
* selector: unknown; required?: boolean }> }][]} actions
|
||||
* @param {{}} schema
|
||||
* @returns {Promise<{}>}
|
||||
*/
|
||||
async function generate_schema(entities, devices, areas, services, schema) {
|
||||
async function generate_schema(entities, devices, areas, actions, schema) {
|
||||
schema.$defs.entity = {
|
||||
enum: Object.keys(entities),
|
||||
};
|
||||
@@ -192,12 +192,18 @@ async function generate_schema(entities, devices, areas, services, schema) {
|
||||
};
|
||||
|
||||
const oneOf = [];
|
||||
for (const [id, data] of services) {
|
||||
for (const [id, data] of actions) {
|
||||
const i_properties = {
|
||||
action: {
|
||||
title: data.name,
|
||||
description: data.description,
|
||||
const: id,
|
||||
},
|
||||
service: {
|
||||
title: data.name,
|
||||
description: data.description,
|
||||
const: id,
|
||||
deprecated: true,
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
@@ -393,12 +399,16 @@ async function generate_schema(entities, devices, areas, services, schema) {
|
||||
properties: i_properties,
|
||||
});
|
||||
}
|
||||
schema.$defs.tap_action = {
|
||||
schema.$defs.tap_action_tap = {
|
||||
type: 'object',
|
||||
oneOf: oneOf,
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
},
|
||||
service: {
|
||||
type: 'string',
|
||||
deprecated: true,
|
||||
},
|
||||
confirm: {
|
||||
$ref: '#/$defs/confirm',
|
||||
@@ -411,8 +421,16 @@ async function generate_schema(entities, devices, areas, services, schema) {
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
anyOf: [
|
||||
{
|
||||
required: ['action'],
|
||||
},
|
||||
{
|
||||
required: ['service'],
|
||||
},
|
||||
],
|
||||
};
|
||||
delete schema.$defs.tap.properties.service;
|
||||
delete schema.$defs.tap.properties.action;
|
||||
delete schema.$schema;
|
||||
|
||||
return schema;
|
||||
@@ -450,22 +468,22 @@ let entities;
|
||||
let devices;
|
||||
/** @type {Awaited<ReturnType<typeof get_areas>>} */
|
||||
let areas;
|
||||
/** @type {Awaited<ReturnType<typeof get_services>>} */
|
||||
let services;
|
||||
/** @type {Awaited<ReturnType<typeof get_actions>>} */
|
||||
let actions;
|
||||
let schema;
|
||||
async function loadSchema() {
|
||||
[entities, devices, areas, services, schema] = await Promise.all([
|
||||
[entities, devices, areas, actions, schema] = await Promise.all([
|
||||
get_entities(),
|
||||
get_devices(),
|
||||
get_areas(),
|
||||
get_services(),
|
||||
get_actions(),
|
||||
get_schema(),
|
||||
]);
|
||||
if (window.makeMarkers) {
|
||||
window.makeMarkers();
|
||||
}
|
||||
try {
|
||||
schema = await generate_schema(entities, devices, areas, services, schema);
|
||||
schema = await generate_schema(entities, devices, areas, actions, schema);
|
||||
} catch {}
|
||||
console.log(schema);
|
||||
if (window.m && window.modelUri) {
|
||||
@@ -811,14 +829,16 @@ require(['vs/editor/editor.main'], async () => {
|
||||
|
||||
const runAction = editor.addCommand(
|
||||
0,
|
||||
async function (_, action) {
|
||||
const service = action.tap_action.service.split('.');
|
||||
let data = action.tap_action.data;
|
||||
async function (_, tap) {
|
||||
const action = (tap.tap_action.action ?? tap.tap_action.service).split(
|
||||
'.'
|
||||
);
|
||||
let data = tap.tap_action.data;
|
||||
if (data) {
|
||||
data.entity_id = action.entity;
|
||||
data.entity_id = tap.entity;
|
||||
} else {
|
||||
data = {
|
||||
entity_id: action.entity,
|
||||
entity_id: tap.entity,
|
||||
};
|
||||
}
|
||||
const t = toast({
|
||||
@@ -826,7 +846,7 @@ require(['vs/editor/editor.main'], async () => {
|
||||
});
|
||||
try {
|
||||
const res = await fetch(
|
||||
api_url + '/services/' + service[0] + '/' + service[1],
|
||||
api_url + '/services/' + action[0] + '/' + action[1],
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -1152,7 +1172,7 @@ require(['vs/editor/editor.main'], async () => {
|
||||
if (node.type === 'property') {
|
||||
if (node.key[0].value === 'tap_action') {
|
||||
const d = get(data, path);
|
||||
if (d.tap_action.service) {
|
||||
if (d.tap_action.action ?? d.tap_action.service) {
|
||||
lenses.push({
|
||||
range: {
|
||||
startLineNumber: node.key[0].range.start.line + 1,
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"@types/toastify-js": "1.12.0",
|
||||
"@vscode/webview-ui-toolkit": "1.4.0",
|
||||
"json-ast-comments": "1.1.1",
|
||||
"monaco-editor": "0.52.2",
|
||||
"monaco-editor": "0.54.2",
|
||||
"prettier": "^3.6.2",
|
||||
"serve": "^14.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user