Compare commits

..

9 Commits
v3.13 ... v3.14

Author SHA1 Message Date
Philip Abbey
02a9472ac1 Update HISTORY.md 2026-06-24 02:10:38 +01:00
Philip Abbey
e0b5978406 Update HomeAssistantView.mc
Bug fix for numeric picker initialisation when no user defined template is supplied.
2026-06-24 02:02:18 +01:00
Philip Abbey
c48d74138a Update config.schema.json
Numeric's tap_action is different to the tap's, and re-using the $ref caused problems, so properties have been copied.
2026-06-24 01:38:34 +01:00
Philip Abbey
3404c7f679 Update schema for numeric tap_action and HISTORY
Add schema metadata and stricter validation for numeric tap_action: include title & description, disallow additionalProperties on the tap_action and its picker properties to better define the number picker payload. Update HISTORY.md with a 3.14 entry documenting numeric items now supporting tap_action.data and the PIN/menu behavior change, and credit the contributor.
2026-06-24 01:12:20 +01:00
Philip Abbey
8c5bdbc1f8 Non-touch screens cannot use PIN
When a menu item requests a PIN on devices without a touch screen, replace the menu item with an info item displaying a message for the user.
2026-06-24 00:17:26 +01:00
Philip Abbey
a97440b997 Preserve tap action data for numeric picker actions (#362)
Numeric menu items currently discard existing `tap_action.data` when
confirming a picker value. This prevents service call options such as
`transition` from being sent with `light.turn_on`.

This change passes `tap_action.data` into numeric menu items and merges
that data with the selected numeric picker value before calling the Home
Assistant service.

Example fixed payload:

```json
{
  "entity_id": "light.living_room",
  "transition": 2,
  "brightness": 128
}

Tested with a Forerunner 965 build and a Home Assistant light brightness numeric picker.
2026-06-23 23:27:45 +01:00
StumblingGamer
17690d4349 Proposing changes to documentation and JSON Schema
Added proposed documentation section explaining Numeric data object and also updated JSON schema to match changes.

I tested the numeric example with tap_action.data in the documented web editor. The current schema did not appear to reject the field,
likely because numeric tap_action does not currently forbid additional properties at that nested level. I still added data explicitly
to the numeric schema so it is documented, discoverable, and type-checked as an object.
2026-06-23 11:12:00 -04:00
StumblingGamer
b1fb5d9417 Preserve tap action data for numeric picker actions
Pass tap_action.data into numeric menu items and merge it with the selected picker value before calling the Home Assistant service.
2026-06-22 11:03:23 -04:00
Philip Abbey
2fb59d2a56 Update Actions.md
Main update is to point out the obvious that a PIN can only be used with touch screen enabled devices. Some additional tidying too.
2026-06-18 08:30:53 +01:00
7 changed files with 119 additions and 34 deletions

View File

@@ -59,3 +59,4 @@
| 3.11 | <img src="images/Venu2_glance_default.png" width="200" title="Default Glance"/><br/>Better handling of menus larger than the Glance view can handle. As a result the Glance can no longer verify the availability of the JSON menu as fetching it might cause an _"Error: Out Of Memory Error"_. Provide two user supplied HTTP Headers. Bug fix for the numeric picker. | | 3.11 | <img src="images/Venu2_glance_default.png" width="200" title="Default Glance"/><br/>Better handling of menus larger than the Glance view can handle. As a result the Glance can no longer verify the availability of the JSON menu as fetching it might cause an _"Error: Out Of Memory Error"_. Provide two user supplied HTTP Headers. Bug fix for the numeric picker. |
| 3.12 | Amended GPS accuracy values used by the background service. | | 3.12 | Amended GPS accuracy values used by the background service. |
| 3.13 | Added support for new devices: `d2mach2pro`, `fr170`, `fr170m`, `fr70`. | | 3.13 | Added support for new devices: `d2mach2pro`, `fr170`, `fr170m`, `fr70`. |
| 3.14 | Numeric items now support `tap_action.data` fields so service call options like `transition` are catered for. See the documentation on [Additional Action Data](https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/examples/Numeric.md#additional-action-data). This features has been provided by [@StumblingGamer](https://github.com/StumblingGamer). Now prevent PIN enabled menu items from being enabled on non-touch screen devices. The PIN must be removed for the menu item to function. Fix for initialising numeric pickers with no user defined template. |

View File

@@ -251,11 +251,17 @@
"$ref": "#/$defs/content" "$ref": "#/$defs/content"
}, },
"tap_action": { "tap_action": {
"$ref": "#/$defs/tap_action", "title": "Tap Action",
"description": "Numeric tap action definition to include the number picker. 'confirm', 'pin' and 'exit' fields are optional.",
"properties": { "properties": {
"action": { "action": {
"$ref": "#/$defs/action" "$ref": "#/$defs/action"
}, },
"data": {
"type": "object",
"title": "Additional action data",
"description": "Fixed Home Assistant service data. The selected picker value is merged into this object using the picker's data_attribute. Service-specific fields cannot be schema checked here."
},
"picker": { "picker": {
"type": "object", "type": "object",
"title": "Number picker configuration", "title": "Number picker configuration",
@@ -289,13 +295,24 @@
"max", "max",
"step", "step",
"data_attribute" "data_attribute"
] ],
"additionalProperties": false
},
"confirm": {
"$ref": "#/$defs/confirm"
},
"pin": {
"$ref": "#/$defs/pin"
},
"exit": {
"$ref": "#/$defs/exit"
} }
}, },
"required": [ "required": [
"action", "action",
"picker" "picker"
] ],
"additionalProperties": false
}, },
"enabled": { "enabled": {
"$ref": "#/$defs/enabled" "$ref": "#/$defs/enabled"
@@ -982,7 +999,7 @@
"tap_action": { "tap_action": {
"type": "object", "type": "object",
"title": "Tap Action", "title": "Tap Action",
"description": "'confirm' and 'pin' fields are optional.", "description": "'confirm', 'pin' and 'exit' fields are optional.",
"properties": { "properties": {
"confirm": { "confirm": {
"$ref": "#/$defs/confirm" "$ref": "#/$defs/confirm"

View File

@@ -25,6 +25,8 @@ Two simple examples using a scene and a cover as `tap` menu items.
} }
``` ```
## Confirmation
Any menu item with an action (`tap`, `template`, or `toggle`), may have a confirmation view added. For consistency this is always done via the `tap_action` JSON object, even though for a `toggle` menu item there will only ever be a single field inside. For the `toggle` menu item, the confirmation is presented on both `on` and `off` directions. There is no option for asymmetry, i.e. only in one direction. Any menu item with an action (`tap`, `template`, or `toggle`), may have a confirmation view added. For consistency this is always done via the `tap_action` JSON object, even though for a `toggle` menu item there will only ever be a single field inside. For the `toggle` menu item, the confirmation is presented on both `on` and `off` directions. There is no option for asymmetry, i.e. only in one direction.
```json ```json
@@ -56,6 +58,8 @@ The `confirm` field may contain a string instead of a Boolean in order to provid
} }
``` ```
## Personal Identification Number (PIN)
**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.**
This can be enabled by setting the `pin` field in the `tap_action`. The `pin` field overrides `confirm`. Explicitly setting `confirm` is not necessary. This can be enabled by setting the `pin` field in the `tap_action`. The `pin` field overrides `confirm`. Explicitly setting `confirm` is not necessary.
@@ -70,9 +74,14 @@ The 4-digit PIN is set globally for all actions in the app settings in Connect I
When entering an invalid PIN for the fifth time within 2 minutes, the PIN dialog will be locked for all actions for the next 10 minutes. Entering a valid PIN will always reset the failure counter. When entering an invalid PIN for the fifth time within 2 minutes, the PIN dialog will be locked for all actions for the next 10 minutes. Entering a valid PIN will always reset the failure counter.
<img src="../images/pin_view.png" width="200" title="Confirmation View"/> <img src="../images/pin_view.png" width="200" title="PIN View"/>
Note that for notify events, you _must_ not supply an `entity_id` or the API call will fail. There are other examples too. > [!IMPORTANT]
> The PIN entry requires a touch screen enabled device.
## Additional Data
Some `action`s require additional `data` to be supplied. The following example shows how we handle this scenario. Note that for notify events, you _must not_ supply an `entity_id` or the API call will fail. There are other examples too.
```json ```json
{ {

View File

@@ -39,6 +39,36 @@ Field | Purpose
It may well be the case that often `attribute` and `data_attribute` are the same attribute, as with this example. It may well be the case that often `attribute` and `data_attribute` are the same attribute, as with this example.
## Additional Action Data
The `tap_action` object may also include a `data` object for fixed service call parameters. When the picker value is selected, the application merges the picker value into this `data` object using `data_attribute`.
For example, a light brightness picker can request a HomeAssistant transition while still using the picker value for `brightness`.
```json
{
"name": "Lounge Brightness",
"content": "{{ ((state_attr('light.lounge','brightness') | int(0) / 255) * 100) | round(0) }}%",
"type": "numeric",
"entity": "light.lounge",
"tap_action": {
"action": "light.turn_on",
"data": {
"transition": 2
},
"picker": {
"step": 5,
"min": 0,
"max": 255,
"attribute": "brightness",
"data_attribute": "brightness"
}
}
}
```
The resulting action data sent to HomeAssistant contains both the fixed `transition` value and the selected `brightness` value.
## Helper ## Helper
You might define a "helper" entity as follows in HomeAssistant: You might define a "helper" entity as follows in HomeAssistant:

View File

@@ -112,7 +112,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?,
action 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,
@@ -163,6 +163,7 @@ class HomeAssistantMenuItemFactory {
entity_id as Lang.String?, entity_id as Lang.String?,
template as Lang.String?, template as Lang.String?,
action as Lang.String?, action as Lang.String?,
data as Lang.Dictionary?,
picker as Lang.Dictionary, picker as Lang.Dictionary,
options as { options as {
:exit as Lang.Boolean, :exit as Lang.Boolean,
@@ -171,9 +172,12 @@ class HomeAssistantMenuItemFactory {
:icon as WatchUi.Bitmap :icon as WatchUi.Bitmap
} }
) as WatchUi.MenuItem { ) as WatchUi.MenuItem {
var data = null;
if (entity_id != null) { if (entity_id != null) {
data = { "entity_id" => entity_id }; if (data == null) {
data = { "entity_id" => entity_id };
} else {
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++) {

View File

@@ -155,7 +155,10 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
// //
function onConfirm(b as Lang.Boolean) as Void { function onConfirm(b as Lang.Boolean) as Void {
var dataAttribute = mPicker["data_attribute"] as Lang.String?; var dataAttribute = mPicker["data_attribute"] as Lang.String?;
var entity_id = mData["entity_id"] as Lang.String?; var entity_id = null as Lang.String?;
if (mData != null) {
entity_id = mData["entity_id"] as Lang.String?;
}
WatchUi.popView(WatchUi.SLIDE_RIGHT); WatchUi.popView(WatchUi.SLIDE_RIGHT);
WatchUi.requestUpdate(); WatchUi.requestUpdate();
@@ -164,12 +167,18 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
return; return;
} }
if (mAction != null) { if (mAction != null) {
var data = {} as Lang.Dictionary;
if (mData != null) {
var keys = mData.keys();
for (var i = 0; i < keys.size(); i++) {
data[keys[i]] = mData[keys[i]];
}
}
data["entity_id"] = entity_id.toString();
data[dataAttribute.toString()] = mValue;
mHomeAssistantService.call( mHomeAssistantService.call(
mAction, mAction,
{ data,
"entity_id" => entity_id.toString(),
dataAttribute.toString() => mValue
},
mExit mExit
); );
} }

View File

@@ -133,22 +133,39 @@ class HomeAssistantView extends WatchUi.Menu2 {
)); ));
} }
} else if (type.equals("numeric") && action != null) { } else if (type.equals("numeric") && action != null) {
if (tap_action != null) { if (System.getDeviceSettings().isTouchScreen) {
var picker = tap_action.get("picker") as Lang.Dictionary?; // Numeric items are only actionable on touch screen devices.
if (picker != null) { if (tap_action != null) {
addItem(HomeAssistantMenuItemFactory.create().numeric( var picker = tap_action.get("picker") as Lang.Dictionary?;
name, if (picker != null) {
entity, addItem(HomeAssistantMenuItemFactory.create().numeric(
content, name,
action, entity,
picker, content,
{ action,
:exit => exit, data,
:confirm => confirm, picker,
:pin => pin {
} :exit => exit,
)); :confirm => confirm,
:pin => pin
}
));
}
} }
} else {
addItem(HomeAssistantMenuItemFactory.create().tap(
"PIN requires Touchscreen",
null,
null,
null,
data,
{
:exit => false,
:confirm => false,
:pin => false
}
));
} }
} 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.
@@ -192,11 +209,9 @@ class HomeAssistantView extends WatchUi.Menu2 {
} }
fullList.addAll(item.getMenuView().getItemsToUpdate()); fullList.addAll(item.getMenuView().getItemsToUpdate());
} else if (item instanceof HomeAssistantNumericMenuItem) { } else if (item instanceof HomeAssistantNumericMenuItem) {
// Numeric items can have an optional template to evaluate // Numeric items can have an optional template to evaluate, but they must always be included in order
var nmi = item as HomeAssistantNumericMenuItem; // to initialise the numeric picker's value.
if (nmi.hasTemplate()) { fullList.add(item);
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) {