mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-11-13 12:38:15 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ab8229602 | ||
|
|
8acc450c2c | ||
|
|
1a52a942fd | ||
|
|
d8b82f23e4 | ||
|
|
4943c87edb | ||
|
|
179c4d1bc5 | ||
|
|
6822cbe434 | ||
|
|
fbeadf7ba9 | ||
|
|
b688cec8f6 | ||
|
|
4119665817 | ||
|
|
f0e263ae54 | ||
|
|
abd6552916 | ||
|
|
60f754f3e3 | ||
|
|
bc5a7d04e4 | ||
|
|
643c4aa2e5 | ||
|
|
8360a3e4a2 | ||
|
|
ad83988ade | ||
|
|
cac94fecd4 | ||
|
|
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 | ||
|
|
4fbe4135b1 | ||
|
|
edef4ef464 | ||
|
|
92e4278332 | ||
|
|
4348c899ae | ||
|
|
b34291e41f | ||
|
|
7453b40cb1 | ||
|
|
483603a44d | ||
|
|
bc3271ba63 | ||
|
|
5ccd1be4e7 | ||
|
|
7871334b4a | ||
|
|
2f3ee236e8 | ||
|
|
6609fed35d | ||
|
|
d68aecd19c | ||
|
|
9a61c9ce77 | ||
|
|
2981893af7 | ||
|
|
52e2efddd8 |
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -4,14 +4,6 @@
|
|||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
|
||||||
"name": "Python: Current File",
|
|
||||||
"type": "python",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "${file}",
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"justMyCode": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "monkeyc",
|
"type": "monkeyc",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|||||||
@@ -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
|
# Background Service
|
||||||
|
|
||||||
|
|||||||
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 |
|
||||||
@@ -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
|
# 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.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.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.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.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. [Schema update](README.md#old-deprecated-formats) to keep pace with HomeAssistant and correct a previous decision. |
|
||||||
|
|||||||
@@ -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
|
# User Specified Custom HTTP Headers
|
||||||
|
|
||||||
|
|||||||
125
README.md
125
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
|
# GarminHomeAssistant
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ Example schema:
|
|||||||
"name": "Food is Ready!",
|
"name": "Food is Ready!",
|
||||||
"type": "tap",
|
"type": "tap",
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"service": "script.turn_on",
|
"action": "script.turn_on",
|
||||||
"confirm": true
|
"confirm": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -132,7 +132,7 @@ Example schema:
|
|||||||
"name": "Turn off USBs",
|
"name": "Turn off USBs",
|
||||||
"type": "tap",
|
"type": "tap",
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"service": "automation.trigger"
|
"action": "automation.trigger"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -140,9 +140,25 @@ Example schema:
|
|||||||
"name": "TV Lights Scene",
|
"name": "TV Lights Scene",
|
||||||
"type": "tap",
|
"type": "tap",
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"service": "scene.turn_on",
|
"action": "scene.turn_on",
|
||||||
"pin": true
|
"pin": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Heating",
|
||||||
|
"content": "{{ ' %.1f' | format(state_attr('climate.room','temperature')) }}",
|
||||||
|
"type": "numeric",
|
||||||
|
"entity": "climate.room",
|
||||||
|
"tap_action": {
|
||||||
|
"action": "climate.set_temperature",
|
||||||
|
"picker": {
|
||||||
|
"step": 0.5,
|
||||||
|
"start": 10,
|
||||||
|
"stop": 30,
|
||||||
|
"attribute": "temperature",
|
||||||
|
"data_attribute": "temperature"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -155,34 +171,47 @@ The example above illustrates how to configure:
|
|||||||
* Lights or switches (`toggle`), <img src="images/toggle_icon.png" height="20">
|
* Lights or switches (`toggle`), <img src="images/toggle_icon.png" height="20">
|
||||||
* Enables for automations (`toggle`), <img src="images/toggle_icon.png" height="20">
|
* Enables for automations (`toggle`), <img src="images/toggle_icon.png" height="20">
|
||||||
* Script invocation (`tap`)
|
* Script invocation (`tap`)
|
||||||
* Service invocation, e.g. Scene setting, (`tap`)
|
* Action invocation, e.g. Scene setting, (`tap`)
|
||||||
* A sub-menu to open (`group`)
|
* 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
|
* 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).
|
* 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`.
|
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)|
|
| HA Entity Type | Tap | Toggle | Info (status)| Numeric |
|
||||||
|------------------|:---:|:------:|:------------:|
|
|------------------|:---:|:------:|:------------:|:-------:|
|
||||||
| Switch | ❌ | ✅ | ✅ |
|
| Switch | ❌ | ✅ | ✅ | ❌ |
|
||||||
| Light | ❌ | ✅ | ✅ |
|
| Switched Light | ❌ | ✅ | ✅ | ❌ |
|
||||||
| Automation | ✅ | ✅ | ❌ |
|
| Dimmer Light | ❌ | ❌ | ✅ | ✅ |
|
||||||
| Script | ✅ | ❌ | ❌ |
|
| Automation | ✅ | ✅ | ❌ | ❌ |
|
||||||
| Scene | ✅ | ❌ | ❌ |
|
| Script | ✅ | ❌ | ❌ | ❌ |
|
||||||
| Sensor | ❌ | ❌ | ✅ |
|
| Scene | ✅ | ❌ | ❌ | ❌ |
|
||||||
| Binary Sensor | ❌ | ❌ | ✅ |
|
| Sensor | ❌ | ❌ | ✅ | ❌ |
|
||||||
| Any other entity | ❌ | ❌ | ✅ |
|
| Binary Sensor | ❌ | ❌ | ✅ | ❌ |
|
||||||
| Any service | ✅ | ❌ | ❌ |
|
| Thermostat | ❌ | ❌ | ✅ | ✅ |
|
||||||
|
| Amplifier | ❌ | ❌ | ✅ | ✅ |
|
||||||
|
| Any other entity | ❌ | ❌ | ✅ | ❌ |
|
||||||
|
| 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` 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 ;-) ?
|
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.
|
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
|
```json
|
||||||
{
|
{
|
||||||
@@ -193,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
|
```json
|
||||||
{
|
{
|
||||||
@@ -206,18 +237,66 @@ 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
|
### More Examples
|
||||||
|
|
||||||
* [Switches](examples/Switches.md)
|
* [Switches](examples/Switches.md)
|
||||||
* [Actions](examples/Actions.md)
|
* [Actions](examples/Actions.md)
|
||||||
* [Templates](examples/Templates.md)
|
* [Templates](examples/Templates.md)
|
||||||
|
* [Numeric](examples/Numeric.md)
|
||||||
|
|
||||||
## Editing the JSON file
|
## Editing the JSON file
|
||||||
|
|
||||||
You have options. The first is what we use.
|
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.
|
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
|
3. Locally installed VSCode, or if not installed, try
|
||||||
4. The on-line version at https://vscode.dev/, which works really well.
|
4. The on-line version at https://vscode.dev/, which works really well.
|
||||||
@@ -364,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.
|
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
|
# 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.
|
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.
|
||||||
|
|||||||
@@ -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
|
# Troubleshooting Guides
|
||||||
|
|
||||||
@@ -315,9 +315,15 @@ JSON for copy & paste:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
When the application persists in reporting "No JSON returned from HTTP request." this might be due to a mismatch between the Webhook ID and the device settings on the HomeAssistant server. We have discovered that the Webhook ID is required for HomeAssistant API calls with templates in order to work in a non-privileged account. The application options include the ability to clear the Webhook ID in the application forcing a new one to be set up. This should prevent the above error being shown on startup.
|
When the application persists in reporting _"No JSON returned from HTTP request"_ this might be due to a mismatch between the Webhook ID and the device settings on the HomeAssistant server. We have discovered that the Webhook ID is required for HomeAssistant API calls with templates in order to work in a non-privileged account. The application options include the ability to clear the Webhook ID in the application forcing a new one to be set up. This should prevent the above error being shown on startup.
|
||||||
|
|
||||||
Look for this option in the application settings:
|

|
||||||
|
|
||||||
|
We now also have reports of an [HTTP 410](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/410) error occurring after an application update. With thanks to [@Aaroneisele55](https://github.com/Aaroneisele55) for resolving this issue also by the clearing of the Webhook ID. The cause of the problem remains unknown as updates do not generally require this correction between the Home Assistant server and the watch settings.
|
||||||
|
|
||||||
|
**Therefore, when the URL is known to work, any failure to return the JSON menu definition from an HTTPS request should try resetting the Webhook ID used with Home Assistant.**
|
||||||
|
|
||||||
|
To reset the Webhook ID look for this option in the application settings:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
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
|
# Wi-Fi & LTE
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
"$ref": "#/$defs/items"
|
"$ref": "#/$defs/items"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["title", "items"],
|
"required": [
|
||||||
|
"title",
|
||||||
|
"items"
|
||||||
|
],
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"toggle": {
|
"toggle": {
|
||||||
@@ -37,28 +40,26 @@
|
|||||||
"$ref": "#/$defs/content"
|
"$ref": "#/$defs/content"
|
||||||
},
|
},
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"type": "object",
|
"$ref": "#/$defs/tap_action",
|
||||||
"properties": {
|
|
||||||
"confirm": {
|
|
||||||
"$ref": "#/$defs/confirm"
|
|
||||||
},
|
|
||||||
"pin": {
|
|
||||||
"$ref": "#/$defs/pin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "#/$defs/enabled"
|
"$ref": "#/$defs/enabled"
|
||||||
},
|
},
|
||||||
"exit": {
|
"exit": {
|
||||||
"$ref": "#/$defs/exit"
|
"$ref": "#/$defs/exit",
|
||||||
|
"deprecated": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["entity", "name", "type"],
|
"required": [
|
||||||
|
"entity",
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"template": {
|
"template": {
|
||||||
|
"deprecated": true,
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -86,7 +87,11 @@
|
|||||||
"$ref": "#/$defs/enabled"
|
"$ref": "#/$defs/enabled"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "content", "type"],
|
"required": [
|
||||||
|
"name",
|
||||||
|
"content",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -109,16 +114,22 @@
|
|||||||
"description": "Use 'info' or 'tap' instead."
|
"description": "Use 'info' or 'tap' instead."
|
||||||
},
|
},
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"$ref": "#/$defs/tap_action"
|
"$ref": "#/$defs/tap_action_tap"
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "#/$defs/enabled"
|
"$ref": "#/$defs/enabled"
|
||||||
},
|
},
|
||||||
"exit": {
|
"exit": {
|
||||||
"$ref": "#/$defs/exit"
|
"$ref": "#/$defs/exit",
|
||||||
|
"deprecated": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "content", "type", "tap_action"],
|
"required": [
|
||||||
|
"name",
|
||||||
|
"content",
|
||||||
|
"type",
|
||||||
|
"tap_action"
|
||||||
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -140,7 +151,11 @@
|
|||||||
"$ref": "#/$defs/enabled"
|
"$ref": "#/$defs/enabled"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "content", "type"],
|
"required": [
|
||||||
|
"name",
|
||||||
|
"content",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"tap": {
|
"tap": {
|
||||||
@@ -160,22 +175,26 @@
|
|||||||
"$ref": "#/$defs/content"
|
"$ref": "#/$defs/content"
|
||||||
},
|
},
|
||||||
"service": {
|
"service": {
|
||||||
"$ref": "#/$defs/entity",
|
"$ref": "#/$defs/action",
|
||||||
"deprecated": true,
|
"deprecated": true,
|
||||||
"title": "Schema change:",
|
"title": "Schema change:",
|
||||||
"description": "Use 'tap_action' instead to mirror Home Assistant."
|
"description": "Use 'tap_action' instead to mirror Home Assistant."
|
||||||
},
|
},
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"$ref": "#/$defs/tap_action"
|
"$ref": "#/$defs/tap_action_tap"
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"$ref": "#/$defs/enabled"
|
"$ref": "#/$defs/enabled"
|
||||||
},
|
},
|
||||||
"exit": {
|
"exit": {
|
||||||
"$ref": "#/$defs/exit"
|
"$ref": "#/$defs/exit",
|
||||||
|
"deprecated": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "type"],
|
"required": [
|
||||||
|
"name",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
@@ -210,31 +229,698 @@
|
|||||||
"$ref": "#/$defs/enabled"
|
"$ref": "#/$defs/enabled"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "title", "type", "items"],
|
"required": [
|
||||||
|
"name",
|
||||||
|
"title",
|
||||||
|
"type",
|
||||||
|
"items"
|
||||||
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"numeric": {
|
||||||
|
"type": "object",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"$ref": "#/$defs/name"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/type",
|
||||||
|
"const": "numeric"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"$ref": "#/$defs/content"
|
||||||
|
},
|
||||||
|
"tap_action": {
|
||||||
|
"$ref": "#/$defs/tap_action",
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"$ref": "#/$defs/action"
|
||||||
|
},
|
||||||
|
"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 action data",
|
||||||
|
"description": "Attribute on the action data for the value to set."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"min",
|
||||||
|
"max",
|
||||||
|
"step",
|
||||||
|
"data_attribute"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"action",
|
||||||
|
"picker"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"$ref": "#/$defs/enabled"
|
||||||
|
},
|
||||||
|
"exit": {
|
||||||
|
"$ref": "#/$defs/exit"
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"$ref": "#/$defs/entity"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"type",
|
||||||
|
"entity",
|
||||||
|
"tap_action"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^(light|input_number|number|fan|valve|cover|media_player|climate)\\.[^.]+$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^light\\.[^.]+$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "light.turn_on"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"attribute": {
|
||||||
|
"const": "brightness"
|
||||||
|
},
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "brightness"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "integer",
|
||||||
|
"max": 255,
|
||||||
|
"description": "Lights are not a percentage."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^input_number\\.[^.]+$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "input_number.set_value"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"not": {
|
||||||
|
"required": [
|
||||||
|
"attribute"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^number\\.[^.]+$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "number.set_value"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"not": {
|
||||||
|
"required": [
|
||||||
|
"attribute"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^fan\\.[^.]+$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "fan.set_percentage"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"attribute": {
|
||||||
|
"const": "percentage"
|
||||||
|
},
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "percentage"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "integer",
|
||||||
|
"maximum": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^valve\\.[^.]+$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "valve.set_valve_position"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"attribute": {
|
||||||
|
"const": "position"
|
||||||
|
},
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "position"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "integer",
|
||||||
|
"maximum": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^cover\\.[^.]+$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"enum": [
|
||||||
|
"cover.set_position",
|
||||||
|
"cover.set_tilt_position"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "cover.set_tilt_position"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "cover.set_tilt_position"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"attribute": {
|
||||||
|
"const": "tilt_position"
|
||||||
|
},
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "tilt_position"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "integer",
|
||||||
|
"maximum": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "cover.set_position"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"attribute": {
|
||||||
|
"const": "position"
|
||||||
|
},
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "position"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "integer",
|
||||||
|
"maximum": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^media_player\\.[^.]+$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "media_player.volume_set"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"attribute": {
|
||||||
|
"const": "volume_level"
|
||||||
|
},
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "volume_level"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 1,
|
||||||
|
"description": "Fraction [0,1], not percentage."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"entity": {
|
||||||
|
"pattern": "^climate\\.[^.]+$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"tap_action": {
|
||||||
|
"properties": {
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"const": "climate.set_temperature"
|
||||||
|
},
|
||||||
|
"picker": {
|
||||||
|
"properties": {
|
||||||
|
"attribute": {
|
||||||
|
"const": "temperature"
|
||||||
|
},
|
||||||
|
"data_attribute": {
|
||||||
|
"const": "temperature"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"not": {
|
||||||
|
"required": [
|
||||||
|
"attribute"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Menu item type",
|
"title": "Menu item type",
|
||||||
"description": "One of 'info', 'tap', 'toggle' or 'group'."
|
"description": "One of 'info', 'tap', 'toggle', 'group' or 'numeric'."
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"oneOf": [
|
"examples": [
|
||||||
{
|
{
|
||||||
|
"type": "tap",
|
||||||
|
"name": "Example",
|
||||||
|
"tap_action": {
|
||||||
|
"action": "notify.notify",
|
||||||
|
"data": {
|
||||||
|
"message": "Example"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "toggle",
|
||||||
|
"name": "Example",
|
||||||
|
"entity": "switch.example"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "group",
|
||||||
|
"name": "Example",
|
||||||
|
"title": "Example",
|
||||||
|
"items": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "numeric",
|
||||||
|
"name": "Example",
|
||||||
|
"entity": "light.example",
|
||||||
|
"tap_action": {
|
||||||
|
"action": "light.turn_on",
|
||||||
|
"picker": {
|
||||||
|
"attribute": "brightness",
|
||||||
|
"data_attribute": "brightness",
|
||||||
|
"min": 0,
|
||||||
|
"max": 255,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "numeric",
|
||||||
|
"name": "Example",
|
||||||
|
"entity": "input_number.example",
|
||||||
|
"tap_action": {
|
||||||
|
"action": "input_number.set_value",
|
||||||
|
"picker": {
|
||||||
|
"data_attribute": "value",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "numeric",
|
||||||
|
"name": "Example",
|
||||||
|
"entity": "number.example",
|
||||||
|
"tap_action": {
|
||||||
|
"action": "number.set_value",
|
||||||
|
"picker": {
|
||||||
|
"data_attribute": "value",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "numeric",
|
||||||
|
"name": "Example",
|
||||||
|
"entity": "fan.example",
|
||||||
|
"tap_action": {
|
||||||
|
"action": "fan.set_percentage",
|
||||||
|
"picker": {
|
||||||
|
"attribute": "percentage",
|
||||||
|
"data_attribute": "percentage",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "numeric",
|
||||||
|
"name": "Example",
|
||||||
|
"entity": "valve.example",
|
||||||
|
"tap_action": {
|
||||||
|
"action": "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": {
|
||||||
|
"action": "cover.set_position",
|
||||||
|
"picker": {
|
||||||
|
"attribute": "position",
|
||||||
|
"data_attribute": "position",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "numeric",
|
||||||
|
"name": "Example",
|
||||||
|
"entity": "cover.example",
|
||||||
|
"tap_action": {
|
||||||
|
"action": "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": {
|
||||||
|
"action": "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": {
|
||||||
|
"action": "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"
|
"$ref": "#/$defs/toggle"
|
||||||
},
|
},
|
||||||
{
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
"$ref": "#/$defs/template"
|
"$ref": "#/$defs/template"
|
||||||
},
|
},
|
||||||
{
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "tap"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
"$ref": "#/$defs/tap"
|
"$ref": "#/$defs/tap"
|
||||||
},
|
},
|
||||||
{
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "info"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
"$ref": "#/$defs/info"
|
"$ref": "#/$defs/info"
|
||||||
},
|
},
|
||||||
{
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
"$ref": "#/$defs/group"
|
"$ref": "#/$defs/group"
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"$ref": "#/$defs/numeric"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -248,43 +934,82 @@
|
|||||||
"title": "Home Assistant entity name",
|
"title": "Home Assistant entity name",
|
||||||
"pattern": "^[^.]+\\.[^.]+$"
|
"pattern": "^[^.]+\\.[^.]+$"
|
||||||
},
|
},
|
||||||
"service": {
|
"action": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Home Assistant service name",
|
"title": "Home Assistant action name",
|
||||||
"pattern": "^[^.]+\\.[^.]+$"
|
"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": {
|
"tap_action": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "Action",
|
"title": "Tap Action",
|
||||||
"description": "'confirm' field is optional.",
|
"description": "'confirm' and 'pin' fields are optional.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"service": {
|
|
||||||
"$ref": "#/$defs/service"
|
|
||||||
},
|
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"$ref": "#/$defs/confirm"
|
"$ref": "#/$defs/confirm"
|
||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"$ref": "#/$defs/pin"
|
"$ref": "#/$defs/pin"
|
||||||
},
|
},
|
||||||
"data": {
|
"exit": {
|
||||||
"type": "object",
|
"$ref": "#/$defs/exit"
|
||||||
"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"]
|
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"title": "Home Assistant Template",
|
"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"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"type": "boolean",
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"string"
|
||||||
|
],
|
||||||
"default": false,
|
"default": false,
|
||||||
"title": "Confirmation",
|
"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": {
|
"pin": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -307,7 +1032,10 @@
|
|||||||
"$ref": "#/$defs/content"
|
"$ref": "#/$defs/content"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["type", "content"]
|
"required": [
|
||||||
|
"type",
|
||||||
|
"content"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -317,7 +1045,9 @@
|
|||||||
"const": "status"
|
"const": "status"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["type"]
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
# Actions
|
||||||
@@ -11,7 +11,7 @@ A simple example using a scene as a `tap` menu item.
|
|||||||
"name": "Telly Scene",
|
"name": "Telly Scene",
|
||||||
"type": "tap",
|
"type": "tap",
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"service": "scene.turn_on"
|
"action": "scene.turn_on"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
@@ -37,6 +37,14 @@ For example:
|
|||||||
"confirm": true
|
"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.**
|
**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.**
|
||||||
@@ -62,7 +70,7 @@ Note that for notify events, you _must_ not supply an `entity_id` or the API cal
|
|||||||
"name": "Message",
|
"name": "Message",
|
||||||
"type": "tap",
|
"type": "tap",
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"service": "notify.mobile_app_on_phone",
|
"action": "notify.mobile_app_on_phone",
|
||||||
"data": {
|
"data": {
|
||||||
"title": "This is a title",
|
"title": "This is a title",
|
||||||
"message": "This is the message"
|
"message": "This is the message"
|
||||||
@@ -73,9 +81,9 @@ Note that for notify events, you _must_ not supply an `entity_id` or the API cal
|
|||||||
```
|
```
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!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
|
## Exit on Tap
|
||||||
|
|
||||||
@@ -87,9 +95,9 @@ You can choose individual items that will quit after they have completed their a
|
|||||||
"name": "Turn off Stuff",
|
"name": "Turn off Stuff",
|
||||||
"type": "tap",
|
"type": "tap",
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"service": "automation.trigger"
|
"action": "automation.trigger",
|
||||||
},
|
|
||||||
"exit": true
|
"exit": true
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -103,8 +111,69 @@ If you would like to temporarily disable an item in your menu, e.g. for seasonal
|
|||||||
"name": "Turn off Stuff",
|
"name": "Turn off Stuff",
|
||||||
"type": "tap",
|
"type": "tap",
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"service": "automation.trigger"
|
"action": "automation.trigger"
|
||||||
},
|
},
|
||||||
"enabled": false
|
"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
|
# Glance
|
||||||
|
|
||||||
|
|||||||
167
examples/Numeric.md
Normal file
167
examples/Numeric.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
[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": {
|
||||||
|
"action": "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 `action` 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 HomeAssistant:
|
||||||
|
|
||||||
|
<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 `action` 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": {
|
||||||
|
"action": "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": {
|
||||||
|
"action": "media_player.volume_set",
|
||||||
|
"picker": {
|
||||||
|
"step": 0.005,
|
||||||
|
"min": 0.2,
|
||||||
|
"max": 0.6,
|
||||||
|
"attribute": "volume_level",
|
||||||
|
"data_attribute": "volume_level"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<img src="../images/number_picker_raw.bmp" width="200" title="HomeAssistant Helper definition for an 'input_number'." style="margin:5px"/>
|
||||||
|
|
||||||
|
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="HomeAssistant 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:
|
||||||
|
|
||||||
|
```jinja
|
||||||
|
{{ state_attr('media_player.amplifier','volume_level') * 100 -80 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Conversion from dB to range 0.0 to 1.0:
|
||||||
|
|
||||||
|
```jinja
|
||||||
|
{{ (value+80)/100 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Availability template:
|
||||||
|
|
||||||
|
```jinja
|
||||||
|
{{ not is_state('media_player.amplifier','unavailable') }}
|
||||||
|
```
|
||||||
|
|
||||||
|
<img src="../images/number_picker_db.bmp" width="200" title="HomeAssistant Helper definition for an 'input_number'." style="margin:5px"/>
|
||||||
|
|
||||||
|
As an alternative to using the GUI, the following can be pasted into HomeAssistant'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": {
|
||||||
|
"action": "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 HomeAssistant 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
|
# Switches
|
||||||
|
|
||||||
@@ -48,12 +48,12 @@ switch:
|
|||||||
friendly_name: <name>
|
friendly_name: <name>
|
||||||
value_template: <value>
|
value_template: <value>
|
||||||
turn_on:
|
turn_on:
|
||||||
service: <service>
|
action: <action>
|
||||||
data:
|
data:
|
||||||
entity_id: <entity>
|
entity_id: <entity>
|
||||||
<attribute>: <value>
|
<attribute>: <value>
|
||||||
turn_off:
|
turn_off:
|
||||||
service: <service>
|
action: <action>
|
||||||
data:
|
data:
|
||||||
entity_id: <entity>
|
entity_id: <entity>
|
||||||
<attribute>: <value>
|
<attribute>: <value>
|
||||||
@@ -90,11 +90,11 @@ switch:
|
|||||||
friendly_name: Cover
|
friendly_name: Cover
|
||||||
value_template: "{{ is_state('cover.cover', 'open') }}"
|
value_template: "{{ is_state('cover.cover', 'open') }}"
|
||||||
turn_on:
|
turn_on:
|
||||||
service: cover.open_cover
|
action: cover.open_cover
|
||||||
data:
|
data:
|
||||||
entity_id: cover.cover
|
entity_id: cover.cover
|
||||||
turn_off:
|
turn_off:
|
||||||
service: cover.close_cover
|
action: cover.close_cover
|
||||||
data:
|
data:
|
||||||
entity_id: cover.cover
|
entity_id: cover.cover
|
||||||
```
|
```
|
||||||
@@ -118,8 +118,10 @@ You can choose individual items that will quit after they have completed their a
|
|||||||
"entity": "light.hall_light",
|
"entity": "light.hall_light",
|
||||||
"name": "Hall Light & Quit",
|
"name": "Hall Light & Quit",
|
||||||
"type": "toggle",
|
"type": "toggle",
|
||||||
|
"tap_action" {
|
||||||
"exit": true
|
"exit": true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Disable Menu Item
|
## Disable Menu Item
|
||||||
|
|||||||
@@ -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
|
# Templates
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ Note: Only when you use the `tap_action` field do you also need to include the `
|
|||||||
"type": "tap",
|
"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 %}",
|
"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": {
|
"tap_action": {
|
||||||
"service": "cover.toggle",
|
"action": "cover.toggle",
|
||||||
"pin": true
|
"pin": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +173,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
|||||||
"type": "tap",
|
"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 %}",
|
"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": {
|
"tap_action": {
|
||||||
"service": "light.turn_on",
|
"action": "light.turn_on",
|
||||||
"data": {
|
"data": {
|
||||||
"brightness_pct": 12
|
"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",
|
"name": "LEDs 1",
|
||||||
"type": "tap",
|
"type": "tap",
|
||||||
"tap_action": {
|
"tap_action": {
|
||||||
"service": "light.turn_on",
|
"action": "light.turn_on",
|
||||||
"data": {
|
"data": {
|
||||||
"brightness_pct": 37
|
"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",
|
"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 %}",
|
"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": {
|
"tap_action": {
|
||||||
"service": "light.turn_on",
|
"action": "light.turn_on",
|
||||||
"data": {
|
"data": {
|
||||||
"brightness_pct": 62
|
"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",
|
"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 %}",
|
"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": {
|
"tap_action": {
|
||||||
"service": "light.turn_on",
|
"action": "light.turn_on",
|
||||||
"data": {
|
"data": {
|
||||||
"brightness_pct": 87
|
"brightness_pct": 87
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,15 +44,22 @@ Half = 2
|
|||||||
# Original icons for 416x416 screen size with 48x48 icons
|
# Original icons for 416x416 screen size with 48x48 icons
|
||||||
original = (96, 48, 24)
|
original = (96, 48, 24)
|
||||||
|
|
||||||
|
# The icons need to scale as a ratio of screen size 48:416 pixels
|
||||||
|
#
|
||||||
|
# Icon 55 53 48 46 42 37 32 30 28 26 24 21 19 18
|
||||||
|
# Screen 480 454 416 390 360 320 280 260 240 218 208 176 166 156
|
||||||
|
|
||||||
# Convert icons to different screen sizes by these parameters
|
# Convert icons to different screen sizes by these parameters
|
||||||
lookup = {
|
lookup = {
|
||||||
# Doub Sing Half
|
# Doub Sing Half
|
||||||
# 0 1 2
|
# 0 1 2
|
||||||
|
480: (110, 55, 28),
|
||||||
454: (106, 53, 27),
|
454: (106, 53, 27),
|
||||||
# 416: ( 96, 48, 24),
|
# 416: ( 96, 48, 24),
|
||||||
390: ( 90, 46, 23),
|
390: ( 90, 46, 23),
|
||||||
360: ( 84, 42, 21),
|
360: ( 84, 42, 21),
|
||||||
320: ( 74, 38, 19),
|
320: ( 74, 38, 19),
|
||||||
|
295: ( 68, 34, 17), # Especially for the instinct3amoled50mm device that clip the icons
|
||||||
280: ( 64, 32, 16),
|
280: ( 64, 32, 16),
|
||||||
260: ( 60, 30, 15),
|
260: ( 60, 30, 15),
|
||||||
240: ( 56, 28, 14),
|
240: ( 56, 28, 14),
|
||||||
|
|||||||
BIN
images/http_410_error.jpg
Normal file
BIN
images/http_410_error.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
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/number_picker_db.bmp
Normal file
BIN
images/number_picker_db.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 338 KiB |
BIN
images/number_picker_raw.bmp
Normal file
BIN
images/number_picker_raw.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 338 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 |
@@ -40,7 +40,7 @@ output_dir_prefix = 'resources-launcher-'
|
|||||||
input_dir = output_dir_prefix + '70-70'
|
input_dir = output_dir_prefix + '70-70'
|
||||||
|
|
||||||
# Convert icons to different screen sizes by these parameters
|
# Convert icons to different screen sizes by these parameters
|
||||||
lookup = [26, 30, 33, 35, 36, 40, 54, 60, 61, 62, 65, 80]
|
lookup = [26, 30, 33, 35, 36, 38, 40, 52, 54, 56, 60, 61, 62, 65, 68, 80]
|
||||||
|
|
||||||
# Delete all but the original 48x48 icon directories
|
# Delete all but the original 48x48 icon directories
|
||||||
for entry in os.listdir("."):
|
for entry in os.listdir("."):
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
Device Information & References:
|
Device Information & References:
|
||||||
* https://developer.garmin.com/connect-iq/compatible-devices/
|
* https://developer.garmin.com/connect-iq/compatible-devices/
|
||||||
* https://developer.garmin.com/connect-iq/reference-guides/devices-reference/
|
* https://developer.garmin.com/connect-iq/device-reference/
|
||||||
|
|
||||||
philipabbey's Test App id="98c36259-498a-4458-9cef-74a273ad2bc3" type="watch-app"
|
philipabbey's Test App id="98c36259-498a-4458-9cef-74a273ad2bc3" type="watch-app"
|
||||||
Live Application id="40131e87-31ff-454b-a8e2-92276ee399d6" type="watch-app"
|
Live Application id="40131e87-31ff-454b-a8e2-92276ee399d6" type="watch-app"
|
||||||
@@ -44,7 +44,6 @@
|
|||||||
<iq:product id="d2mach1"/>
|
<iq:product id="d2mach1"/>
|
||||||
<iq:product id="descentg1"/>
|
<iq:product id="descentg1"/>
|
||||||
<iq:product id="descentg2"/>
|
<iq:product id="descentg2"/>
|
||||||
<iq:product id="descentmk1"/>
|
|
||||||
<iq:product id="descentmk2"/>
|
<iq:product id="descentmk2"/>
|
||||||
<iq:product id="descentmk2s"/>
|
<iq:product id="descentmk2s"/>
|
||||||
<iq:product id="descentmk343mm"/>
|
<iq:product id="descentmk343mm"/>
|
||||||
@@ -70,6 +69,7 @@
|
|||||||
<iq:product id="epix2"/>
|
<iq:product id="epix2"/>
|
||||||
<iq:product id="epix2pro42mm"/>
|
<iq:product id="epix2pro42mm"/>
|
||||||
<iq:product id="epix2pro47mm"/>
|
<iq:product id="epix2pro47mm"/>
|
||||||
|
<iq:product id="epix2pro47mmsystem7preview"/>
|
||||||
<iq:product id="epix2pro51mm"/>
|
<iq:product id="epix2pro51mm"/>
|
||||||
<iq:product id="fenix5"/>
|
<iq:product id="fenix5"/>
|
||||||
<iq:product id="fenix5plus"/>
|
<iq:product id="fenix5plus"/>
|
||||||
|
|||||||
@@ -133,20 +133,20 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
|
|||||||
var data = { "gps_accuracy" => accuracy };
|
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.
|
// 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) {
|
if (position.position != null) {
|
||||||
data.put("gps", position.position.toDegrees());
|
data["gps"] = position.position.toDegrees();
|
||||||
}
|
}
|
||||||
if (position.speed != null) {
|
if (position.speed != null) {
|
||||||
data.put("speed", Math.round(position.speed));
|
data["speed"] = Math.round(position.speed);
|
||||||
}
|
}
|
||||||
if (position.heading != null) {
|
if (position.heading != null) {
|
||||||
var heading = Math.round(position.heading * 180 / Math.PI);
|
var heading = Math.round(position.heading * 180 / Math.PI);
|
||||||
while (heading < 0) {
|
while (heading < 0) {
|
||||||
heading += 360;
|
heading += 360;
|
||||||
}
|
}
|
||||||
data.put("course", heading);
|
data["course"] = heading;
|
||||||
}
|
}
|
||||||
if (position.altitude != null) {
|
if (position.altitude != null) {
|
||||||
data.put("altitude", Math.round(position.altitude));
|
data["altitude"] = Math.round(position.altitude);
|
||||||
}
|
}
|
||||||
// System.println("BackgroundServiceDelegate onTemporalEvent(): data = " + data.toString());
|
// System.println("BackgroundServiceDelegate onTemporalEvent(): data = " + data.toString());
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
private var mGlanceTimer as Timer.Timer?;
|
private var mGlanceTimer as Timer.Timer?;
|
||||||
private var mUpdateTimer as Timer.Timer?;
|
private var mUpdateTimer as Timer.Timer?;
|
||||||
// Array initialised by onReturnFetchMenuConfig()
|
// Array initialised by onReturnFetchMenuConfig()
|
||||||
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem>?;
|
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or HomeAssistantNumericMenuItem>?;
|
||||||
private var mIsApp as Lang.Boolean = false; // Or Widget
|
private var mIsApp as Lang.Boolean = false; // Or Widget
|
||||||
private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates
|
private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates
|
||||||
private var mTemplates as Lang.Dictionary? = null; // Cache of compiled templates
|
private var mTemplates as Lang.Dictionary? = null; // Cache of compiled templates
|
||||||
@@ -629,6 +629,12 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||||
(item as HomeAssistantToggleMenuItem).updateToggleState(data[i.toString() + "t"]);
|
(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) {
|
if (Settings.getMenuCheck() && Settings.getCacheConfig() && !mIsCacheChecked) {
|
||||||
// We are caching the menu configuration, so let's fetch it and check if its been updated.
|
// 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 item = mItemsToUpdate[i];
|
||||||
var template = item.getTemplate();
|
var template = item.getTemplate();
|
||||||
if (template != null) {
|
if (template != null) {
|
||||||
mTemplates.put(i.toString(), {
|
mTemplates[i.toString()] = { "template" => template };
|
||||||
"template" => template
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||||
mTemplates.put(i.toString() + "t", {
|
mTemplates[i.toString() + "t"] = { "template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate() };
|
||||||
"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 phoneConnected = System.getDeviceSettings().phoneConnected;
|
||||||
var connectionAvailable = System.getDeviceSettings().connectionAvailable;
|
var connectionAvailable = System.getDeviceSettings().connectionAvailable;
|
||||||
|
|
||||||
// System.println("API URL = " + Settings.getApiUrl());
|
// System.println("HomeAssistantApp fetchApiStatus(): API URL = " + Settings.getApiUrl());
|
||||||
if (Settings.getApiUrl().equals("")) {
|
if (Settings.getApiUrl().equals("")) {
|
||||||
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
|
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
|
||||||
WatchUi.requestUpdate();
|
WatchUi.requestUpdate();
|
||||||
|
|||||||
@@ -24,10 +24,13 @@ class HomeAssistantConfirmation extends WatchUi.Confirmation {
|
|||||||
|
|
||||||
//! Class Constructor
|
//! Class Constructor
|
||||||
//
|
//
|
||||||
function initialize() {
|
function initialize(message as Lang.String?) {
|
||||||
|
if (message == null) {
|
||||||
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
|
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
|
||||||
|
} else {
|
||||||
|
WatchUi.Confirmation.initialize(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Delegate to respond to the confirmation request.
|
//! Delegate to respond to the confirmation request.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
|
|||||||
}?
|
}?
|
||||||
) {
|
) {
|
||||||
if (options != null) {
|
if (options != null) {
|
||||||
options.put(:icon, icon);
|
options[:icon] = icon;
|
||||||
} else {
|
} else {
|
||||||
options = { :icon => icon };
|
options = { :icon => icon };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ class HomeAssistantMenuItemFactory {
|
|||||||
//! Class Constructor
|
//! Class Constructor
|
||||||
//
|
//
|
||||||
private function initialize() {
|
private function initialize() {
|
||||||
mMenuItemOptions = {
|
mMenuItemOptions = { :alignment => Settings.getMenuAlignment() };
|
||||||
:alignment => Settings.getMenuAlignment()
|
|
||||||
};
|
|
||||||
|
|
||||||
mTapTypeIcon = new WatchUi.Bitmap({
|
mTapTypeIcon = new WatchUi.Bitmap({
|
||||||
:rezId => $.Rez.Drawables.TapTypeIcon,
|
:rezId => $.Rez.Drawables.TapTypeIcon,
|
||||||
@@ -84,7 +82,7 @@ class HomeAssistantMenuItemFactory {
|
|||||||
) as WatchUi.MenuItem {
|
) as WatchUi.MenuItem {
|
||||||
var keys = mMenuItemOptions.keys();
|
var keys = mMenuItemOptions.keys();
|
||||||
for (var i = 0; i < keys.size(); i++) {
|
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(
|
return new HomeAssistantToggleMenuItem(
|
||||||
label,
|
label,
|
||||||
@@ -99,7 +97,7 @@ class HomeAssistantMenuItemFactory {
|
|||||||
//! @param label Menu item label.
|
//! @param label Menu item label.
|
||||||
//! @param entity_id Home Assistant Entity ID (optional)
|
//! @param entity_id Home Assistant Entity ID (optional)
|
||||||
//! @param template Template for Home Assistant to render (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 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.
|
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||||
//
|
//
|
||||||
@@ -107,7 +105,7 @@ class HomeAssistantMenuItemFactory {
|
|||||||
label as Lang.String or Lang.Symbol,
|
label as Lang.String or Lang.Symbol,
|
||||||
entity_id as Lang.String?,
|
entity_id as Lang.String?,
|
||||||
template as Lang.String?,
|
template as Lang.String?,
|
||||||
service as Lang.String?,
|
action as Lang.String?,
|
||||||
data as Lang.Dictionary?,
|
data as Lang.Dictionary?,
|
||||||
options as {
|
options as {
|
||||||
:exit as Lang.Boolean,
|
:exit as Lang.Boolean,
|
||||||
@@ -119,25 +117,25 @@ class HomeAssistantMenuItemFactory {
|
|||||||
if (data == null) {
|
if (data == null) {
|
||||||
data = { "entity_id" => entity_id };
|
data = { "entity_id" => entity_id };
|
||||||
} else {
|
} else {
|
||||||
data.put("entity_id", entity_id);
|
data["entity_id"] = entity_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var keys = mMenuItemOptions.keys();
|
var keys = mMenuItemOptions.keys();
|
||||||
for (var i = 0; i < keys.size(); i++) {
|
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) {
|
if (action != null) {
|
||||||
options.put(:icon, mTapTypeIcon);
|
options.put(:icon, mTapTypeIcon);
|
||||||
return new HomeAssistantTapMenuItem(
|
return new HomeAssistantTapMenuItem(
|
||||||
label,
|
label,
|
||||||
template,
|
template,
|
||||||
service,
|
action,
|
||||||
data,
|
data,
|
||||||
options,
|
options,
|
||||||
mHomeAssistantService
|
mHomeAssistantService
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
options.put(:icon, mInfoTypeIcon);
|
options[:icon] = mInfoTypeIcon;
|
||||||
return new HomeAssistantTapMenuItem(
|
return new HomeAssistantTapMenuItem(
|
||||||
label,
|
label,
|
||||||
template,
|
template,
|
||||||
@@ -148,7 +146,43 @@ class HomeAssistantMenuItemFactory {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//! Numeric menu item.
|
||||||
|
//!
|
||||||
|
//! @param definition Items array from the JSON that defines this sub menu.
|
||||||
|
//! @param template Template for Home Assistant to render (optional)
|
||||||
|
//
|
||||||
|
function numeric(
|
||||||
|
label as Lang.String or Lang.Symbol,
|
||||||
|
entity_id as Lang.String?,
|
||||||
|
template as Lang.String?,
|
||||||
|
action as Lang.String?,
|
||||||
|
picker as Lang.Dictionary,
|
||||||
|
options as {
|
||||||
|
:exit as Lang.Boolean,
|
||||||
|
:confirm as Lang.Boolean,
|
||||||
|
:pin as Lang.Boolean,
|
||||||
|
:icon as WatchUi.Bitmap
|
||||||
|
}
|
||||||
|
) as WatchUi.MenuItem {
|
||||||
|
var data = null;
|
||||||
|
if (entity_id != null) {
|
||||||
|
data = { "entity_id" => entity_id };
|
||||||
|
}
|
||||||
|
var keys = mMenuItemOptions.keys();
|
||||||
|
for (var i = 0; i < keys.size(); i++) {
|
||||||
|
options[keys[i]] = mMenuItemOptions.get(keys[i]);
|
||||||
|
}
|
||||||
|
options[:icon] = mTapTypeIcon;
|
||||||
|
return new HomeAssistantNumericMenuItem(
|
||||||
|
label,
|
||||||
|
template,
|
||||||
|
action,
|
||||||
|
data,
|
||||||
|
picker,
|
||||||
|
options,
|
||||||
|
mHomeAssistantService
|
||||||
|
);
|
||||||
|
}
|
||||||
//! Group menu item.
|
//! Group menu item.
|
||||||
//!
|
//!
|
||||||
//! @param definition Items array from the JSON that defines this sub menu.
|
//! @param definition Items array from the JSON that defines this sub menu.
|
||||||
|
|||||||
100
source/HomeAssistantNumericFactory.mc
Normal file
100
source/HomeAssistantNumericFactory.mc
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
//-----------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Distributed under MIT Licence
|
||||||
|
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
|
||||||
|
// 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 & @thmichel, 13 October 2025
|
||||||
|
//
|
||||||
|
//------------------------------------------------------------
|
||||||
|
|
||||||
|
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 = "%d";
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
|
public function initialize(picker as Lang.Dictionary) {
|
||||||
|
PickerFactory.initialize();
|
||||||
|
|
||||||
|
// Get values from data
|
||||||
|
var val = picker["min"];
|
||||||
|
if (val != null) {
|
||||||
|
mStart = val.toString().toFloat();
|
||||||
|
}
|
||||||
|
val = picker["max"];
|
||||||
|
if (val != null) {
|
||||||
|
mStop = val.toString().toFloat();
|
||||||
|
}
|
||||||
|
val = picker["step"];
|
||||||
|
if (val != null) {
|
||||||
|
mStep = val.toString().toFloat();
|
||||||
|
}
|
||||||
|
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 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//! 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 Lang.Number) as Lang.Object? {
|
||||||
|
return mStart + (index * mStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Get the number of picker items
|
||||||
|
//!
|
||||||
|
//! @return Number of items
|
||||||
|
//
|
||||||
|
public function getSize() as Lang.Number {
|
||||||
|
return ((mStop - mStart) / mStep).toNumber() + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
253
source/HomeAssistantNumericMenuItem.mc
Normal file
253
source/HomeAssistantNumericMenuItem.mc
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
//-----------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Distributed under MIT Licence
|
||||||
|
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
|
||||||
|
// 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 & @thmichel, 13 October 2025
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
private var mData as Lang.Dictionary?;
|
||||||
|
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
|
||||||
|
//!
|
||||||
|
//! @param label Menu item label.
|
||||||
|
//! @param template Menu item template.
|
||||||
|
//! @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
|
||||||
|
//! one of these objects is created for all menu items to re-use.
|
||||||
|
//
|
||||||
|
function initialize(
|
||||||
|
label as Lang.String or Lang.Symbol,
|
||||||
|
template as Lang.String,
|
||||||
|
action 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,
|
||||||
|
:exit as Lang.Boolean,
|
||||||
|
:confirm as Lang.Boolean,
|
||||||
|
:pin as Lang.Boolean
|
||||||
|
}?,
|
||||||
|
haService as HomeAssistantService
|
||||||
|
) {
|
||||||
|
mAction = action;
|
||||||
|
mData = data;
|
||||||
|
mPicker = picker;
|
||||||
|
mExit = options[:exit];
|
||||||
|
mConfirm = options[:confirm];
|
||||||
|
mPin = options[:pin];
|
||||||
|
mLabel = label;
|
||||||
|
mHomeAssistantService = haService;
|
||||||
|
|
||||||
|
HomeAssistantMenuItem.initialize(
|
||||||
|
label,
|
||||||
|
template,
|
||||||
|
{
|
||||||
|
: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 callAction() as Void {
|
||||||
|
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||||
|
if (mPin && hasTouchScreen) {
|
||||||
|
var pin = Settings.getPin();
|
||||||
|
if (pin != null) {
|
||||||
|
var pinConfirmationView = new HomeAssistantPinConfirmationView();
|
||||||
|
WatchUi.pushView(
|
||||||
|
pinConfirmationView,
|
||||||
|
new HomeAssistantPinConfirmationDelegate({
|
||||||
|
:callback => method(:onConfirm),
|
||||||
|
:pin => pin,
|
||||||
|
:state => false,
|
||||||
|
:view => pinConfirmationView,
|
||||||
|
}),
|
||||||
|
WatchUi.SLIDE_IMMEDIATE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (mConfirm) {
|
||||||
|
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);
|
||||||
|
WatchUi.pushView(
|
||||||
|
dialog,
|
||||||
|
new WifiLteExecutionConfirmDelegate({
|
||||||
|
:type => "action",
|
||||||
|
:action => mAction,
|
||||||
|
:data => mData,
|
||||||
|
:exit => mExit,
|
||||||
|
}, dialog),
|
||||||
|
WatchUi.SLIDE_LEFT
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
var view;
|
||||||
|
if (mConfirm instanceof Lang.String) {
|
||||||
|
view = new HomeAssistantConfirmation(mConfirm as Lang.String?);
|
||||||
|
} else {
|
||||||
|
view = new HomeAssistantConfirmation(null);
|
||||||
|
}
|
||||||
|
WatchUi.pushView(
|
||||||
|
view,
|
||||||
|
new HomeAssistantConfirmationDelegate({
|
||||||
|
:callback => method(:onConfirm),
|
||||||
|
:confirmationView => view,
|
||||||
|
:state => false,
|
||||||
|
}),
|
||||||
|
WatchUi.SLIDE_IMMEDIATE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onConfirm(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! 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 {
|
||||||
|
var dataAttribute = mPicker["data_attribute"];
|
||||||
|
if (dataAttribute == null) {
|
||||||
|
//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 action if no entity_id is set to avoid crash
|
||||||
|
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHomeAssistantService.call(
|
||||||
|
mAction,
|
||||||
|
{
|
||||||
|
"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.
|
||||||
|
//
|
||||||
|
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) {
|
||||||
|
var f = data as Lang.Float;
|
||||||
|
setSubLabel(f.format(mFormatString));
|
||||||
|
} else if(data instanceof Lang.Number) {
|
||||||
|
var f = data.toFloat() as Lang.Float;
|
||||||
|
setSubLabel(f.format(mFormatString));
|
||||||
|
} else if (data instanceof Lang.String) {
|
||||||
|
// This should not happen
|
||||||
|
setSubLabel(data);
|
||||||
|
} 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 Picker's value. Needed to set new value via the Action call
|
||||||
|
//!
|
||||||
|
//! @param value New value to set.
|
||||||
|
//
|
||||||
|
public function setValue(value as Lang.Number or Lang.Float) as Void {
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Get the Picker's value.
|
||||||
|
//!
|
||||||
|
//! Needed to set new value via the Action 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
source/HomeAssistantNumericPicker.mc
Normal file
98
source/HomeAssistantNumericPicker.mc
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
//-----------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Distributed under MIT Licence
|
||||||
|
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
|
||||||
|
// 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 & @thmichel, 13 October 2025
|
||||||
|
//
|
||||||
|
//------------------------------------------------------------
|
||||||
|
|
||||||
|
using Toybox.Application;
|
||||||
|
using Toybox.Lang;
|
||||||
|
using Toybox.Graphics;
|
||||||
|
using Toybox.System;
|
||||||
|
using Toybox.WatchUi;
|
||||||
|
|
||||||
|
//! Picker that allows the user to choose a float value
|
||||||
|
//
|
||||||
|
class HomeAssistantNumericPicker extends WatchUi.Picker {
|
||||||
|
private var mItem as HomeAssistantNumericMenuItem;
|
||||||
|
|
||||||
|
//! Constructor
|
||||||
|
//
|
||||||
|
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();
|
||||||
|
|
||||||
|
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()]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//! 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.Number or Lang.Float) as Void {
|
||||||
|
mItem.setValue(value);
|
||||||
|
mItem.callAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! 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 {
|
||||||
|
mPicker.onConfirm(values[0]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 responseCode Response code.
|
||||||
//! @param data Response data.
|
//! @param data Response data.
|
||||||
@@ -87,7 +87,7 @@ class HomeAssistantService {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 200:
|
case 200:
|
||||||
// System.println("HomeAssistantService onReturnCall(): Service executed.");
|
// System.println("HomeAssistantService onReturnCall(): Action executed.");
|
||||||
getApp().forceStatusUpdates();
|
getApp().forceStatusUpdates();
|
||||||
var d = data as Lang.Array;
|
var d = data as Lang.Array;
|
||||||
var toast = WatchUi.loadResource($.Rez.Strings.Executed) as Lang.String;
|
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 action The Home Assistant action to be run, e.g. from the JSON `action` field.
|
||||||
//! @param data Data to be supplied to the service call.
|
//! @param data Data to be supplied to the action call.
|
||||||
//
|
//
|
||||||
function call(
|
function call(
|
||||||
service as Lang.String,
|
action as Lang.String,
|
||||||
data as Lang.Dictionary?,
|
data as Lang.Dictionary?,
|
||||||
exit as Lang.Boolean
|
exit as Lang.Boolean
|
||||||
) as Void {
|
) as Void {
|
||||||
@@ -136,8 +136,8 @@ class HomeAssistantService {
|
|||||||
WatchUi.pushView(
|
WatchUi.pushView(
|
||||||
dialog,
|
dialog,
|
||||||
new WifiLteExecutionConfirmDelegate({
|
new WifiLteExecutionConfirmDelegate({
|
||||||
:type => "service",
|
:type => "action",
|
||||||
:service => service,
|
:action => action,
|
||||||
:data => data,
|
:data => data,
|
||||||
:exit => exit,
|
:exit => exit,
|
||||||
}, dialog),
|
}, dialog),
|
||||||
@@ -151,9 +151,9 @@ class HomeAssistantService {
|
|||||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
|
||||||
} else {
|
} else {
|
||||||
// Can't use null for substring() parameters due to API version level.
|
// 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() URL=" + url);
|
||||||
// System.println("HomeAssistantService call() service=" + service);
|
// System.println("HomeAssistantService call() action=" + action);
|
||||||
|
|
||||||
var entity_id = "";
|
var entity_id = "";
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
|
|||||||
var url;
|
var url;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "service":
|
case "action":
|
||||||
var service = WifiLteExecutionConfirmDelegate.mCommandData[:service];
|
var action = WifiLteExecutionConfirmDelegate.mCommandData[:action];
|
||||||
url = Settings.getApiUrl() + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, service.length());
|
url = Settings.getApiUrl() + "/services/" + action.substring(0, action.find(".")) + "/" + action.substring(action.find(".")+1, action.length());
|
||||||
var entity_id = "";
|
var entity_id = "";
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
entity_id = data.get("entity_id");
|
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?) {
|
private function performRequest(url as Lang.String, data as Lang.Dictionary?) {
|
||||||
Communications.makeWebRequest(
|
Communications.makeWebRequest(
|
||||||
url,
|
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,
|
:method => Communications.HTTP_REQUEST_METHOD_POST,
|
||||||
:headers => Settings.augmentHttpHeaders({
|
:headers => Settings.augmentHttpHeaders({
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ using Toybox.Lang;
|
|||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
using Toybox.Graphics;
|
using Toybox.Graphics;
|
||||||
|
|
||||||
//! Menu button that triggers a service.
|
//! Menu button that triggers an action.
|
||||||
//
|
//
|
||||||
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||||
private var mHomeAssistantService as HomeAssistantService;
|
private var mHomeAssistantService as HomeAssistantService;
|
||||||
private var mService as Lang.String?;
|
private var mAction 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 mExit as Lang.Boolean;
|
||||||
private var mPin as Lang.Boolean;
|
private var mPin as Lang.Boolean;
|
||||||
private var mData as Lang.Dictionary?;
|
private var mData as Lang.Dictionary?;
|
||||||
@@ -31,11 +31,11 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
|||||||
//!
|
//!
|
||||||
//! @param label Menu item label.
|
//! @param label Menu item label.
|
||||||
//! @param template Menu item template.
|
//! @param template Menu item template.
|
||||||
//! @param service Menu item service.
|
//! @param action Menu item action.
|
||||||
//! @param data Data to supply to the service call.
|
//! @param data Data to supply to the action call.
|
||||||
//! @param exit Should the service call complete and then exit?
|
//! @param exit Should the action call complete and then exit?
|
||||||
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
|
//! @param confirm Should the action 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 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 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 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
|
//! @param haService Shared Home Assistant service object that will perform the required call. Only
|
||||||
@@ -44,7 +44,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
|||||||
function initialize(
|
function initialize(
|
||||||
label as Lang.String or Lang.Symbol,
|
label as Lang.String or Lang.Symbol,
|
||||||
template as Lang.String,
|
template as Lang.String,
|
||||||
service as Lang.String?,
|
action as Lang.String?,
|
||||||
data as Lang.Dictionary?,
|
data as Lang.Dictionary?,
|
||||||
options as {
|
options as {
|
||||||
:alignment as WatchUi.MenuItem.Alignment,
|
:alignment as WatchUi.MenuItem.Alignment,
|
||||||
@@ -65,16 +65,16 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
|||||||
);
|
);
|
||||||
|
|
||||||
mHomeAssistantService = haService;
|
mHomeAssistantService = haService;
|
||||||
mService = service;
|
mAction = action;
|
||||||
mData = data;
|
mData = data;
|
||||||
mExit = options[:exit];
|
mExit = options[:exit];
|
||||||
mConfirm = options[:confirm];
|
mConfirm = options[:confirm];
|
||||||
mPin = options[:pin];
|
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;
|
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||||
if (mPin && hasTouchScreen) {
|
if (mPin && hasTouchScreen) {
|
||||||
var pin = Settings.getPin();
|
var pin = Settings.getPin();
|
||||||
@@ -95,20 +95,24 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
|||||||
if ((! System.getDeviceSettings().phoneConnected ||
|
if ((! System.getDeviceSettings().phoneConnected ||
|
||||||
! System.getDeviceSettings().connectionAvailable) &&
|
! System.getDeviceSettings().connectionAvailable) &&
|
||||||
Settings.getWifiLteExecutionEnabled()) {
|
Settings.getWifiLteExecutionEnabled()) {
|
||||||
var dialogMsg = WatchUi.loadResource($.Rez.Strings.WifiLtePrompt) as Lang.String;
|
var dialog = new WatchUi.Confirmation(WatchUi.loadResource($.Rez.Strings.WifiLtePrompt) as Lang.String);
|
||||||
var dialog = new WatchUi.Confirmation(dialogMsg);
|
|
||||||
WatchUi.pushView(
|
WatchUi.pushView(
|
||||||
dialog,
|
dialog,
|
||||||
new WifiLteExecutionConfirmDelegate({
|
new WifiLteExecutionConfirmDelegate({
|
||||||
:type => "service",
|
:type => "action",
|
||||||
:service => mService,
|
:action => mAction,
|
||||||
:data => mData,
|
:data => mData,
|
||||||
:exit => mExit,
|
:exit => mExit,
|
||||||
}, dialog),
|
}, dialog),
|
||||||
WatchUi.SLIDE_LEFT
|
WatchUi.SLIDE_LEFT
|
||||||
);
|
);
|
||||||
} else {
|
} 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(
|
WatchUi.pushView(
|
||||||
view,
|
view,
|
||||||
new HomeAssistantConfirmationDelegate({
|
new HomeAssistantConfirmationDelegate({
|
||||||
@@ -128,9 +132,9 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
|||||||
//!
|
//!
|
||||||
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
|
//! @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) {
|
if (mAction != null) {
|
||||||
mHomeAssistantService.call(mService, mData, mExit);
|
mHomeAssistantService.call(mAction, mData, mExit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
private var mData as Lang.Dictionary;
|
private var mData as Lang.Dictionary;
|
||||||
private var mTemplate as Lang.String?;
|
private var mTemplate as Lang.String?;
|
||||||
private var mExit as Lang.Boolean;
|
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 mPin as Lang.Boolean;
|
||||||
private var mHasVibrate as Lang.Boolean = false;
|
private var mHasVibrate as Lang.Boolean = false;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
//!
|
//!
|
||||||
//! @param label Menu item label.
|
//! @param label Menu item label.
|
||||||
//! @param template Menu item template.
|
//! @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.
|
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||||
//
|
//
|
||||||
function initialize(
|
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.
|
//! @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;
|
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||||
if (mPin && hasTouchScreen) {
|
if (mPin && hasTouchScreen) {
|
||||||
// Undo the toggle
|
// Undo the toggle
|
||||||
@@ -324,7 +324,12 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
Settings.getWifiLteExecutionEnabled()) {
|
Settings.getWifiLteExecutionEnabled()) {
|
||||||
wifiPrompt(b);
|
wifiPrompt(b);
|
||||||
} else {
|
} 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(
|
WatchUi.pushView(
|
||||||
confirmationView,
|
confirmationView,
|
||||||
new HomeAssistantConfirmationDelegate({
|
new HomeAssistantConfirmationDelegate({
|
||||||
@@ -349,7 +354,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
setState(b);
|
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.
|
//! @param s Desired state: `true` to turn on, `false` to turn off.
|
||||||
//
|
//
|
||||||
@@ -377,7 +382,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
//! @param id The entity ID, e.g., `"switch.kitchen"`.
|
//! @param id The entity ID, e.g., `"switch.kitchen"`.
|
||||||
//! @param s Desired state: `true` for "turn_on", `false` for "turn_off".
|
//! @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 {
|
private static function getUrl(id as Lang.String, s as Lang.Boolean) as Lang.String {
|
||||||
var url = Settings.getApiUrl() + "/services/";
|
var url = Settings.getApiUrl() + "/services/";
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
if (options == null) {
|
if (options == null) {
|
||||||
options = { :title => definition.get("title") as Lang.String };
|
options = { :title => definition.get("title") as Lang.String };
|
||||||
} else {
|
} else {
|
||||||
options.put(:title, definition.get("title") as Lang.String);
|
options[:title] = definition.get("title") as Lang.String;
|
||||||
}
|
}
|
||||||
WatchUi.Menu2.initialize(options);
|
WatchUi.Menu2.initialize(options);
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
var content = items[i].get("content") as Lang.String?;
|
var content = items[i].get("content") as Lang.String?;
|
||||||
var entity = items[i].get("entity") as Lang.String?;
|
var entity = items[i].get("entity") as Lang.String?;
|
||||||
var tap_action = items[i].get("tap_action") as Lang.Dictionary?;
|
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?;
|
var confirm = false as Lang.Boolean or Lang.String or Null;
|
||||||
var pin = false as Lang.Boolean?;
|
var pin = false as Lang.Boolean?;
|
||||||
var data = null as Lang.Dictionary?;
|
var data = null as Lang.Dictionary?;
|
||||||
var enabled = true as Lang.Boolean?;
|
var enabled = true as Lang.Boolean?;
|
||||||
@@ -57,10 +57,13 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
enabled = items[i].get("enabled"); // Optional
|
enabled = items[i].get("enabled"); // Optional
|
||||||
}
|
}
|
||||||
if (items[i].get("exit") != null) {
|
if (items[i].get("exit") != null) {
|
||||||
exit = items[i].get("exit"); // Optional
|
exit = items[i].get("exit"); // Deprecated
|
||||||
}
|
}
|
||||||
if (tap_action != null) {
|
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
|
data = tap_action.get("data"); // Optional
|
||||||
if (tap_action.get("confirm") != null) {
|
if (tap_action.get("confirm") != null) {
|
||||||
confirm = tap_action.get("confirm"); // Optional
|
confirm = tap_action.get("confirm"); // Optional
|
||||||
@@ -68,6 +71,9 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
if (tap_action.get("pin") != null) {
|
if (tap_action.get("pin") != null) {
|
||||||
pin = tap_action.get("pin"); // Optional
|
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 != null && name != null && enabled) {
|
||||||
if (type.equals("toggle") && entity != null) {
|
if (type.equals("toggle") && entity != null) {
|
||||||
@@ -81,12 +87,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
:pin => pin
|
:pin => pin
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
} else if (type.equals("tap") && service != null) {
|
} else if (type.equals("tap") && action != null) {
|
||||||
addItem(HomeAssistantMenuItemFactory.create().tap(
|
addItem(HomeAssistantMenuItemFactory.create().tap(
|
||||||
name,
|
name,
|
||||||
entity,
|
entity,
|
||||||
content,
|
content,
|
||||||
service,
|
action,
|
||||||
data,
|
data,
|
||||||
{
|
{
|
||||||
:exit => exit,
|
:exit => exit,
|
||||||
@@ -103,7 +109,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
name,
|
name,
|
||||||
entity,
|
entity,
|
||||||
content,
|
content,
|
||||||
service,
|
action,
|
||||||
data,
|
data,
|
||||||
{
|
{
|
||||||
:exit => false,
|
:exit => false,
|
||||||
@@ -117,7 +123,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
name,
|
name,
|
||||||
entity,
|
entity,
|
||||||
content,
|
content,
|
||||||
service,
|
action,
|
||||||
data,
|
data,
|
||||||
{
|
{
|
||||||
:exit => exit,
|
:exit => exit,
|
||||||
@@ -126,13 +132,31 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
} else if (type.equals("numeric") && action != null) {
|
||||||
|
if (tap_action != null) {
|
||||||
|
var picker = tap_action.get("picker") as Lang.Dictionary?;
|
||||||
|
if (picker != null) {
|
||||||
|
addItem(HomeAssistantMenuItemFactory.create().numeric(
|
||||||
|
name,
|
||||||
|
entity,
|
||||||
|
content,
|
||||||
|
action,
|
||||||
|
picker,
|
||||||
|
{
|
||||||
|
:exit => exit,
|
||||||
|
:confirm => confirm,
|
||||||
|
:pin => pin
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (type.equals("info") && content != null) {
|
} else if (type.equals("info") && content != null) {
|
||||||
// Cannot exit from a non-actionable information only menu item.
|
// Cannot exit from a non-actionable information only menu item.
|
||||||
addItem(HomeAssistantMenuItemFactory.create().tap(
|
addItem(HomeAssistantMenuItemFactory.create().tap(
|
||||||
name,
|
name,
|
||||||
entity,
|
entity,
|
||||||
content,
|
content,
|
||||||
service,
|
action,
|
||||||
data,
|
data,
|
||||||
{
|
{
|
||||||
:exit => false,
|
:exit => false,
|
||||||
@@ -154,7 +178,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
//!
|
//!
|
||||||
//! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state.
|
//! @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 Null> {
|
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or HomeAssistantNumericMenuItem or Null> {
|
||||||
var fullList = [];
|
var fullList = [];
|
||||||
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
|
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
|
||||||
|
|
||||||
@@ -167,6 +191,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
fullList.add(item);
|
fullList.add(item);
|
||||||
}
|
}
|
||||||
fullList.addAll(item.getMenuView().getItemsToUpdate());
|
fullList.addAll(item.getMenuView().getItemsToUpdate());
|
||||||
|
} else if (item instanceof HomeAssistantNumericMenuItem) {
|
||||||
|
// Numeric items can have an optional template to evaluate
|
||||||
|
var nmi = item as HomeAssistantNumericMenuItem;
|
||||||
|
if (nmi.hasTemplate()) {
|
||||||
|
fullList.add(item);
|
||||||
|
}
|
||||||
} else if (item instanceof HomeAssistantToggleMenuItem) {
|
} else if (item instanceof HomeAssistantToggleMenuItem) {
|
||||||
fullList.add(item);
|
fullList.add(item);
|
||||||
} else if (item instanceof HomeAssistantTapMenuItem) {
|
} else if (item instanceof HomeAssistantTapMenuItem) {
|
||||||
@@ -183,8 +213,8 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
//! Called when this View is brought to the foreground. Restore
|
//! Called when this View is brought to the foreground. Restore
|
||||||
//! the state of this View and prepare it to be shown. This includes
|
//! the state of this View and prepare it to be shown. This includes
|
||||||
//! loading resources into memory.
|
//! loading resources into memory.
|
||||||
|
//
|
||||||
function onShow() as Void {}
|
function onShow() as Void {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Delegate for the HomeAssistantView.
|
//! Delegate for the HomeAssistantView.
|
||||||
@@ -243,17 +273,22 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
|||||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||||
var haToggleItem = item as HomeAssistantToggleMenuItem;
|
var haToggleItem = item as HomeAssistantToggleMenuItem;
|
||||||
// System.println(haToggleItem.getLabel() + " " + haToggleItem.getId() + " " + haToggleItem.isEnabled());
|
// System.println(haToggleItem.getLabel() + " " + haToggleItem.getId() + " " + haToggleItem.isEnabled());
|
||||||
haToggleItem.callService(haToggleItem.isEnabled());
|
haToggleItem.callAction(haToggleItem.isEnabled());
|
||||||
} else if (item instanceof HomeAssistantTapMenuItem) {
|
} else if (item instanceof HomeAssistantTapMenuItem) {
|
||||||
var haItem = item as HomeAssistantTapMenuItem;
|
var haItem = item as HomeAssistantTapMenuItem;
|
||||||
// System.println(haItem.getLabel() + " " + haItem.getId());
|
// 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());
|
||||||
|
// create new view to select new value
|
||||||
|
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) {
|
} else if (item instanceof HomeAssistantGroupMenuItem) {
|
||||||
var haMenuItem = item as HomeAssistantGroupMenuItem;
|
var haMenuItem = item as HomeAssistantGroupMenuItem;
|
||||||
// System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());
|
|
||||||
WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT);
|
WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT);
|
||||||
// } else {
|
|
||||||
// System.println(item.getLabel() + " " + item.getId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ using Toybox.Timer;
|
|||||||
class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||||
public static var mCommandData as {
|
public static var mCommandData as {
|
||||||
:type as Lang.String,
|
:type as Lang.String,
|
||||||
:service as Lang.String?,
|
:action as Lang.String?,
|
||||||
:data as Lang.Dictionary?,
|
:data as Lang.Dictionary?,
|
||||||
:url as Lang.String?,
|
:url as Lang.String?,
|
||||||
:id as Lang.Number?,
|
:id as Lang.Number?,
|
||||||
@@ -40,8 +40,8 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
|||||||
//!
|
//!
|
||||||
//! @param options A dictionary describing the command to be executed:<br>
|
//! @param options A dictionary describing the command to be executed:<br>
|
||||||
//! `{`<br>
|
//! `{`<br>
|
||||||
//!   `:type: as Lang.String,` // The command type, either `"service"` or `"entity"`.<br>
|
//!   `:type: as Lang.String,` // The command type, either `"action"` or `"entity"`.<br>
|
||||||
//!   `:service: as Lang.String?,` // (For type `"service"`) The Home Assistant service to call (e.g., "light.turn_on").<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>
|
//!   `: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>
|
//!   `: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>
|
//!   `: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(
|
function initialize(
|
||||||
cOptions as {
|
cOptions as {
|
||||||
:type as Lang.String,
|
:type as Lang.String,
|
||||||
:service as Lang.String?,
|
:action as Lang.String?,
|
||||||
:data as Lang.Dictionary?,
|
:data as Lang.Dictionary?,
|
||||||
:url as Lang.String?,
|
:url as Lang.String?,
|
||||||
:callback as Lang.Method?,
|
:callback as Lang.Method?,
|
||||||
@@ -73,7 +73,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
|||||||
mConfirmationView = view;
|
mConfirmationView = view;
|
||||||
mCommandData = {
|
mCommandData = {
|
||||||
:type => cOptions[:type],
|
:type => cOptions[:type],
|
||||||
:service => cOptions[:service],
|
:action => cOptions[:action],
|
||||||
:data => cOptions[:data],
|
:data => cOptions[:data],
|
||||||
:url => cOptions[:url],
|
:url => cOptions[:url],
|
||||||
:callback => cOptions[:callback],
|
:callback => cOptions[:callback],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@@ -7,7 +8,7 @@
|
|||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
data-name="vs/editor/editor.main"
|
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.54.0/min/vs/editor/editor.main.css" />
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
@@ -453,7 +454,7 @@ http:
|
|||||||
</div>
|
</div>
|
||||||
</dialog>
|
</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/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://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>
|
<script src="https://code.iconify.design/1/1.0.6/iconify.min.js"></script>
|
||||||
|
|||||||
98
web/main.js
98
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:
|
* @returns {Promise<[string, { name: string; description: string; fields:
|
||||||
* Record<string, { name: string; description: string; example: string;
|
* Record<string, { name: string; description: string; example: string;
|
||||||
* selector: unknown; required?: boolean }> }][]>} [id, data]
|
* selector: unknown; required?: boolean }> }][]>} [id, data]
|
||||||
*/
|
*/
|
||||||
async function get_services() {
|
async function get_actions() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(api_url + '/services', {
|
const res = await fetch(api_url + '/services', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -122,15 +122,15 @@ async function get_services() {
|
|||||||
document.querySelector('#api_url').classList.remove('invalid');
|
document.querySelector('#api_url').classList.remove('invalid');
|
||||||
document.querySelector('#api_token').classList.remove('invalid');
|
document.querySelector('#api_token').classList.remove('invalid');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const services = [];
|
const actions = [];
|
||||||
for (const d of data) {
|
for (const d of data) {
|
||||||
for (const service in d.services) {
|
for (const action in d.services) {
|
||||||
services.push([`${d.domain}.${service}`, d.services[service]]);
|
actions.push([`${d.domain}.${action}`, d.services[action]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return services;
|
return actions;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error fetching services:', e);
|
console.error('Error fetching actions:', e);
|
||||||
document.querySelector('#api_url').classList.add('invalid');
|
document.querySelector('#api_url').classList.add('invalid');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -141,10 +141,32 @@ async function get_services() {
|
|||||||
* @returns {Promise<{}>}
|
* @returns {Promise<{}>}
|
||||||
*/
|
*/
|
||||||
async function get_schema() {
|
async function get_schema() {
|
||||||
const res = await fetch(
|
const searchParams = new URL(window.location).searchParams;
|
||||||
'https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json'
|
|
||||||
);
|
const url = searchParams.get('schema');
|
||||||
return res.json();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,11 +176,11 @@ async function get_schema() {
|
|||||||
* @param {Record<string, string>} areas
|
* @param {Record<string, string>} areas
|
||||||
* @param {[string, { name: string; description: string; fields:
|
* @param {[string, { name: string; description: string; fields:
|
||||||
* Record<string, { name: string; description: string; example: string;
|
* Record<string, { name: string; description: string; example: string;
|
||||||
* selector: unknown; required?: boolean }> }][]} services
|
* selector: unknown; required?: boolean }> }][]} actions
|
||||||
* @param {{}} schema
|
* @param {{}} schema
|
||||||
* @returns {Promise<{}>}
|
* @returns {Promise<{}>}
|
||||||
*/
|
*/
|
||||||
async function generate_schema(entities, devices, areas, services, schema) {
|
async function generate_schema(entities, devices, areas, actions, schema) {
|
||||||
schema.$defs.entity = {
|
schema.$defs.entity = {
|
||||||
enum: Object.keys(entities),
|
enum: Object.keys(entities),
|
||||||
};
|
};
|
||||||
@@ -170,12 +192,18 @@ async function generate_schema(entities, devices, areas, services, schema) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const oneOf = [];
|
const oneOf = [];
|
||||||
for (const [id, data] of services) {
|
for (const [id, data] of actions) {
|
||||||
const i_properties = {
|
const i_properties = {
|
||||||
|
action: {
|
||||||
|
title: data.name,
|
||||||
|
description: data.description,
|
||||||
|
const: id,
|
||||||
|
},
|
||||||
service: {
|
service: {
|
||||||
title: data.name,
|
title: data.name,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
const: id,
|
const: id,
|
||||||
|
deprecated: true,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -371,12 +399,16 @@ async function generate_schema(entities, devices, areas, services, schema) {
|
|||||||
properties: i_properties,
|
properties: i_properties,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
schema.$defs.tap_action = {
|
schema.$defs.tap_action_tap = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
oneOf: oneOf,
|
oneOf: oneOf,
|
||||||
properties: {
|
properties: {
|
||||||
|
action: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
service: {
|
service: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
deprecated: true,
|
||||||
},
|
},
|
||||||
confirm: {
|
confirm: {
|
||||||
$ref: '#/$defs/confirm',
|
$ref: '#/$defs/confirm',
|
||||||
@@ -389,8 +421,16 @@ async function generate_schema(entities, devices, areas, services, schema) {
|
|||||||
properties: {},
|
properties: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
required: ['action'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: ['service'],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
delete schema.$defs.tap.properties.service;
|
delete schema.$defs.tap.properties.action;
|
||||||
delete schema.$schema;
|
delete schema.$schema;
|
||||||
|
|
||||||
return schema;
|
return schema;
|
||||||
@@ -428,22 +468,22 @@ let entities;
|
|||||||
let devices;
|
let devices;
|
||||||
/** @type {Awaited<ReturnType<typeof get_areas>>} */
|
/** @type {Awaited<ReturnType<typeof get_areas>>} */
|
||||||
let areas;
|
let areas;
|
||||||
/** @type {Awaited<ReturnType<typeof get_services>>} */
|
/** @type {Awaited<ReturnType<typeof get_actions>>} */
|
||||||
let services;
|
let actions;
|
||||||
let schema;
|
let schema;
|
||||||
async function loadSchema() {
|
async function loadSchema() {
|
||||||
[entities, devices, areas, services, schema] = await Promise.all([
|
[entities, devices, areas, actions, schema] = await Promise.all([
|
||||||
get_entities(),
|
get_entities(),
|
||||||
get_devices(),
|
get_devices(),
|
||||||
get_areas(),
|
get_areas(),
|
||||||
get_services(),
|
get_actions(),
|
||||||
get_schema(),
|
get_schema(),
|
||||||
]);
|
]);
|
||||||
if (window.makeMarkers) {
|
if (window.makeMarkers) {
|
||||||
window.makeMarkers();
|
window.makeMarkers();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
schema = await generate_schema(entities, devices, areas, services, schema);
|
schema = await generate_schema(entities, devices, areas, actions, schema);
|
||||||
} catch {}
|
} catch {}
|
||||||
console.log(schema);
|
console.log(schema);
|
||||||
if (window.m && window.modelUri) {
|
if (window.m && window.modelUri) {
|
||||||
@@ -789,14 +829,16 @@ require(['vs/editor/editor.main'], async () => {
|
|||||||
|
|
||||||
const runAction = editor.addCommand(
|
const runAction = editor.addCommand(
|
||||||
0,
|
0,
|
||||||
async function (_, action) {
|
async function (_, tap) {
|
||||||
const service = action.tap_action.service.split('.');
|
const action = (tap.tap_action.action ?? tap.tap_action.service).split(
|
||||||
let data = action.tap_action.data;
|
'.'
|
||||||
|
);
|
||||||
|
let data = tap.tap_action.data;
|
||||||
if (data) {
|
if (data) {
|
||||||
data.entity_id = action.entity;
|
data.entity_id = tap.entity;
|
||||||
} else {
|
} else {
|
||||||
data = {
|
data = {
|
||||||
entity_id: action.entity,
|
entity_id: tap.entity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const t = toast({
|
const t = toast({
|
||||||
@@ -804,7 +846,7 @@ require(['vs/editor/editor.main'], async () => {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
api_url + '/services/' + service[0] + '/' + service[1],
|
api_url + '/services/' + action[0] + '/' + action[1],
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -1130,7 +1172,7 @@ require(['vs/editor/editor.main'], async () => {
|
|||||||
if (node.type === 'property') {
|
if (node.type === 'property') {
|
||||||
if (node.key[0].value === 'tap_action') {
|
if (node.key[0].value === 'tap_action') {
|
||||||
const d = get(data, path);
|
const d = get(data, path);
|
||||||
if (d.tap_action.service) {
|
if (d.tap_action.action ?? d.tap_action.service) {
|
||||||
lenses.push({
|
lenses.push({
|
||||||
range: {
|
range: {
|
||||||
startLineNumber: node.key[0].range.start.line + 1,
|
startLineNumber: node.key[0].range.start.line + 1,
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/toastify-js": "^1.12.4",
|
"@types/toastify-js": "1.12.0",
|
||||||
"@vscode/webview-ui-toolkit": "1.4.0",
|
"@vscode/webview-ui-toolkit": "1.4.0",
|
||||||
"json-ast-comments": "1.1.1",
|
"json-ast-comments": "1.1.1",
|
||||||
"monaco-editor": "0.52.2",
|
"monaco-editor": "0.54.2",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"serve": "^14.2.1"
|
"serve": "^14.2.1"
|
||||||
}
|
}
|
||||||
|
|||||||
8
web/pnpm-lock.yaml
generated
8
web/pnpm-lock.yaml
generated
@@ -6,8 +6,8 @@ settings:
|
|||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/toastify-js':
|
'@types/toastify-js':
|
||||||
specifier: ^1.12.4
|
specifier: 1.12.0
|
||||||
version: 1.12.4
|
version: 1.12.0
|
||||||
'@vscode/webview-ui-toolkit':
|
'@vscode/webview-ui-toolkit':
|
||||||
specifier: 1.4.0
|
specifier: 1.4.0
|
||||||
version: 1.4.0(react@18.2.0)
|
version: 1.4.0(react@18.2.0)
|
||||||
@@ -55,8 +55,8 @@ packages:
|
|||||||
exenv-es6: 1.1.1
|
exenv-es6: 1.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/toastify-js@1.12.4:
|
/@types/toastify-js@1.12.0:
|
||||||
resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==}
|
resolution: {integrity: sha512-fqpDHaKhFukN9KRm24bbH0wozvHmSwjvkaLjBUrWcSfSS4zysIwTYqNLG3XbSNhRlsTNRNLGS23tp/VhPwsfHQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@vscode/webview-ui-toolkit@1.4.0(react@18.2.0):
|
/@vscode/webview-ui-toolkit@1.4.0(react@18.2.0):
|
||||||
|
|||||||
Reference in New Issue
Block a user