mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-11-13 20:48:14 +00:00
Compare commits
3 Commits
main
...
support-mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6aafd4fcb1 | ||
|
|
a52d6188be | ||
|
|
e697f75ce3 |
@@ -81,7 +81,7 @@ Example schema:
|
||||
"name": "Food is Ready!",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "script.turn_on",
|
||||
"action": "script.turn_on",
|
||||
"confirm": true
|
||||
}
|
||||
},
|
||||
@@ -132,7 +132,7 @@ Example schema:
|
||||
"name": "Turn off USBs",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "automation.trigger"
|
||||
"action": "automation.trigger"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -140,7 +140,7 @@ Example schema:
|
||||
"name": "TV Lights Scene",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "scene.turn_on",
|
||||
"action": "scene.turn_on",
|
||||
"pin": true
|
||||
}
|
||||
},
|
||||
@@ -150,7 +150,7 @@ Example schema:
|
||||
"type": "numeric",
|
||||
"entity": "climate.myheating",
|
||||
"tap_action": {
|
||||
"service": "climate.set_temperature",
|
||||
"action": "climate.set_temperature",
|
||||
"data": {
|
||||
"step": "0.5",
|
||||
"start": "10",
|
||||
|
||||
@@ -122,16 +122,29 @@
|
||||
"$ref": "#/$defs/tap_action",
|
||||
"properties": {
|
||||
"service": {
|
||||
"$ref": "#/$defs/service"
|
||||
"$ref": "#/$defs/action",
|
||||
"deprecated": true
|
||||
},
|
||||
"action": {
|
||||
"$ref": "#/$defs/action"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"title": "Your services's parameters",
|
||||
"title": "Your action'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"
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"service"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"enabled": {
|
||||
@@ -182,7 +195,7 @@
|
||||
"type": "tap",
|
||||
"name": "Example",
|
||||
"tap_action": {
|
||||
"service": "notify.notify",
|
||||
"action": "notify.notify",
|
||||
"data": {
|
||||
"message": "Example"
|
||||
}
|
||||
@@ -204,7 +217,7 @@
|
||||
"$ref": "#/$defs/content"
|
||||
},
|
||||
"service": {
|
||||
"$ref": "#/$defs/service",
|
||||
"$ref": "#/$defs/action",
|
||||
"deprecated": true,
|
||||
"title": "Schema change:",
|
||||
"description": "Use 'tap_action' instead to mirror Home Assistant."
|
||||
@@ -213,16 +226,29 @@
|
||||
"$ref": "#/$defs/tap_action",
|
||||
"properties": {
|
||||
"service": {
|
||||
"$ref": "#/$defs/service"
|
||||
"$ref": "#/$defs/action",
|
||||
"deprecated": true
|
||||
},
|
||||
"action": {
|
||||
"$ref": "#/$defs/action"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"title": "Your services's parameters",
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"service"
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"service"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"enabled": {
|
||||
@@ -294,8 +320,8 @@
|
||||
"name": "Example",
|
||||
"entity": "light.example",
|
||||
"attribute": "brightness",
|
||||
"service": "light.turn_on",
|
||||
"service_attribute": "brightness",
|
||||
"action": "light.turn_on",
|
||||
"action_attribute": "brightness",
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"step": 1
|
||||
@@ -304,8 +330,8 @@
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "input_number.example",
|
||||
"service": "input_number.set_value",
|
||||
"service_attribute": "value",
|
||||
"action": "input_number.set_value",
|
||||
"action_attribute": "value",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
@@ -314,8 +340,8 @@
|
||||
"type": "numeric",
|
||||
"name": "Example",
|
||||
"entity": "number.example",
|
||||
"service": "number.set_value",
|
||||
"service_attribute": "value",
|
||||
"action": "number.set_value",
|
||||
"action_attribute": "value",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
@@ -325,8 +351,8 @@
|
||||
"name": "Example",
|
||||
"entity": "fan.example",
|
||||
"attribute": "percentage",
|
||||
"service": "fan.set_percentage",
|
||||
"service_attribute": "percentage",
|
||||
"action": "fan.set_percentage",
|
||||
"action_attribute": "percentage",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
@@ -336,8 +362,8 @@
|
||||
"name": "Example",
|
||||
"entity": "valve.example",
|
||||
"attribute": "position",
|
||||
"service": "valve.set_valve_position",
|
||||
"service_attribute": "position",
|
||||
"action": "valve.set_valve_position",
|
||||
"action_attribute": "position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
@@ -347,8 +373,8 @@
|
||||
"name": "Example",
|
||||
"entity": "cover.example",
|
||||
"attribute": "position",
|
||||
"service": "cover.set_position",
|
||||
"service_attribute": "position",
|
||||
"action": "cover.set_position",
|
||||
"action_attribute": "position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
@@ -358,8 +384,8 @@
|
||||
"name": "Example",
|
||||
"entity": "cover.example",
|
||||
"attribute": "tilt_position",
|
||||
"service": "cover.set_tilt_position",
|
||||
"service_attribute": "tilt_position",
|
||||
"action": "cover.set_tilt_position",
|
||||
"action_attribute": "tilt_position",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
@@ -369,8 +395,8 @@
|
||||
"name": "Example",
|
||||
"entity": "media_player.example",
|
||||
"attribute": "volume_level",
|
||||
"service": "media_player.volume_set",
|
||||
"service_attribute": "volume_level",
|
||||
"action": "media_player.volume_set",
|
||||
"action_attribute": "volume_level",
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.01
|
||||
@@ -380,8 +406,8 @@
|
||||
"name": "Example",
|
||||
"entity": "climate.example",
|
||||
"attribute": "temperature",
|
||||
"service": "climate.set_temperature",
|
||||
"service_attribute": "temperature",
|
||||
"action": "climate.set_temperature",
|
||||
"action_attribute": "temperature",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
@@ -435,12 +461,12 @@
|
||||
"type": "string",
|
||||
"title": "Attribute on the entity with the current numeric value. To use the state of the entity, do not specify."
|
||||
},
|
||||
"service": {
|
||||
"$ref": "#/$defs/service"
|
||||
"action": {
|
||||
"$ref": "#/$defs/action"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"type": "string",
|
||||
"title": "Attribute on the service data for the value to set."
|
||||
"title": "Attribute on the action data for the value to set."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -450,8 +476,8 @@
|
||||
"max",
|
||||
"step",
|
||||
"entity",
|
||||
"service",
|
||||
"service_attribute"
|
||||
"action",
|
||||
"action_attribute"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
@@ -473,10 +499,10 @@
|
||||
"attribute": {
|
||||
"const": "brightness"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "light.turn_on"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "brightness"
|
||||
},
|
||||
"min": {
|
||||
@@ -501,10 +527,10 @@
|
||||
"attribute": {
|
||||
"const": "value"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "input_number.set_value"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "value"
|
||||
}
|
||||
},
|
||||
@@ -527,10 +553,10 @@
|
||||
"attribute": {
|
||||
"const": "value"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "number.set_value"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "value"
|
||||
}
|
||||
},
|
||||
@@ -553,10 +579,10 @@
|
||||
"attribute": {
|
||||
"const": "percentage"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "fan.set_percentage"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "percentage"
|
||||
},
|
||||
"min": {
|
||||
@@ -580,10 +606,10 @@
|
||||
"attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "valve.set_valve_position"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"min": {
|
||||
@@ -607,10 +633,10 @@
|
||||
"attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "cover.set_position"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "position"
|
||||
},
|
||||
"min": {
|
||||
@@ -634,10 +660,10 @@
|
||||
"attribute": {
|
||||
"const": "tilt_position"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "cover.set_tilt_position"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "tilt_position"
|
||||
},
|
||||
"min": {
|
||||
@@ -661,10 +687,10 @@
|
||||
"attribute": {
|
||||
"const": "volume_level"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "media_player.volume_set"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "volume_level"
|
||||
},
|
||||
"min": {
|
||||
@@ -688,10 +714,10 @@
|
||||
"attribute": {
|
||||
"const": "temperature"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"const": "climate.set_temperature"
|
||||
},
|
||||
"service_attribute": {
|
||||
"action_attribute": {
|
||||
"const": "temperature"
|
||||
}
|
||||
}
|
||||
@@ -745,9 +771,9 @@
|
||||
"title": "Home Assistant entity name",
|
||||
"pattern": "^[^.]+\\.[^.]+$"
|
||||
},
|
||||
"service": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"title": "Home Assistant service name",
|
||||
"title": "Home Assistant action name",
|
||||
"pattern": "^[^.]+\\.[^.]+$"
|
||||
},
|
||||
"tap_action": {
|
||||
|
||||
@@ -11,7 +11,7 @@ A simple example using a scene as a `tap` menu item.
|
||||
"name": "Telly Scene",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "scene.turn_on"
|
||||
"action": "scene.turn_on"
|
||||
}
|
||||
},
|
||||
```
|
||||
@@ -62,7 +62,7 @@ Note that for notify events, you _must_ not supply an `entity_id` or the API cal
|
||||
"name": "Message",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "notify.mobile_app_on_phone",
|
||||
"action": "notify.mobile_app_on_phone",
|
||||
"data": {
|
||||
"title": "This is a title",
|
||||
"message": "This is the message"
|
||||
@@ -73,9 +73,9 @@ Note that for notify events, you _must_ not supply an `entity_id` or the API cal
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Be careful with the value of the `service` field.
|
||||
> Be careful with the value of the `action` field.
|
||||
|
||||
Note that the `service` field will need to be a locally custom `script.<something>` as soon as any `data` fields are populated and not something more generic like `script.turn_on`. If the `service` field is wrong, the application will fail with a [`Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE`](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html) error in the response from your HomeAssistant and show the error message as _"No JSON returned from HTTP request"_ on your device. In the [web-based editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) you can use the standard developer tools to observe an `HTTP 400` error which the application does not see. Here we are limited by the [Garmin Connect IQ](https://developer.garmin.com/connect-iq/overview/) software development kit (SDK). We do not have enough information at the point of execution in the application to determine the cause of the error. Nor is there an immediately obvious way of identifying this issue using the JSON schema checks.
|
||||
Note that the `action` field will need to be a locally custom `script.<something>` as soon as any `data` fields are populated and not something more generic like `script.turn_on`. If the `action` field is wrong, the application will fail with a [`Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE`](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html) error in the response from your HomeAssistant and show the error message as _"No JSON returned from HTTP request"_ on your device. In the [web-based editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) you can use the standard developer tools to observe an `HTTP 400` error which the application does not see. Here we are limited by the [Garmin Connect IQ](https://developer.garmin.com/connect-iq/overview/) software development kit (SDK). We do not have enough information at the point of execution in the application to determine the cause of the error. Nor is there an immediately obvious way of identifying this issue using the JSON schema checks.
|
||||
|
||||
## Exit on Tap
|
||||
|
||||
@@ -87,7 +87,7 @@ You can choose individual items that will quit after they have completed their a
|
||||
"name": "Turn off Stuff",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "automation.trigger"
|
||||
"action": "automation.trigger"
|
||||
},
|
||||
"exit": true
|
||||
}
|
||||
@@ -103,7 +103,7 @@ If you would like to temporarily disable an item in your menu, e.g. for seasonal
|
||||
"name": "Turn off Stuff",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "automation.trigger"
|
||||
"action": "automation.trigger"
|
||||
},
|
||||
"enabled": false
|
||||
}
|
||||
|
||||
@@ -48,12 +48,12 @@ switch:
|
||||
friendly_name: <name>
|
||||
value_template: <value>
|
||||
turn_on:
|
||||
service: <service>
|
||||
action: <action>
|
||||
data:
|
||||
entity_id: <entity>
|
||||
<attribute>: <value>
|
||||
turn_off:
|
||||
service: <service>
|
||||
action: <action>
|
||||
data:
|
||||
entity_id: <entity>
|
||||
<attribute>: <value>
|
||||
@@ -90,11 +90,11 @@ switch:
|
||||
friendly_name: Cover
|
||||
value_template: "{{ is_state('cover.cover', 'open') }}"
|
||||
turn_on:
|
||||
service: cover.open_cover
|
||||
action: cover.open_cover
|
||||
data:
|
||||
entity_id: cover.cover
|
||||
turn_off:
|
||||
service: cover.close_cover
|
||||
action: cover.close_cover
|
||||
data:
|
||||
entity_id: cover.cover
|
||||
```
|
||||
|
||||
@@ -116,7 +116,7 @@ Note: Only when you use the `tap_action` field do you also need to include the `
|
||||
"type": "tap",
|
||||
"content": "{% if is_state('binary_sensor.garage_connected', 'on') %}{{state_translated('cover.garage_door')}} - {{state_attr('cover.garage_door', 'current_position')}}%{%else%}Unconnected{% endif %}",
|
||||
"tap_action": {
|
||||
"service": "cover.toggle",
|
||||
"action": "cover.toggle",
|
||||
"pin": true
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
||||
"type": "tap",
|
||||
"content": "{% if not (is_state('light.green_house', 'off') or is_state('light.green_house', 'unavailable')) %}{{ (((state_attr('light.green_house', 'brightness') | float) / 255 * 100) | round(0)) | int }}%{% else %}Off{% endif %}",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"data": {
|
||||
"brightness_pct": 12
|
||||
}
|
||||
@@ -184,7 +184,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
||||
"name": "LEDs 1",
|
||||
"type": "tap",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"data": {
|
||||
"brightness_pct": 37
|
||||
}
|
||||
@@ -196,7 +196,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
||||
"type": "tap",
|
||||
"content": "{% if not (is_state('light.green_house', 'off') or is_state('light.green_house', 'unavailable')) %}{{ (((state_attr('light.green_house', 'brightness') | float) / 255 * 100) | round(0)) | int }}%{% else %}Off{% endif %}",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"data": {
|
||||
"brightness_pct": 62
|
||||
}
|
||||
@@ -208,7 +208,7 @@ An example of a dimmer light with 4 brightness settings 0..3. Here our light wor
|
||||
"type": "tap",
|
||||
"content": "{% if not (is_state('light.green_house', 'off') or is_state('light.green_house', 'unavailable')) %}{{ (((state_attr('light.green_house', 'brightness') | float) / 255 * 100) | round(0))| int }}%{% else %}Off{% endif %}",
|
||||
"tap_action": {
|
||||
"service": "light.turn_on",
|
||||
"action": "light.turn_on",
|
||||
"data": {
|
||||
"brightness_pct": 87
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class HomeAssistantMenuItemFactory {
|
||||
//! @param label Menu item label.
|
||||
//! @param entity_id Home Assistant Entity ID (optional)
|
||||
//! @param template Template for Home Assistant to render (optional)
|
||||
//! @param service Template for Home Assistant to render (optional)
|
||||
//! @param action Action to run on Home Assistant (optional)
|
||||
//! @param data Sourced from the menu JSON, this is the `data` field from the `tap_action` field.
|
||||
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||
//
|
||||
@@ -107,7 +107,7 @@ class HomeAssistantMenuItemFactory {
|
||||
label as Lang.String or Lang.Symbol,
|
||||
entity_id as Lang.String?,
|
||||
template as Lang.String?,
|
||||
service as Lang.String?,
|
||||
action as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
options as {
|
||||
:exit as Lang.Boolean,
|
||||
@@ -126,12 +126,12 @@ class HomeAssistantMenuItemFactory {
|
||||
for (var i = 0; i < keys.size(); i++) {
|
||||
options.put(keys[i], mMenuItemOptions.get(keys[i]));
|
||||
}
|
||||
if (service != null) {
|
||||
if (action != null) {
|
||||
options.put(:icon, mTapTypeIcon);
|
||||
return new HomeAssistantTapMenuItem(
|
||||
label,
|
||||
template,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
options,
|
||||
mHomeAssistantService
|
||||
@@ -157,7 +157,7 @@ class HomeAssistantMenuItemFactory {
|
||||
label as Lang.String or Lang.Symbol,
|
||||
entity_id as Lang.String?,
|
||||
template as Lang.String?,
|
||||
service as Lang.String?,
|
||||
action as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
options as {
|
||||
:exit as Lang.Boolean,
|
||||
@@ -183,7 +183,7 @@ class HomeAssistantMenuItemFactory {
|
||||
return new HomeAssistantNumericMenuItem(
|
||||
label,
|
||||
template,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
options,
|
||||
mHomeAssistantService
|
||||
|
||||
@@ -23,7 +23,7 @@ using Toybox.Graphics;
|
||||
//
|
||||
class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
private var mHomeAssistantService as HomeAssistantService?;
|
||||
private var mService as Lang.String?;
|
||||
private var mAction as Lang.String?;
|
||||
private var mConfirm as Lang.Boolean;
|
||||
private var mExit as Lang.Boolean;
|
||||
private var mPin as Lang.Boolean;
|
||||
@@ -36,11 +36,11 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param template Menu item template.
|
||||
//! @param service Menu item service.
|
||||
//! @param data Data to supply to the service call.
|
||||
//! @param exit Should the service call complete and then exit?
|
||||
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the service call be protected with a PIN for some low level of security?
|
||||
//! @param action Menu item action.
|
||||
//! @param data Data to supply to the action call.
|
||||
//! @param exit Should the action call complete and then exit?
|
||||
//! @param confirm Should the action call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the action call be protected with a PIN for some low level of security?
|
||||
//! @param icon Icon to use for the menu item.
|
||||
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||
//! @param haService Shared Home Assistant service object that will perform the required call. Only
|
||||
@@ -49,7 +49,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
function initialize(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
template as Lang.String,
|
||||
service as Lang.String?,
|
||||
action as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
options as {
|
||||
:alignment as WatchUi.MenuItem.Alignment,
|
||||
@@ -60,7 +60,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
}?,
|
||||
haService as HomeAssistantService
|
||||
) {
|
||||
mService = service;
|
||||
mAction = action;
|
||||
mData = data;
|
||||
mExit = options[:exit];
|
||||
mConfirm = options[:confirm];
|
||||
@@ -80,7 +80,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
|
||||
|
||||
|
||||
function callService() as Void {
|
||||
function callAction() as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
var pin = Settings.getPin();
|
||||
@@ -106,8 +106,8 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
WatchUi.pushView(
|
||||
dialog,
|
||||
new WifiLteExecutionConfirmDelegate({
|
||||
:type => "service",
|
||||
:service => mService,
|
||||
:type => "action",
|
||||
:action => mAction,
|
||||
:data => mData,
|
||||
:exit => mExit,
|
||||
}, dialog),
|
||||
@@ -137,7 +137,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
|
||||
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
|
||||
//
|
||||
function onConfirm(b as Lang.Boolean) as Void {
|
||||
mHomeAssistantService.call(mService, {"entity_id" => mData.get("entity_id").toString(),mData.get("valueLabel").toString() => mValue}, mExit);
|
||||
mHomeAssistantService.call(mAction, {"entity_id" => mData.get("entity_id").toString(),mData.get("valueLabel").toString() => mValue}, mExit);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class HomeAssistantService {
|
||||
break;
|
||||
|
||||
case 200:
|
||||
// System.println("HomeAssistantService onReturnCall(): Service executed.");
|
||||
// System.println("HomeAssistantService onReturnCall(): Action executed.");
|
||||
getApp().forceStatusUpdates();
|
||||
var d = data as Lang.Array;
|
||||
var toast = WatchUi.loadResource($.Rez.Strings.Executed) as Lang.String;
|
||||
@@ -118,13 +118,13 @@ class HomeAssistantService {
|
||||
}
|
||||
}
|
||||
|
||||
//! Invoke a service call for a menu item.
|
||||
//! Invoke a action call for a menu item.
|
||||
//!
|
||||
//! @param service The Home Assistant service to be run, e.g. from the JSON `service` field.
|
||||
//! @param data Data to be supplied to the service call.
|
||||
//! @param action The Home Assistant action to be run, e.g. from the JSON `action` field.
|
||||
//! @param data Data to be supplied to the action call.
|
||||
//
|
||||
function call(
|
||||
service as Lang.String,
|
||||
action as Lang.String,
|
||||
data as Lang.Dictionary?,
|
||||
exit as Lang.Boolean
|
||||
) as Void {
|
||||
@@ -136,8 +136,8 @@ class HomeAssistantService {
|
||||
WatchUi.pushView(
|
||||
dialog,
|
||||
new WifiLteExecutionConfirmDelegate({
|
||||
:type => "service",
|
||||
:service => service,
|
||||
:type => "action",
|
||||
:action => action,
|
||||
:data => data,
|
||||
:exit => exit,
|
||||
}, dialog),
|
||||
@@ -151,9 +151,9 @@ class HomeAssistantService {
|
||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
|
||||
} else {
|
||||
// Can't use null for substring() parameters due to API version level.
|
||||
var url = Settings.getApiUrl() + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, service.length());
|
||||
var url = Settings.getApiUrl() + "/services/" + action.substring(0, action.find(".")) + "/" + action.substring(action.find(".")+1, action.length());
|
||||
// System.println("HomeAssistantService call() URL=" + url);
|
||||
// System.println("HomeAssistantService call() service=" + service);
|
||||
// System.println("HomeAssistantService call() action=" + action);
|
||||
|
||||
var entity_id = "";
|
||||
if (data != null) {
|
||||
|
||||
@@ -50,9 +50,9 @@ class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
|
||||
var url;
|
||||
|
||||
switch (type) {
|
||||
case "service":
|
||||
var service = WifiLteExecutionConfirmDelegate.mCommandData[:service];
|
||||
url = Settings.getApiUrl() + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, service.length());
|
||||
case "action":
|
||||
var action = WifiLteExecutionConfirmDelegate.mCommandData[:action];
|
||||
url = Settings.getApiUrl() + "/services/" + action.substring(0, action.find(".")) + "/" + action.substring(action.find(".")+1, action.length());
|
||||
var entity_id = "";
|
||||
if (data != null) {
|
||||
entity_id = data.get("entity_id");
|
||||
@@ -78,7 +78,7 @@ class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
|
||||
private function performRequest(url as Lang.String, data as Lang.Dictionary?) {
|
||||
Communications.makeWebRequest(
|
||||
url,
|
||||
data, // May include {"entity_id": xxxx} for service calls
|
||||
data, // May include {"entity_id": xxxx} for action calls
|
||||
{
|
||||
:method => Communications.HTTP_REQUEST_METHOD_POST,
|
||||
:headers => Settings.augmentHttpHeaders({
|
||||
|
||||
@@ -21,7 +21,7 @@ using Toybox.Graphics;
|
||||
//
|
||||
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
private var mHomeAssistantService as HomeAssistantService;
|
||||
private var mService as Lang.String?;
|
||||
private var mAction as Lang.String?;
|
||||
private var mConfirm as Lang.Boolean;
|
||||
private var mExit as Lang.Boolean;
|
||||
private var mPin as Lang.Boolean;
|
||||
@@ -31,11 +31,11 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param template Menu item template.
|
||||
//! @param service Menu item service.
|
||||
//! @param data Data to supply to the service call.
|
||||
//! @param exit Should the service call complete and then exit?
|
||||
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the service call be protected with a PIN for some low level of security?
|
||||
//! @param action Menu item action.
|
||||
//! @param data Data to supply to the action call.
|
||||
//! @param exit Should the action call complete and then exit?
|
||||
//! @param confirm Should the action call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the action call be protected with a PIN for some low level of security?
|
||||
//! @param icon Icon to use for the menu item.
|
||||
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||
//! @param haService Shared Home Assistant service object that will perform the required call. Only
|
||||
@@ -44,7 +44,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
function initialize(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
template as Lang.String,
|
||||
service as Lang.String?,
|
||||
action as Lang.String?,
|
||||
data as Lang.Dictionary?,
|
||||
options as {
|
||||
:alignment as WatchUi.MenuItem.Alignment,
|
||||
@@ -65,16 +65,16 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
);
|
||||
|
||||
mHomeAssistantService = haService;
|
||||
mService = service;
|
||||
mAction = action;
|
||||
mData = data;
|
||||
mExit = options[:exit];
|
||||
mConfirm = options[:confirm];
|
||||
mPin = options[:pin];
|
||||
}
|
||||
|
||||
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
|
||||
//! Call a Home Assistant action only after checks have been done for confirmation or PIN entry.
|
||||
//
|
||||
function callService() as Void {
|
||||
function callAction() as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
var pin = Settings.getPin();
|
||||
@@ -100,10 +100,10 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
WatchUi.pushView(
|
||||
dialog,
|
||||
new WifiLteExecutionConfirmDelegate({
|
||||
:type => "service",
|
||||
:service => mService,
|
||||
:data => mData,
|
||||
:exit => mExit,
|
||||
:type => "action",
|
||||
:action => mAction,
|
||||
:data => mData,
|
||||
:exit => mExit,
|
||||
}, dialog),
|
||||
WatchUi.SLIDE_LEFT
|
||||
);
|
||||
@@ -129,8 +129,8 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
|
||||
//
|
||||
function onConfirm(b as Lang.Boolean) as Void {
|
||||
if (mService != null) {
|
||||
mHomeAssistantService.call(mService, mData, mExit);
|
||||
if (mAction != null) {
|
||||
mHomeAssistantService.call(mAction, mData, mExit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param template Menu item template.
|
||||
//! @param data Data to supply to the service call.
|
||||
//! @param data Data to supply to the action call.
|
||||
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
|
||||
//
|
||||
function initialize(
|
||||
@@ -72,10 +72,12 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
//
|
||||
private function setUiToggle(state as Null or Lang.String) as Void {
|
||||
if (state != null) {
|
||||
if (state.equals("on") && !isEnabled()) {
|
||||
setEnabled(true);
|
||||
} else if (state.equals("off") && isEnabled()) {
|
||||
if (state.equals("unavailable" || "unknown")) {
|
||||
return;
|
||||
} else if ((state.equals("off") || state.equals("closed")) && isEnabled()) {
|
||||
setEnabled(false);
|
||||
} else if (!isEnabled()) {
|
||||
setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +297,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
|
||||
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
|
||||
//
|
||||
function callService(b as Lang.Boolean) as Void {
|
||||
function callAction(b as Lang.Boolean) as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
// Undo the toggle
|
||||
@@ -377,7 +379,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
//! @param id The entity ID, e.g., `"switch.kitchen"`.
|
||||
//! @param s Desired state: `true` for "turn_on", `false` for "turn_off".
|
||||
//!
|
||||
//! @return Full service URL string.
|
||||
//! @return Full action URL string.
|
||||
//
|
||||
private static function getUrl(id as Lang.String, s as Lang.Boolean) as Lang.String {
|
||||
var url = Settings.getApiUrl() + "/services/";
|
||||
|
||||
@@ -47,7 +47,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
var content = items[i].get("content") as Lang.String?;
|
||||
var entity = items[i].get("entity") as Lang.String?;
|
||||
var tap_action = items[i].get("tap_action") as Lang.Dictionary?;
|
||||
var service = items[i].get("service") as Lang.String?; // Deprecated schema
|
||||
var action = items[i].get("service") as Lang.String?; // Deprecated schema
|
||||
var confirm = false as Lang.Boolean?;
|
||||
var pin = false as Lang.Boolean?;
|
||||
var data = null as Lang.Dictionary?;
|
||||
@@ -60,7 +60,10 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
exit = items[i].get("exit"); // Optional
|
||||
}
|
||||
if (tap_action != null) {
|
||||
service = tap_action.get("service");
|
||||
action = tap_action.get("service"); // Deprecated
|
||||
if (tap_action.get("action") != null) {
|
||||
action = tap_action.get("action"); // Optional
|
||||
}
|
||||
data = tap_action.get("data"); // Optional
|
||||
if (tap_action.get("confirm") != null) {
|
||||
confirm = tap_action.get("confirm"); // Optional
|
||||
@@ -81,12 +84,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
:pin => pin
|
||||
}
|
||||
));
|
||||
} else if (type.equals("tap") && service != null) {
|
||||
} else if (type.equals("tap") && action != null) {
|
||||
addItem(HomeAssistantMenuItemFactory.create().tap(
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => exit,
|
||||
@@ -103,7 +106,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => false,
|
||||
@@ -117,7 +120,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => exit,
|
||||
@@ -126,12 +129,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
}
|
||||
));
|
||||
}
|
||||
} else if (type.equals("numeric") && service != null) {
|
||||
} else if (type.equals("numeric") && action != null) {
|
||||
addItem(HomeAssistantMenuItemFactory.create().numeric(
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => exit,
|
||||
@@ -145,7 +148,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
name,
|
||||
entity,
|
||||
content,
|
||||
service,
|
||||
action,
|
||||
data,
|
||||
{
|
||||
:exit => false,
|
||||
@@ -263,11 +266,11 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||
var haToggleItem = item as HomeAssistantToggleMenuItem;
|
||||
// System.println(haToggleItem.getLabel() + " " + haToggleItem.getId() + " " + haToggleItem.isEnabled());
|
||||
haToggleItem.callService(haToggleItem.isEnabled());
|
||||
haToggleItem.callAction(haToggleItem.isEnabled());
|
||||
} else if (item instanceof HomeAssistantTapMenuItem) {
|
||||
var haItem = item as HomeAssistantTapMenuItem;
|
||||
// System.println(haItem.getLabel() + " " + haItem.getId());
|
||||
haItem.callService();
|
||||
haItem.callAction();
|
||||
} else if (item instanceof HomeAssistantNumericMenuItem) {
|
||||
var haItem = item as HomeAssistantNumericMenuItem;
|
||||
// System.println(haItem.getLabel() + " " + haItem.getId());
|
||||
|
||||
@@ -25,7 +25,7 @@ using Toybox.Timer;
|
||||
class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||
public static var mCommandData as {
|
||||
:type as Lang.String,
|
||||
:service as Lang.String?,
|
||||
:action as Lang.String?,
|
||||
:data as Lang.Dictionary?,
|
||||
:url as Lang.String?,
|
||||
:id as Lang.Number?,
|
||||
@@ -40,8 +40,8 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||
//!
|
||||
//! @param options A dictionary describing the command to be executed:<br>
|
||||
//! `{`<br>
|
||||
//!   `:type: as Lang.String,` // The command type, either `"service"` or `"entity"`.<br>
|
||||
//!   `:service: as Lang.String?,` // (For type `"service"`) The Home Assistant service to call (e.g., "light.turn_on").<br>
|
||||
//!   `:type: as Lang.String,` // The command type, either `"action"` or `"entity"`.<br>
|
||||
//!   `:action: as Lang.String?,` // (For type `"action"`) The Home Assistant action to call (e.g., "light.turn_on").<br>
|
||||
//!   `:url: as Lang.Dictionary?,` // (For type `"entity"`) The full Home Assistant entity API URL.<br>
|
||||
//!   `:callback: as Lang.String?,` // (For type `"entity"`) A callback method (Method<data as Dictionary>) to handle the response.<br>
|
||||
//!   `:data: as Lang.Method?,` // (Optional) A dictionary of data to send with the request.<br>
|
||||
@@ -52,7 +52,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||
function initialize(
|
||||
cOptions as {
|
||||
:type as Lang.String,
|
||||
:service as Lang.String?,
|
||||
:action as Lang.String?,
|
||||
:data as Lang.Dictionary?,
|
||||
:url as Lang.String?,
|
||||
:callback as Lang.Method?,
|
||||
@@ -73,7 +73,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
|
||||
mConfirmationView = view;
|
||||
mCommandData = {
|
||||
:type => cOptions[:type],
|
||||
:service => cOptions[:service],
|
||||
:action => cOptions[:action],
|
||||
:data => cOptions[:data],
|
||||
:url => cOptions[:url],
|
||||
:callback => cOptions[:callback],
|
||||
|
||||
@@ -67,7 +67,7 @@ class HomeAssistantNumericPicker extends WatchUi.Picker {
|
||||
//! @return true if user is done, false otherwise
|
||||
public function onConfirm(value as Lang.String) as Void {
|
||||
mItem.setValue(value);
|
||||
mItem.callService();
|
||||
mItem.callAction();
|
||||
}
|
||||
|
||||
|
||||
|
||||
30
web/main.js
30
web/main.js
@@ -101,12 +101,12 @@ async function get_areas() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all services in HomeAssistant.
|
||||
* Get all actions in HomeAssistant.
|
||||
* @returns {Promise<[string, { name: string; description: string; fields:
|
||||
* Record<string, { name: string; description: string; example: string;
|
||||
* selector: unknown; required?: boolean }> }][]>} [id, data]
|
||||
*/
|
||||
async function get_services() {
|
||||
async function get_actions() {
|
||||
try {
|
||||
const res = await fetch(api_url + '/services', {
|
||||
method: 'GET',
|
||||
@@ -122,15 +122,15 @@ async function get_services() {
|
||||
document.querySelector('#api_url').classList.remove('invalid');
|
||||
document.querySelector('#api_token').classList.remove('invalid');
|
||||
const data = await res.json();
|
||||
const services = [];
|
||||
const actions = [];
|
||||
for (const d of data) {
|
||||
for (const service in d.services) {
|
||||
services.push([`${d.domain}.${service}`, d.services[service]]);
|
||||
for (const action in d.services) {
|
||||
actions.push([`${d.domain}.${action}`, d.services[action]]);
|
||||
}
|
||||
}
|
||||
return services;
|
||||
return actions;
|
||||
} catch (e) {
|
||||
console.error('Error fetching services:', e);
|
||||
console.error('Error fetching actions:', e);
|
||||
document.querySelector('#api_url').classList.add('invalid');
|
||||
return [];
|
||||
}
|
||||
@@ -154,11 +154,11 @@ async function get_schema() {
|
||||
* @param {Record<string, string>} areas
|
||||
* @param {[string, { name: string; description: string; fields:
|
||||
* Record<string, { name: string; description: string; example: string;
|
||||
* selector: unknown; required?: boolean }> }][]} services
|
||||
* selector: unknown; required?: boolean }> }][]} actions
|
||||
* @param {{}} schema
|
||||
* @returns {Promise<{}>}
|
||||
*/
|
||||
async function generate_schema(entities, devices, areas, services, schema) {
|
||||
async function generate_schema(entities, devices, areas, actions, schema) {
|
||||
schema.$defs.entity = {
|
||||
enum: Object.keys(entities),
|
||||
};
|
||||
@@ -170,7 +170,7 @@ async function generate_schema(entities, devices, areas, services, schema) {
|
||||
};
|
||||
|
||||
const oneOf = [];
|
||||
for (const [id, data] of services) {
|
||||
for (const [id, data] of actions) {
|
||||
const i_properties = {
|
||||
service: {
|
||||
title: data.name,
|
||||
@@ -428,22 +428,22 @@ let entities;
|
||||
let devices;
|
||||
/** @type {Awaited<ReturnType<typeof get_areas>>} */
|
||||
let areas;
|
||||
/** @type {Awaited<ReturnType<typeof get_services>>} */
|
||||
let services;
|
||||
/** @type {Awaited<ReturnType<typeof get_actions>>} */
|
||||
let actions;
|
||||
let schema;
|
||||
async function loadSchema() {
|
||||
[entities, devices, areas, services, schema] = await Promise.all([
|
||||
[entities, devices, areas, actions, schema] = await Promise.all([
|
||||
get_entities(),
|
||||
get_devices(),
|
||||
get_areas(),
|
||||
get_services(),
|
||||
get_actions(),
|
||||
get_schema(),
|
||||
]);
|
||||
if (window.makeMarkers) {
|
||||
window.makeMarkers();
|
||||
}
|
||||
try {
|
||||
schema = await generate_schema(entities, devices, areas, services, schema);
|
||||
schema = await generate_schema(entities, devices, areas, actions, schema);
|
||||
} catch {}
|
||||
console.log(schema);
|
||||
if (window.m && window.modelUri) {
|
||||
|
||||
Reference in New Issue
Block a user