Compare commits

...

28 Commits

Author SHA1 Message Date
a6b4925ff7 257 pin dialog does not popup when using is servicetap (#258)
Need this urgently as it affects the use of the PIN for tap menu items.
2025-07-18 20:51:10 +01:00
3672a598fb Update HISTORY.md
Added v2.32
2025-07-18 20:49:10 +01:00
f6d0916315 Update HomeAssistantTapMenuItem.mc
Requirement for use of the PIN was not being correctly extracted from the options. Goes to show why the security features should not be relied on! A recent compilation fix created this breaking change.
2025-07-18 20:46:49 +01:00
6db7b67536 Update HISTORY.md
Added v2.31 text
2025-07-11 09:43:30 +01:00
e5df010af8 253 compilation failed on fenix5s (#254)
This is the fix to an annoying problem I faced as I was about to compile
the final v2.31 release. Please approve, but **don't merge** just yet.
I'll do that once the full device list compilation succeeds, just to
check if there are any residual issues.

Thanks
2025-07-11 09:31:37 +01:00
ee964ce882 Update HomeAssistantMenuItemFactory.mc
Function documentation fix.
2025-07-11 01:11:00 +01:00
ee9da24592 Fix for too many arguments to a function call on some devices 2025-07-11 01:06:15 +01:00
906cdf7371 Code documentation comments were not updated 2025-07-10 23:54:05 +01:00
7dd85937fa 243 option to close application after command (#252)
Also closes #250.
2025-07-10 22:02:12 +01:00
d141c03104 Review comment
Moved point of application exit for the menu item option.
2025-07-10 19:46:04 +01:00
842a31a1cc Review comment
enable => enabled
2025-07-10 18:24:30 +01:00
5639ff5c42 Amended heading levels in docs 2025-07-09 21:51:18 +01:00
8b3e86a00f Amended documentation for two new menu item options
The options are 'enable' and 'exit'.
2025-07-09 21:48:58 +01:00
6dbcea94cf Update HomeAssistantView.mc
Refined more precisely where the exit option can be used and enforced in the code.
2025-07-09 21:09:33 +01:00
b28daacafd Update config.schema.json
Removed exit from non-actionable menu items.
2025-07-09 20:54:28 +01:00
029a9f373e Update config.schema.json
Added to boolean options for disable and exit.
2025-07-09 19:57:11 +01:00
ec044c5408 Added two new options to menu items
1. The ability to disable a menu item without deleting it.
2. The option to quit the application on item selection.
2025-07-08 22:48:42 +01:00
659a060c76 Forgotten image used on App home page 2025-07-08 22:45:39 +01:00
3acef26fea 247 improve the glance view (#249)
2s seems to be a good compromise between the status flickering and the
update delay. When the menu is cached there's no flickering.
2025-07-07 13:49:53 +01:00
e898fc1fe5 Update config.schema.json
Signed-off-by: __JosephAbbey <me@josephabbey.dev>
2025-07-07 13:49:33 +01:00
4df1fd69bc Update Globals.mc
Increased the API back off time from 1s to 2s to remove the glance's status flickering. The assumption is that the GET requests are too fast for the Bluetooth stack.
2025-07-06 21:31:33 +01:00
fc19599586 Update export.cmd
Removed a commented out command line option no longer used and unlikely to be replaced.
2025-07-06 21:29:19 +01:00
13f70af45a Update Glance.md
Explained to turn on menu caching.
2025-07-06 21:28:40 +01:00
90ed1f4bea Update HomeAssistantGlanceView.mc
Improved layout.
2025-07-06 19:02:59 +01:00
2117b27210 Update HISTORY.md
Added two images.
2025-07-06 19:01:27 +01:00
df7874e825 Update HISTORY.md
Added link to Glance view for 2.30 text.
2025-07-06 17:32:12 +01:00
f3c5947b82 Merge branch 'main' into 247-improve-the-glance-view 2025-07-06 14:49:49 +01:00
47a930828a Revised glance view
Refreshed default view and new customisable view.
2025-07-06 14:44:53 +01:00
26 changed files with 680 additions and 130 deletions

View File

@ -1,4 +1,4 @@
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | Battery Reporting | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
# Background Service

View File

@ -1,4 +1,4 @@
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | Version History
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
# Version History
@ -34,7 +34,7 @@
| 2.19 | A template to evaluate is now optionally allowed on both `group` and `toggle` menu items. The template to evaluate is non-optional on a `template` menu item. All updates are performed in a single HTTP GET request for efficiency. Bug fix for negative heading values. Vibration now (optionally) confirms toggle menu items being tapped. |
| 2.20 | Simplified the code base now that templates have been requested in all menu items. This means the `template` menu item became a superset of `tap`. Therefore the `tap` code has been has been upgraded to include `template` and the latter deprecated. JSON menu definitions continue to support `template` items by instantiating a `tap` menu item, but the schema marks them as deprecated and users should migrate their menu definitions now. Use the [web editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) for assistance with changes. |
| 2.21 | Added 7 new devices (`edge1050`, `enduro3`, `fenix843mm`, `fenix847mm`, `fenix8solar47mm`, `fenix8solar51mm`, `fenixe`) and upgraded the SDK to 7.3.0. Fix for a bug on Edge devices introduced by v2.16 activity reporting improvements. |
| 2.22 | Major feature release adding an optional PIN to menu items. This significant new feature has been provided by [moesterheld](https://github.com/moesterheld). Please do not rely on this application for security. Use at your own risk! |
| 2.22 | <img src="images/pin_view.png" width="200" title="PIN Entry View"/><br/>Major feature release adding an optional PIN to menu items. This significant new feature has been provided by [moesterheld](https://github.com/moesterheld). Please do not rely on this application for security. Use at your own risk! |
| 2.23 | Added "info" menu item for displaying information via a template without a tap or toggle. Essentially like the old 'template' type that was deprecated when all items were amended to display evaluated templates. That action removed the display only items too hastily. Added 5 new devices in the model range Instinct 3 and Instinct E. |
| 2.24 | Experiment to prevent new Webhook IDs being created unnecessarily. Reduced the latency for the first menu update. Added 4 new devices: approachs50, descentg2, descentmk1, and gpsmap66. |
| 2.25 | 2 Bug fixes. First time startup issues caused by v2.24 change and a fix for pure numbers in returned templates. |
@ -42,3 +42,6 @@
| 2.27 | Trivial bug fix for the glance view to prevent the "Unconfigured" result being erroneously displayed because the settings were not yet pulled from persistent storage. |
| 2.28 | Added support for Vivoactive 6 device which also required an SDK update to 8.1.0. |
| 2.29 | Added support for three new devices, Forerunners 570 42mm & 47mm and 970. |
| 2.30 | <img src="images/Venu2_glance_default.png" width="200" title="Default Glance"/><br/>Extensive re-work of the [Glance](examples/Glance.md) view, including the ability to customise it with a user supplied template. |
| 2.31 | Adding [two new options](./examples/Actions.md#exit-on-tap) to the menu items: 1) The ability to disable a menu item, e.g. temporarily for seasonal changes, 2) The option to exit after a menu item has been select. |
| 2.32 | Bug fix for a breaking change extracting options caused by the need to rearrange function parameters for an [annoying compiler error](https://github.com/house-of-abbey/GarminHomeAssistant/issues/253). |

View File

@ -1,4 +1,4 @@
Home | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
# GarminHomeAssistant
@ -24,7 +24,7 @@ As of version 2.0, there are now two installable versions. For older devices bef
| Version | Explanation |
|------------------------|-------------|
| Application (original) | For newer devices that allow glance views in their applications, e.g. Venu 2, the GarminHomeAssistant application can be started either from a glance or from the list of applications and activities. Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/61c91d28-ec5e-438d-9f83-39e9f45b199d) application page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the application. The application can be started two different ways, either from the glance in the carousel, or as an application from the list of applications & activities. With the latter, it is worth marking the application as a favourite.<br/><img src="images/Venu2_app_start.png" width="200" title="Venu 2" style="margin:5px"/><img src="images/Vivoactive3_app_start.jpg" width="200" title="Venu 2" style="margin:5px"/><br/>If you place the application on your list of favourites, and rearrange it to appear near the top, then the item is just one button press away from the watch face. This second picture here shows the application menu on a Vivoactive 3 watch.<br/><img src="images/Venu2_glance_start.png" width="200" title="Venu 2" style="margin:5px"/><br/>On newer watches, you can also start the application from the glance carousel. The glance view here typically displays some trackable status, so ours provides some early indication of availability. Older watches will still allow you to start this application from the list of applications and activities. |
| Application (original) | For newer devices that allow glance views in their applications, e.g. Venu 2, the GarminHomeAssistant application can be started either from a glance or from the list of applications and activities. Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/61c91d28-ec5e-438d-9f83-39e9f45b199d) application page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the application. The application can be started two different ways, either from the glance in the carousel, or as an application from the list of applications & activities. With the latter, it is worth marking the application as a favourite.<br/><img src="images/Venu2_app_start.png" width="200" title="Venu 2" style="margin:5px"/><img src="images/Vivoactive3_app_start.jpg" width="200" title="Vivoactive 3" style="margin:5px"/><br/>If you place the application on your list of favourites, and rearrange it to appear near the top, then the item is just one button press away from the watch face. This second picture here shows the application menu on a Vivoactive 3 watch.<br/><img src="images/Venu2_glance_default.png" width="200" title="Venu 2" style="margin:5px"/><br/>On newer watches, you can also start the application from the glance carousel. The glance view here typically displays some trackable status, so ours provides some early indication of availability. Older watches will still allow you to start this application from the list of applications and activities. |
| Widget | **"Maintenance only mode"** so no new features will be added to this version.<br>For older devices that use widgets, e.g. Venu (1) as opposed to applications with "glances", the GarminHomeAssistant application can instead be started from the widget carousel. This is a separate item in the Connect IQ AppStore and with this installation, the application will no longer appear in the list of applications and activities. Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/) widget page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the widget.<br/><img src="images/Venu_Widget_sim.png" width="200" title="Venu 2" style="margin:5px"/><br/>Typically the widget view implements something similar to the glance view, e.g. status, and exists in a widget carousel to allow you to select an application to launch.<br>**Please note that memory in widgets is more limited than applications. This means a large menu definition can crash the widget without the code catching the error.**<br> This version was born out of the application version and from Ver 2.0 shared the same source code repository until Ver 2.8 when they were [separated](https://github.com/house-of-abbey/GarminHomeAssistantWidget) to allow the application version to take advantage of its increase memory availability. |
### Features

View File

@ -1,4 +1,4 @@
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Background Service](BackgroundService.md) | Trouble Shooting | [Version History](HISTORY.md)
[Home](README.md) | [Switches](examples/Switches.md) | [Actions](examples/Actions.md) | [Templates](examples/Templates.md) | [Glance](examples/Glance.md) | [Background Service](BackgroundService.md) | [Trouble Shooting](TroubleShooting.md) | [Version History](HISTORY.md)
# Troubleshooting Guides

View File

@ -2,14 +2,19 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "The schema will prevent JSON file errors."
},
"title": {
"type": "string"
"type": "string",
"description": "Top level menu title"
},
"glance": {
"$ref": "#/$defs/glance"
},
"items": {
"$ref": "#/$defs/items"
},
"$schema": {
"type": "string"
}
},
"required": ["title", "items"],
@ -29,8 +34,7 @@
"const": "toggle"
},
"content": {
"$ref": "#/$defs/content",
"description": "Optional in a toggle."
"$ref": "#/$defs/content"
},
"tap_action": {
"type": "object",
@ -43,6 +47,12 @@
}
},
"additionalProperties": false
},
"enabled": {
"$ref": "#/$defs/enabled"
},
"exit": {
"$ref": "#/$defs/exit"
}
},
"required": ["entity", "name", "type"],
@ -71,6 +81,9 @@
"deprecated": true,
"title": "Schema change:",
"description": "Use 'info' or 'tap' instead."
},
"enabled": {
"$ref": "#/$defs/enabled"
}
},
"required": ["name", "content", "type"],
@ -97,6 +110,12 @@
},
"tap_action": {
"$ref": "#/$defs/tap_action"
},
"enabled": {
"$ref": "#/$defs/enabled"
},
"exit": {
"$ref": "#/$defs/exit"
}
},
"required": ["name", "content", "type", "tap_action"],
@ -116,6 +135,9 @@
"type": {
"$ref": "#/$defs/type",
"const": "info"
},
"enabled": {
"$ref": "#/$defs/enabled"
}
},
"required": ["name", "content", "type"],
@ -135,8 +157,7 @@
"const": "tap"
},
"content": {
"$ref": "#/$defs/content",
"description": "Optional in a tap."
"$ref": "#/$defs/content"
},
"service": {
"$ref": "#/$defs/entity",
@ -146,6 +167,12 @@
},
"tap_action": {
"$ref": "#/$defs/tap_action"
},
"enabled": {
"$ref": "#/$defs/enabled"
},
"exit": {
"$ref": "#/$defs/exit"
}
},
"required": ["name", "type"],
@ -174,11 +201,13 @@
"const": "group"
},
"content": {
"$ref": "#/$defs/content",
"description": "Optional in a group."
"$ref": "#/$defs/content"
},
"items": {
"$ref": "#/$defs/items"
},
"enabled": {
"$ref": "#/$defs/enabled"
}
},
"required": ["name", "title", "type", "items"],
@ -190,7 +219,6 @@
},
"items": {
"type": "array",
"maxItems": 16,
"items": {
"oneOf": [
{
@ -248,7 +276,8 @@
"required": ["service"]
},
"content": {
"title": "Jinja2 template defining the text to display.",
"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.",
"type": "string"
},
"confirm": {
@ -262,6 +291,47 @@
"default": false,
"title": "PIN Confirmation",
"description": "Optional PIN confirmation of the action before execution as a precaution. Has precedence over 'confirm': true if both are set."
},
"glance": {
"type": "object",
"title": "Glance customisation",
"oneOf": [
{
"properties": {
"type": {
"title": "Glance type",
"description": "One of 'info' or 'status'. 'info' renders the template specified in the 'content' field inside the glance view. 'status' reverts to the default glance view and ignores the 'content' field. This allows for disabling the template temporarily.",
"const": "info"
},
"content": {
"$ref": "#/$defs/content"
}
},
"required": ["type", "content"]
},
{
"properties": {
"type": {
"title": "Glance type",
"description": "One of 'info' or 'status'.",
"const": "status"
}
},
"required": ["type"]
}
]
},
"enabled": {
"type": "boolean",
"default": true,
"title": "Enable the menu item",
"description": "Typically used to temporarily disable a menu item, e.g. for seasonal variations. Enabled (true) by default."
},
"exit": {
"type": "boolean",
"default": false,
"title": "Exit on selection",
"description": "Choose to exit the application after this item has been selected. Disabled (false) by default. N.B. Only actionable menu items can have this field added."
}
}
}

View File

@ -1,4 +1,5 @@
[Home](../README.md) | [Switches](Switches.md) | Actions | [Templates](Templates.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
# Actions
@ -75,3 +76,35 @@ Note that for notify events, you _must_ not supply an `entity_id` or the API cal
> Be careful with the value of the `service` 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 Home Assistant 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
You can choose individual items that will quit after they have completed their action.
```json
{
"entity": "automation.turn_off_stuff",
"name": "Turn off Stuff",
"type": "tap",
"tap_action": {
"service": "automation.trigger"
},
"exit": true
}
```
## Disable Menu Item
If you would like to temporarily disable an item in your menu, e.g. for seasonal reasons like not needing to turn on the heating in summer, then rather than swapping menu definition files or deleting a section of the menu you can mark the item as 'disabled'. This field applies to all menu items.
```json
{
"entity": "automation.turn_off_stuff",
"name": "Turn off Stuff",
"type": "tap",
"tap_action": {
"service": "automation.trigger"
},
"enabled": false
}
```

80
examples/Glance.md Normal file
View File

@ -0,0 +1,80 @@
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
# Glance
Since [version 2.30](../History.md), it is possible to ovverride the text displayed on the Glance view. This page explains how to customise the text.
## Default View
The default view has always been to display the status of the menu and API availability to indicate if there's a problem. This view has now been updated to be more colourful.
<img src="../images/Venu2_glance_default.png" width="200" title="Venu 2 Default Glance"/>
When either the API or the menu file is inaccessible, the fields will turn red.
## Customised View
In order to customise the Glance view you need to add a `glance` field to the top level of the JSON menu file as illustrated here:
```json
{
"$schema": "https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json",
"title": "Home",
"glance": {
"type": "info",
"content": "Text: {% .. %}"
},
"items": [...]
}
```
For example:
<img src="../images/Venu2_glance_custom.png" width="200" title="Venu 2 Customised Glance"/>
```json
{
"$schema": "https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json",
"glance": {
"type": "info",
"content": "Solar Battery: {{ states('sensor.battery_capacity_charge') }}%"
},
:
}
```
You may make this as complicated as you like! But you have limited space and only ASCII text characters. **It is best to turn on menu caching in order to speed up the display of the template**. The display is then nearly instantaneous.
The default view will persist showing until the errors are resolved. In order to extract the custom glance template both the menu and the API are required. So it is logical that the two tests must pass first. The exception here is if the menu is cached, in which case only the API needs to pass.
> [!IMPORTANT]
> Sadly what you cannot do is use special characters like: 🌞🔋⛅🪫. Whilst these do display in menu items, they do not seem to work on the Glance view. We really like them, so have tried but failed. Only ASCII text appears to be supported by the Garmin Connect IQ SDK's Glance View. This is not something we have any control over, please do not request this to be "fixed".
It is possible to revert to the default glance content without deleting the template by changing the `type` to `status`.
```json
{
"$schema": "https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json",
"title": "Home",
"glance": {
"type": "status",
"content": "Text: {% .. %}"
},
"items": [...]
}
```
So the glance view object has a `type` field with two possible values: `info` and `status`. When the type is `status` the `content` field is not required.
## Displayed Errors
The following shows the default glance when the menu file is not available at the specified URL.
<img src="../images/Venu2_glance_no_menu.png" width="200" title="Venu 2 Glance showing errors"/>
Once the custom glance template has been retrieved and evaluated the display will change. Should the connectivity to your Home Assistant then be lost, e.g. you move out of range of your phone, the glance reflects this in the colour of the residual two rectangles. The top one remains an indicator for the API, and the bottom rectangle remains an indicator for the menu availability, reflecting the original placement in the default glance view that has now been replaced.
<img src="../images/Venu2_glance_no_bt.png" width="200" title="Venu 2 Glance showing lost connectivity"/>

View File

@ -1,4 +1,5 @@
[Home](../README.md) | Switches | [Actions](Actions.md) | [Templates](Templates.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
# Switches
@ -108,3 +109,29 @@ Then you can use the following in your config:
"type": "toggle"
}
```
## Exit On Toggle
You can choose individual items that will quit after they have completed their action.
```json
{
"entity": "light.hall_light",
"name": "Hall Light & Quit",
"type": "toggle",
"exit": true
}
```
## Disable Menu Item
If you would like to temporarily disable an item in your menu, e.g. for seasonal reasons like not needing to turn on Christmas tree lights outside the festive season, then rather than swapping menu definition files or deleting a section of the menu you can mark the item as 'disabled'. This field applies to all menu items.
```json
{
"entity": "light.chrissmas_tree",
"name": "Christmas Lights",
"type": "toggle",
"enabled": false
}
```

View File

@ -1,4 +1,4 @@
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | Templates | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
[Home](../README.md) | [Switches](Switches.md) | [Actions](Actions.md) | [Templates](Templates.md) | [Glance](Glance.md) | [Background Service](../BackgroundService.md) | [Trouble Shooting](../TroubleShooting.md) | [Version History](../HISTORY.md)
# Templates
@ -218,6 +218,19 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
}
```
## Disable Menu Item
If you would like to temporarily disable an item in your menu, then rather than swapping menu definition files or deleting a section of the menu you can mark the item as 'disabled'. This field applies to all menu items.
```json
{
"name": "Phone",
"type": "info",
"content": "{{ ... }}",
"enabled": false
}
```
## Warnings
Just remember, on older smaller memory devices **you have the ability to crash the application by creating an excessive menu definition**. Templates can require significant definition for highly customised text. Don't be silly.

View File

@ -105,7 +105,6 @@ echo.
--private-key %SRC%\..\developer_key ^
--package-app ^
--release
rem --warn
echo.
echo Finished exporting HomeAssistant

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -25,7 +25,7 @@ class Globals {
//! Time to let the existing HTTP responses get serviced after a
//! `Communications.NETWORK_RESPONSE_OUT_OF_MEMORY` response code.
static const scApiBackoff = 1000; // ms
static const scApiBackoff = 2000; // ms
//! Needs to be long enough to enable a "double ESC" to quit the application from
//! an ErrorView.

View File

@ -24,18 +24,20 @@ using Toybox.Timer;
//
(:glance, :background)
class HomeAssistantApp extends Application.AppBase {
private var mApiStatus as Lang.String or Null;
private var mMenuStatus as Lang.String or Null;
private var mHaMenu as HomeAssistantView or Null;
private var mQuitTimer as QuitTimer or Null;
private var mGlanceTimer as Timer.Timer or Null;
private var mUpdateTimer as Timer.Timer or Null;
private var mApiStatus as Lang.String or Null;
private var mMenuStatus as Lang.String or Null;
private var mHaMenu as HomeAssistantView or Null;
private var mGlanceTemplate as Lang.String or Null = null;
private var mGlanceText as Lang.String or Null = null;
private var mQuitTimer as QuitTimer or Null;
private var mGlanceTimer as Timer.Timer or Null;
private var mUpdateTimer as Timer.Timer or Null;
// Array initialised by onReturnFetchMenuConfig()
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem> or Null;
private var mIsGlance as Lang.Boolean = false;
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 mTemplates as Lang.Dictionary = {};
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem> or Null;
private var mIsGlance as Lang.Boolean = false;
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 mTemplates as Lang.Dictionary = {};
//! Class Constructor
//
@ -203,7 +205,9 @@ class HomeAssistantApp extends Application.AppBase {
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
}
}
if (!mIsGlance) {
if (mIsGlance) {
glanceTemplate(data);
} else {
if (data == null) {
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
} else {
@ -272,7 +276,9 @@ class HomeAssistantApp extends Application.AppBase {
} else {
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String;
WatchUi.requestUpdate();
if (!mIsGlance) {
if (mIsGlance) {
glanceTemplate(menu);
} else {
buildMenu(menu);
}
return true;
@ -303,6 +309,22 @@ class HomeAssistantApp extends Application.AppBase {
}
}
//! Extract the optional template to override the default glance view.
//
function glanceTemplate(menu as Lang.Dictionary) {
if (menu != null) {
if (menu.get("glance") != null) {
var glance = menu.get("glance") as Lang.Dictionary;
if (glance.get("type").equals("info")) {
mGlanceTemplate = glance.get("content") as Lang.String;
// System.println("HomeAssistantApp glanceTemplate() " + mGlanceTemplate);
} else { // if glance.get("type").equals("status")
mGlanceTemplate = null;
}
}
}
}
//! Callback function for each menu update GET request.
//!
//! @param responseCode Response code.
@ -416,10 +438,9 @@ class HomeAssistantApp extends Application.AppBase {
}
}
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId();
// System.println("HomeAssistantApp updateMenuItems() URL=" + url + ", Template='" + mTemplate + "'");
Communications.makeWebRequest(
url,
Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(),
{
"type" => "render_template",
"data" => mTemplates
@ -548,6 +569,100 @@ class HomeAssistantApp extends Application.AppBase {
}
}
//! Callback function after completing the GET request to render the glance template.
//!
//! @param responseCode Response code.
//! @param data Response data.
//
(:glance)
function onReturnFetchGlanceContent(
responseCode as Lang.Number,
data as Null or Lang.Dictionary or Lang.String
) as Void {
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: " + responseCode);
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Data: " + data);
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
if (!mIsGlance) {
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String);
}
break;
case Communications.BLE_QUEUE_FULL:
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
if (!mIsGlance) {
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
}
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
if (!mIsGlance) {
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
}
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
if (!mIsGlance) {
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
}
break;
case 404:
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: 404, page not found. Check Configuration URL setting.");
if (!mIsGlance) {
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String);
}
break;
case 200:
if (data != null) {
mGlanceText = data.get("glanceTemplate");
}
break;
default:
// System.println("HomeAssistantApp onReturnFetchGlanceContent(): Unhandled HTTP response code = " + responseCode);
if (!mIsGlance) {
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
}
WatchUi.requestUpdate();
}
//! Construct the GET request to convert the optional glance template to text for display.
//
(:glance)
function fetchGlanceContent() as Void {
if (mGlanceTemplate != null) {
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
Communications.makeWebRequest(
Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(),
{
"type" => "render_template",
"data" => {
"glanceTemplate" => {
"template" => mGlanceTemplate
}
}
},
{
:method => Communications.HTTP_REQUEST_METHOD_POST,
:headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
},
method(:onReturnFetchGlanceContent)
);
}
}
//! Record the API status result.
//!
//! @param s A string describing the API status
@ -574,6 +689,16 @@ class HomeAssistantApp extends Application.AppBase {
return mMenuStatus;
}
//! Return the optional glance text that overrides the default glance content. This
//! is derived from the glance template.
//!
//! @return A string derived from the glance template
//
(:glance)
function getGlanceText() as Lang.String or Null {
return mGlanceText;
}
//! Return the Menu construction status.
//!
//! @return A Boolean indicating if the menu is loaded into the application.
@ -623,12 +748,21 @@ class HomeAssistantApp extends Application.AppBase {
return [new HomeAssistantGlanceView(self)];
}
//! Return the glance theme.
//!
//! @return The glance colour
//
function getGlanceTheme() as Application.AppBase.GlanceTheme {
return Application.AppBase.GLANCE_THEME_LIGHT_BLUE;
}
//! Update the menu and API statuses. Required for the Glance update timer.
//
function updateStatus() as Void {
mGlanceTimer = null;
fetchMenuConfig();
fetchApiStatus();
fetchGlanceContent();
}
//! Code for when the application settings are updated.

View File

@ -21,15 +21,36 @@ using Toybox.Graphics;
//
(:glance)
class HomeAssistantGlanceView extends WatchUi.GlanceView {
private static const scLeftMargin = 5; // in pixels
private static const scMidSep = 10; // Middle Separator "text:_text" in pixels
private var mApp as HomeAssistantApp;
private var mTitle as WatchUi.Text or Null;
private var mApiText as WatchUi.Text or Null;
private var mApiStatus as WatchUi.Text or Null;
private var mMenuText as WatchUi.Text or Null;
private var mMenuStatus as WatchUi.Text or Null;
private var mAntiAlias as Lang.Boolean = false;
//! Margin left of the filled rectangle in pixels.
private static const scLeftRectMargin = 5;
//! Filled rectangle width in pixels.
private static const scRectWidth = 20;
//! Margin right of the filled rectangle in pixels.
private static const scRightRectMargin = 5;
//! Separator between the first column of text and the second in pixels.
//! i.e. Middle Separator "text:_text"
private static const scMidSep = 10;
//! Margin on the right side of the glance in pixels.
private static const scRightGlanceMargin = 15;
//! Internal margin for the custom template between the border and the text in pixels.
private static const scIntCustMargin = 5;
//! Margin top and bottom of the rectangles in pixels.
private static const scVertMargin = 5;
//! Size of the rounded rectangle corners in pixels.
private static const scRectRadius = 5;
//! Dynamically scale the width of the first column of text based on the
//! language selection for the word "Menu".
private var mTextWidth as Lang.Number = 0;
// Re-usable text items for drawing
private var mApp as HomeAssistantApp;
private var mTitle as WatchUi.Text or Null;
private var mApiText as WatchUi.Text or Null;
private var mApiStatus as WatchUi.Text or Null;
private var mMenuText as WatchUi.Text or Null;
private var mMenuStatus as WatchUi.Text or Null;
private var mGlanceContent as WatchUi.TextArea or Null;
private var mAntiAlias as Lang.Boolean = false;
//! Class Constructor
//
@ -46,15 +67,16 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
//! @param dc Device context
//
function onLayout(dc as Graphics.Dc) as Void {
var h = dc.getHeight();
var tw = dc.getTextWidthInPixels(WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String, Graphics.FONT_XTINY);
var h = dc.getHeight();
mTextWidth = dc.getTextWidthInPixels(WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String + ":", Graphics.FONT_XTINY);
mTitle = new WatchUi.Text({
:text => WatchUi.loadResource($.Rez.Strings.AppName) as Lang.String,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_TINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
:locX => scLeftMargin,
:locX => scLeftRectMargin,
:locY => 1 * h / 6
});
@ -63,7 +85,7 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
:locX => scLeftMargin,
:locX => scLeftRectMargin + scRectWidth + scRightRectMargin,
:locY => 3 * h / 6
});
mApiStatus = new WatchUi.Text({
@ -71,15 +93,16 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
:locX => scLeftMargin + scMidSep + tw,
:locX => scLeftRectMargin + scRectWidth + scRightRectMargin + scMidSep + mTextWidth,
:locY => 3 * h / 6
});
mMenuText = new WatchUi.Text({
:text => WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String + ":",
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
:locX => scLeftMargin,
:locX => scLeftRectMargin + scRectWidth + scRightRectMargin,
:locY => 5 * h / 6
});
mMenuStatus = new WatchUi.Text({
@ -87,9 +110,20 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
:locX => scLeftMargin + scMidSep + tw,
:locX => scLeftRectMargin + scRectWidth + scRightRectMargin + scMidSep + mTextWidth,
:locY => 5 * h / 6
});
mGlanceContent = new WatchUi.TextArea({
:text => "A longer piece of text to wrap.",
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
:locX => scLeftRectMargin + scRectWidth + scRightRectMargin + scIntCustMargin,
:locY => (2 * h / 6) + scVertMargin,
:width => dc.getWidth() - scLeftRectMargin - scRectWidth - scRightRectMargin - (2 * scIntCustMargin) - scRightGlanceMargin,
:height => (4 * h / 6) - (2 * scVertMargin)
});
}
//! Update the view with the latest status text.
@ -97,8 +131,17 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
//! @param dc Device context
//
function onUpdate(dc as Graphics.Dc) as Void {
var h = dc.getHeight();
var w = dc.getWidth() - scLeftRectMargin - scRightGlanceMargin;
var apiStatus = mApp.getApiStatus();
var menuStatus = mApp.getMenuStatus();
var glanceText = mApp.getGlanceText();
var apiCol;
var menuCol;
// System.println("HomeAssistantGlanceView onUpdate() glanceText=" + glanceText);
GlanceView.onUpdate(dc);
if(mAntiAlias) {
if (mAntiAlias) {
dc.setAntiAlias(true);
}
dc.setColor(
@ -106,12 +149,60 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
Graphics.COLOR_TRANSPARENT
);
dc.clear();
mTitle.setColor(Graphics.COLOR_BLUE);
mTitle.draw(dc);
mApiText.draw(dc);
mApiStatus.setText(mApp.getApiStatus());
mApiStatus.draw(dc);
mMenuText.draw(dc);
mMenuStatus.setText(mApp.getMenuStatus());
mMenuStatus.draw(dc);
}
if (apiStatus.equals(WatchUi.loadResource($.Rez.Strings.Checking))) {
apiCol = Graphics.COLOR_YELLOW;
} else if (apiStatus.equals(WatchUi.loadResource($.Rez.Strings.Available))) {
apiCol = Graphics.COLOR_GREEN;
} else {
apiCol = Graphics.COLOR_RED;
}
if (menuStatus.equals(WatchUi.loadResource($.Rez.Strings.Checking))) {
menuCol = Graphics.COLOR_YELLOW;
} else if (menuStatus.equals(WatchUi.loadResource($.Rez.Strings.Available))) {
menuCol = Graphics.COLOR_GREEN;
} else if (menuStatus.equals(WatchUi.loadResource($.Rez.Strings.Cached))) {
menuCol = Graphics.COLOR_GREEN;
} else {
menuCol = Graphics.COLOR_RED;
}
if (glanceText == null) {
// Default Glance View
mApiText.draw(dc);
mApiStatus.setText(apiStatus);
mApiStatus.setColor(apiCol);
dc.setColor(apiCol, apiCol);
dc.drawRoundedRectangle(scLeftRectMargin, 2 * h / 6 + scVertMargin, w, 2 * h / 6 - (2 * scVertMargin), scRectRadius);
dc.fillRoundedRectangle(scLeftRectMargin, 2 * h / 6 + scVertMargin, scRectWidth, 2 * h / 6 - (2 * scVertMargin), scRectRadius);
mApiStatus.draw(dc);
mMenuText.draw(dc);
mMenuStatus.setText(menuStatus);
mMenuStatus.setColor(menuCol);
dc.setColor(menuCol, menuCol);
dc.drawRoundedRectangle(scLeftRectMargin, 4 * h / 6 + scVertMargin, w, 2 * h / 6 - (2 * scVertMargin), scRectRadius);
dc.fillRoundedRectangle(scLeftRectMargin, 4 * h / 6 + scVertMargin, scRectWidth, 2 * h / 6 - (2 * scVertMargin), scRectRadius);
mMenuStatus.draw(dc);
} else {
// Customised Glance View
dc.setColor(Graphics.COLOR_BLUE, Graphics.COLOR_BLUE);
dc.drawRoundedRectangle(
scLeftRectMargin + scRectWidth + scRightRectMargin,
2 * h / 6 + scVertMargin,
w - scRectWidth - scRightRectMargin,
4 * h / 6 - (2 * scVertMargin),
scRectRadius
);
dc.setColor(apiCol, apiCol);
dc.fillRoundedRectangle(scLeftRectMargin, 2 * h / 6 + scVertMargin, scRectWidth, 2 * h / 6 - (2 * scVertMargin), scRectRadius);
dc.setColor(menuCol, menuCol);
dc.fillRoundedRectangle(scLeftRectMargin, 4 * h / 6 + scVertMargin, scRectWidth, 2 * h / 6 - (2 * scVertMargin), scRectRadius);
mGlanceContent.setText(glanceText);
mGlanceContent.draw(dc);
}
}
}

View File

@ -70,23 +70,27 @@ class HomeAssistantMenuItemFactory {
//! @param label Menu item label.
//! @param entity_id Home Assistant Entity ID (optional)
//! @param template Template for Home Assistant to render (optional)
//! @param confirm Should this menu item selection be confirmed?
//! @param pin Should this menu item selection request the security PIN?
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
//
function toggle(
label as Lang.String or Lang.Symbol,
entity_id as Lang.String or Null,
template as Lang.String or Null,
confirm as Lang.Boolean,
pin as Lang.Boolean
options as {
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
:pin as Lang.Boolean
}
) as WatchUi.MenuItem {
var keys = mMenuItemOptions.keys();
for (var i = 0; i < keys.size(); i++) {
options.put(keys[i], mMenuItemOptions.get(keys[i]));
}
return new HomeAssistantToggleMenuItem(
label,
template,
confirm,
pin,
{ "entity_id" => entity_id },
mMenuItemOptions
options
);
}
@ -96,18 +100,20 @@ class HomeAssistantMenuItemFactory {
//! @param entity_id Home Assistant Entity ID (optional)
//! @param template Template for Home Assistant to render (optional)
//! @param service Template for Home Assistant to render (optional)
//! @param confirm Should this menu item selection be confirmed?
//! @param pin Should this menu item selection request the security PIN?
//! @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.
//
function tap(
label as Lang.String or Lang.Symbol,
entity_id as Lang.String or Null,
template as Lang.String or Null,
service as Lang.String or Null,
confirm as Lang.Boolean,
pin as Lang.Boolean,
data as Lang.Dictionary or Null
label as Lang.String or Lang.Symbol,
entity_id as Lang.String or Null,
template as Lang.String or Null,
service as Lang.String or Null,
data as Lang.Dictionary or Null,
options as {
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
:pin as Lang.Boolean
}
) as WatchUi.MenuItem {
if (entity_id != null) {
if (data == null) {
@ -116,28 +122,28 @@ class HomeAssistantMenuItemFactory {
data.put("entity_id", entity_id);
}
}
var keys = mMenuItemOptions.keys();
for (var i = 0; i < keys.size(); i++) {
options.put(keys[i], mMenuItemOptions.get(keys[i]));
}
if (service != null) {
options.put(:icon, mTapTypeIcon);
return new HomeAssistantTapMenuItem(
label,
template,
service,
confirm,
pin,
data,
mTapTypeIcon,
mMenuItemOptions,
options,
mHomeAssistantService
);
} else {
options.put(:icon, mInfoTypeIcon);
return new HomeAssistantTapMenuItem(
label,
template,
service,
confirm,
pin,
null,
data,
mInfoTypeIcon,
mMenuItemOptions,
options,
mHomeAssistantService
);
}

View File

@ -46,7 +46,13 @@ class HomeAssistantService {
data as Null or Lang.Dictionary or Lang.String,
context as Lang.Object
) as Void {
var entity_id = context as Lang.String or Null;
var c = context as Lang.Dictionary;
var entity_id;
var exit = false;
if (c != null) {
entity_id = c.get(:entity_id) as Lang.String;
exit = c.get(:exit) as Lang.Boolean;
}
// System.println("HomeAssistantService onReturnCall() Response Code: " + responseCode);
// System.println("HomeAssistantService onReturnCall() Response Data: " + data);
@ -102,6 +108,9 @@ class HomeAssistantService {
:bgcolor => Graphics.COLOR_BLACK
}).pushView(WatchUi.SLIDE_IMMEDIATE);
}
if (exit) {
System.exit();
}
break;
default:
@ -117,7 +126,8 @@ class HomeAssistantService {
//
function call(
service as Lang.String,
data as Lang.Dictionary or Null
data as Lang.Dictionary or Null,
exit as Lang.Boolean
) as Void {
if (! System.getDeviceSettings().phoneConnected) {
// System.println("HomeAssistantService call(): No Phone connection, skipping API call.");
@ -149,7 +159,10 @@ class HomeAssistantService {
"Authorization" => "Bearer " + Settings.getApiKey()
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
:context => entity_id
:context => {
:entity_id => entity_id,
:exit => exit
}
},
method(:onReturnCall)
);

View File

@ -21,8 +21,9 @@ using Toybox.Graphics;
//
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
private var mHomeAssistantService as HomeAssistantService;
private var mService as Lang.String or Null;
private var mService as Lang.String or Null;
private var mConfirm as Lang.Boolean;
private var mExit as Lang.Boolean;
private var mPin as Lang.Boolean;
private var mData as Lang.Dictionary or Null;
@ -31,45 +32,44 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
//! @param label Menu item label.
//! @param template Menu item template.
//! @param service Menu item service.
//! @param data Data to supply to the service call.
//! @param exit Should the service call complete and then exit?
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
//! @param pin Should the service call be protected with a PIN for some low level of security?
//! @param data Data to supply to the service call.
//! @param icon Icon to use for the menu item.
//! @param options Menu item options to be passed on.
//! @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,
label as Lang.String or Lang.Symbol,
template as Lang.String,
service as Lang.String or Null,
confirm as Lang.Boolean,
pin as Lang.Boolean,
service as Lang.String or Null,
data as Lang.Dictionary or Null,
icon as Graphics.BitmapType or WatchUi.Drawable,
options as {
:alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol,
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
:pin as Lang.Boolean
} or Null,
haService as HomeAssistantService
) {
if (options != null) {
options.put(:icon, icon);
} else {
options = { :icon => icon };
}
HomeAssistantMenuItem.initialize(
label,
template,
options
{
:alignment => options.get(:alignment),
:icon => options.get(:icon)
}
);
mHomeAssistantService = haService;
mService = service;
mConfirm = confirm;
mPin = pin;
mData = data;
mExit = options.get(:exit);
mConfirm = options.get(:confirm);
mPin = options.get(:pin);
}
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
@ -103,7 +103,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
//
function onConfirm(b as Lang.Boolean) as Void {
if (mService != null) {
mHomeAssistantService.call(mService, mData);
mHomeAssistantService.call(mService, mData, mExit);
}
}

View File

@ -22,30 +22,30 @@ using Toybox.Timer;
//! Light or switch toggle menu button that calls the API to maintain the up to date state.
//
class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
private var mConfirm as Lang.Boolean;
private var mPin as Lang.Boolean;
private var mData as Lang.Dictionary;
private var mTemplate as Lang.String;
private var mExit as Lang.Boolean;
private var mConfirm as Lang.Boolean;
private var mPin as Lang.Boolean;
private var mHasVibrate as Lang.Boolean = false;
//! Class Constructor
//!
//! @param label Menu item label.
//! @param template Menu item template.
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
//! @param pin Should the service call be protected with a PIN for some low level of security?
//! @param data Data to supply to the service call.
//! @param options Menu item options to be passed on.
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
//
function initialize(
label as Lang.String or Lang.Symbol,
template as Lang.String,
confirm as Lang.Boolean,
pin as Lang.Boolean,
data as Lang.Dictionary or Null,
options as {
:alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol,
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
:pin as Lang.Boolean
} or Null
) {
WatchUi.ToggleMenuItem.initialize(
@ -53,15 +53,19 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
null,
null,
false,
options
{
:alignment => options.get(:alignment),
:icon => options.get(:icon)
}
);
if (Attention has :vibrate) {
mHasVibrate = true;
}
mConfirm = confirm;
mPin = pin;
mData = data;
mTemplate = template;
mExit = options.get(:exit);
mConfirm = options.get(:confirm);
mPin = options.get(:pin);
}
//! Set the state of a toggle menu item.
@ -212,6 +216,9 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
getApp().setApiStatus(status);
if (mExit) {
System.exit();
}
}
//! Set the state of the toggle menu item.

View File

@ -51,21 +51,95 @@ class HomeAssistantView extends WatchUi.Menu2 {
var confirm = false as Lang.Boolean or Null;
var pin = false as Lang.Boolean or Null;
var data = null as Lang.Dictionary or Null;
var enabled = true as Lang.Boolean or Null;
var exit = false as Lang.Boolean or Null;
if (items[i].get("enabled") != null) {
enabled = items[i].get("enabled"); // Optional
}
if (items[i].get("exit") != null) {
exit = items[i].get("exit"); // Optional
}
if (tap_action != null) {
service = tap_action.get("service");
confirm = tap_action.get("confirm"); // Optional
pin = tap_action.get("pin"); // Optional
data = tap_action.get("data"); // Optional
if (confirm == null) {
confirm = false;
data = tap_action.get("data"); // Optional
if (tap_action.get("confirm") != null) {
confirm = tap_action.get("confirm"); // Optional
}
if (tap_action.get("pin") != null) {
pin = tap_action.get("pin"); // Optional
}
}
if (type != null && name != null) {
if (type != null && name != null && enabled) {
if (type.equals("toggle") && entity != null) {
addItem(HomeAssistantMenuItemFactory.create().toggle(name, entity, content, confirm, pin));
} else if ((type.equals("tap") && service != null) || (type.equals("info") && content != null) || (type.equals("template") && content != null)) {
addItem(HomeAssistantMenuItemFactory.create().toggle(
name,
entity,
content,
{
:exit => exit,
:confirm => confirm,
:pin => pin
}
));
} else if (type.equals("tap") && service != null) {
addItem(HomeAssistantMenuItemFactory.create().tap(
name,
entity,
content,
service,
data,
{
:exit => exit,
:confirm => confirm,
:pin => pin
}
));
} else if (type.equals("template") && content != null) {
// NB. "template" is deprecated in the schema and remains only for backward compatibility. All menu items can now use templates, so the replacement is "info".
addItem(HomeAssistantMenuItemFactory.create().tap(name, entity, content, service, confirm, pin, data));
// The exit option is dependent on the type of template.
if (tap_action == null) {
// No exit from an information only item
addItem(HomeAssistantMenuItemFactory.create().tap(
name,
entity,
content,
service,
data,
{
:exit => false,
:confirm => confirm,
:pin => pin
}
));
} else {
// You may exit from template item with a 'tap_action'.
addItem(HomeAssistantMenuItemFactory.create().tap(
name,
entity,
content,
service,
data,
{
:exit => exit,
:confirm => confirm,
:pin => pin
}
));
}
} else if (type.equals("info") && content != null) {
// Cannot exit from a non-actionable information only menu item.
addItem(HomeAssistantMenuItemFactory.create().tap(
name,
entity,
content,
service,
data,
{
:exit => false,
:confirm => confirm,
:pin => pin
}
));
} else if (type.equals("group")) {
addItem(HomeAssistantMenuItemFactory.create().group(items[i], content));
}

View File

@ -167,7 +167,7 @@ class WebLog {
// }
}
// Callback function to print the outcome of a clear() method. Typically used for debugging this class.
//! Callback function to print the outcome of a clear() method. Typically used for debugging this class.
//!
//! @param responseCode Response code.
//! @param data Response data.