Compare commits

...

32 Commits

Author SHA1 Message Date
Philip Abbey
d1f6f6d9d2 Deduped picker variable 2025-10-29 15:14:30 +00:00
Philip Abbey
35333f4d75 Merge branch 'pr/298' into Picker-formatter 2025-10-29 15:13:13 +00:00
Philip Abbey
a5ddb65512 Suggested code changes from philipabbey
1. attribute is option, so needs a different template in the API call when absent.
2. Automatically derive the format string from the picker step value for any precision of step.
3. Changed all Lang.String representations of numbers to Lang.Number or Lang.Float. I'm keen to remove the use of strings to hold a numeric value.
4. Tidied up and completed some code comments.
5. Adjusted the JSON schema definition. This is still not finished as the 'picker' object is required for 'numeric' menu items and must not be present for the others. Additional schema changes are required for greater precision.
6. Moved fields over from 'data' to 'picker'.
2025-10-29 14:26:02 +00:00
thmichel
b0fa10b2c1 Fixed typo in formatsgtring and error if numeric template didn't return a value 2025-10-29 14:41:32 +01:00
thmichel
6a0ec34cdb Using a picker object to configure the picker now, deriving display format from steps. 2025-10-29 13:54:14 +01:00
thmichel
2cd171637c Reworked numericMenuItem to be able to display a different conten in the sublabel than jus a number. 2025-10-25 21:26:44 +02:00
thmichel
264b160fdf Merge pull request #3 from house-of-abbey/numeric-item-json-schema
Update schema to support numeric items
2025-10-23 12:10:20 +02:00
Joseph Abbey
5bdab41d8b Fix examples lists 2025-10-21 19:10:35 +01:00
Joseph Abbey
85080f5d46 2025-10-21 10:07:43 +01:00
Joseph Abbey
35e0fe26d0 Amendments 2025-10-21 09:05:15 +01:00
Joseph Abbey
427c1834a8 2025-10-21 09:03:49 +01:00
Joseph Abbey
4fbe4135b1 Update schema to support numeric items 2025-10-19 21:23:34 +01:00
thmichel
edef4ef464 Merge branch 'main' into main
Signed-off-by: thmichel <thomas.michel@vermessung-michel.de>
2025-10-19 16:56:09 +02:00
Philip Abbey
92e4278332 Update device reference link in manifest.xml
Signed-off-by: Philip Abbey <philipabbey@users.noreply.github.com>
2025-10-18 17:28:18 +01:00
thmichel
4348c899ae Fixed crash when value was int instead of float 2025-10-16 13:34:39 +02:00
thmichel
b34291e41f Code Cleanup 2025-10-13 15:57:00 +02:00
thmichel
7453b40cb1 Corrected settings 2025-10-13 15:53:55 +02:00
thmichel
483603a44d Updated products 2025-10-13 15:40:49 +02:00
thmichel
bc3271ba63 Merge pull request #2 from thmichel/Picker
Picker
2025-10-13 15:31:13 +02:00
thmichel
5ccd1be4e7 Merge branch 'main' into Picker
Signed-off-by: thmichel <thomas.michel@vermessung-michel.de>
2025-10-13 15:30:49 +02:00
thmichel
7871334b4a Updated Readme and Mianifest 2025-10-13 13:20:50 +02:00
thmichel
2f3ee236e8 Removed credentials from settings 2025-10-11 22:30:55 +02:00
thmichel
6609fed35d REmoved credentials from settings 2025-10-11 22:28:50 +02:00
thmichel
d68aecd19c Merge pull request #1 from thmichel:Picker
Picker
2025-10-11 22:01:51 +02:00
thmichel
9a61c9ce77 Code cleanup 2025-10-11 22:00:43 +02:00
thmichel
2981893af7 Using a Picker to set new value 2025-10-11 21:51:50 +02:00
thmichel
52e2efddd8 Added numeric Menu Item 2025-10-10 11:46:20 +02:00
Philip Abbey
cb382d820f Removed files marked as unintended for committing. 2025-10-01 17:54:35 +01:00
Philip Abbey
236b09d7f6 293 add support for new devices (#295)
Device update. Note dodgy Edge 850 device.
2025-10-01 17:47:21 +01:00
Philip Abbey
06e7c89b60 Amended documentation for Edge devices 2025-09-30 16:58:34 +01:00
Philip Abbey
6fc6be2eef Update HomeAssistantApp.mc
Removed debug print statements.
2025-09-30 16:53:04 +01:00
Philip Abbey
ac97a8af0d New devices and SDK demanded code updates
The SDK upgrade called for more careful specification of "or Null" on some fields in classes. The new devices have been added with their resource needs. Need to check if there's an issue with Edge 850 device.
2025-09-29 21:00:13 +01:00
19 changed files with 1384 additions and 247 deletions

8
.vscode/launch.json vendored
View File

@@ -4,14 +4,6 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
},
{ {
"type": "monkeyc", "type": "monkeyc",
"request": "launch", "request": "launch",

View File

@@ -50,3 +50,4 @@
| 3.2 | Only enable or disable sensors on HomeAssistant when the background service options is changed, i.e. do not call the API to enable on start up every time. | | 3.2 | Only enable or disable sensors on HomeAssistant when the background service options is changed, i.e. do not call the API to enable on start up every time. |
| 3.3 | Providing automatic detection for menu definition updates, but still requires an application restart. | | 3.3 | Providing automatic detection for menu definition updates, but still requires an application restart. |
| 3.4 | Fixed a bug where templates failed to display in toggle menu items (at least on some devices). Fixed a bug where a menu item requesting to exit on completion appeared to indicate failure when using Wi-Fi or LTE. The fix uses a delay in exiting the application modelled as sufficient for a Venu 2 device, so this might need tweaking for other devices. Attempt to fixed an "Out of Memory" bug caused by v3.3 by making automatic checking for menu updates both optional and automatically turned off when insufficient memory is available. This last bug is device dependent and may require another attempt. Internationalisation improvements with thanks to @krzys_h for a new automated translations script. | | 3.4 | Fixed a bug where templates failed to display in toggle menu items (at least on some devices). Fixed a bug where a menu item requesting to exit on completion appeared to indicate failure when using Wi-Fi or LTE. The fix uses a delay in exiting the application modelled as sufficient for a Venu 2 device, so this might need tweaking for other devices. Attempt to fixed an "Out of Memory" bug caused by v3.3 by making automatic checking for menu updates both optional and automatically turned off when insufficient memory is available. This last bug is device dependent and may require another attempt. Internationalisation improvements with thanks to @krzys_h for a new automated translations script. |
| 3.5 | Added support for Edge 550, 850 & MTB, Fenix 8 Pro 47mm, GPSMAP H1, Instinct Crossover AMOLED, Venu 4 41mm & 45mm, & Venu X1 devices which also required an SDK update to 8.3.0. The simulation of the Edge 850 device was off, as it failed to update the display and text was the wrong colour, but the buttons menu items operated HA correctly. The assumption is the simulation model is buggy until someone [reports](https://github.com/house-of-abbey/GarminHomeAssistant/issues) otherwise. |

View File

@@ -143,7 +143,24 @@ Example schema:
"service": "scene.turn_on", "service": "scene.turn_on",
"pin": true "pin": true
} }
} },
{
"name": "Heating",
"content": "{{ ' %.1f' | format(state_attr('climate.myheating','temperature')) }}",
"type": "numeric",
"entity": "climate.myheating",
"tap_action": {
"service": "climate.set_temperature",
"data": {
"step": "0.5",
"start": "10",
"stop": "30",
"valueLabel": "temperature",
"formatString": "%.1f"
}
},
"pin": false
} ,
] ]
} }
``` ```
@@ -157,6 +174,7 @@ The example above illustrates how to configure:
* Script invocation (`tap`) * Script invocation (`tap`)
* Service invocation, e.g. Scene setting, (`tap`) * Service invocation, e.g. Scene setting, (`tap`)
* A sub-menu to open (`group`) * A sub-menu to open (`group`)
* A numeric item (`numeric`), which allows you to set a numeric value e.g. for heating or a dimmer. ValueLabel defines the variable to return. You can optionally set the minimum (start) and maximum (stop) value as well as the step to increase/decrease and a tepmlate how to format the value.
* You can also display the status of devices (`info`) which is essentially a `tap` with no action * You can also display the status of devices (`info`) which is essentially a `tap` with no action
* All menu items can display the results of evaluating [templates](examples/Templates.md). * All menu items can display the results of evaluating [templates](examples/Templates.md).
@@ -354,7 +372,7 @@ Check the latest unresolved [issues](https://github.com/house-of-abbey/GarminHom
5. Parameters to tap menu items cannot have their parameter usage verified. If you get this wrong and crash the application, that's your fault not the application's. In this case, start by removing the parameters for the menu item causing the crash, and add them back one at a time until you find your fault. **Please don't give the application a poor review for your bad parameter definition!** 5. Parameters to tap menu items cannot have their parameter usage verified. If you get this wrong and crash the application, that's your fault not the application's. In this case, start by removing the parameters for the menu item causing the crash, and add them back one at a time until you find your fault. **Please don't give the application a poor review for your bad parameter definition!**
6. We are unable to support Edge 540, Edge 840 and Edge 1050 devices at this time. The simulation of these devices has two unexpected errors when toggling or executing taps. We get both `Communications.NETWORK_RESPONSE_OUT_OF_MEMORY` and `Communications.BLE_QUEUE_FULL` even though the memory usage is about 6% of the available RAM. Based on a lead from user @Petucky, both devices are being re-enabled as testing on a real Edge 840 device has proven successful, however we remain unable to support either devices until the simulator is fixed. 6. We are unable to support Edge 540, Edge 840 and Edge 1050 devices at this time. The simulation of these devices has two unexpected errors when toggling or executing taps. We get both `Communications.NETWORK_RESPONSE_OUT_OF_MEMORY` and `Communications.BLE_QUEUE_FULL` even though the memory usage is about 6% of the available RAM. Based on a lead from user @Petucky, both devices are being re-enabled as testing on a real Edge 840 device has proven successful, however we remain unable to support either devices until the simulator is fixed. The Edge 850 device has different display update issues but is functional. Again the assumption is the simulation model is buggy. Please [report](https://github.com/house-of-abbey/GarminHomeAssistant/issues) your experience on real devices to us so we can withdraw support or confirm they are working.
7. We are unable to support HTTP natively (without the workaround specified earlier). This is a limitation placed upon us by the Connect IQ API which for security reasons refuses to work with HTTP requests. There is nothing developers can do about this limitation. See the [Trouble Shooting](TroubleShooting.md#do-it-yourself-setup) guide for an example setup. We would appreciate it if users did not leave poor reviews for the lack of this feature which is beyond our control to fix. 7. We are unable to support HTTP natively (without the workaround specified earlier). This is a limitation placed upon us by the Connect IQ API which for security reasons refuses to work with HTTP requests. There is nothing developers can do about this limitation. See the [Trouble Shooting](TroubleShooting.md#do-it-yourself-setup) guide for an example setup. We would appreciate it if users did not leave poor reviews for the lack of this feature which is beyond our control to fix.

View File

@@ -17,7 +17,10 @@
"$ref": "#/$defs/items" "$ref": "#/$defs/items"
} }
}, },
"required": ["title", "items"], "required": [
"title",
"items"
],
"additionalProperties": false, "additionalProperties": false,
"$defs": { "$defs": {
"toggle": { "toggle": {
@@ -37,15 +40,7 @@
"$ref": "#/$defs/content" "$ref": "#/$defs/content"
}, },
"tap_action": { "tap_action": {
"type": "object", "$ref": "#/$defs/tap_action",
"properties": {
"confirm": {
"$ref": "#/$defs/confirm"
},
"pin": {
"$ref": "#/$defs/pin"
}
},
"additionalProperties": false "additionalProperties": false
}, },
"enabled": { "enabled": {
@@ -55,7 +50,11 @@
"$ref": "#/$defs/exit" "$ref": "#/$defs/exit"
} }
}, },
"required": ["entity", "name", "type"], "required": [
"entity",
"name",
"type"
],
"additionalProperties": false "additionalProperties": false
}, },
"template": { "template": {
@@ -86,7 +85,11 @@
"$ref": "#/$defs/enabled" "$ref": "#/$defs/enabled"
} }
}, },
"required": ["name", "content", "type"], "required": [
"name",
"content",
"type"
],
"additionalProperties": false "additionalProperties": false
}, },
{ {
@@ -109,7 +112,20 @@
"description": "Use 'info' or 'tap' instead." "description": "Use 'info' or 'tap' instead."
}, },
"tap_action": { "tap_action": {
"$ref": "#/$defs/tap_action" "$ref": "#/$defs/tap_action",
"properties": {
"service": {
"$ref": "#/$defs/service"
},
"data": {
"type": "object",
"title": "Your services's parameters",
"description": "The object containing the parameters and their values to be passed to the entity. No schema checking can be done here, you are on your own! On application crash, remove the parameters."
}
},
"required": [
"service"
]
}, },
"enabled": { "enabled": {
"$ref": "#/$defs/enabled" "$ref": "#/$defs/enabled"
@@ -118,7 +134,12 @@
"$ref": "#/$defs/exit" "$ref": "#/$defs/exit"
} }
}, },
"required": ["name", "content", "type", "tap_action"], "required": [
"name",
"content",
"type",
"tap_action"
],
"additionalProperties": false "additionalProperties": false
} }
] ]
@@ -140,7 +161,11 @@
"$ref": "#/$defs/enabled" "$ref": "#/$defs/enabled"
} }
}, },
"required": ["name", "content", "type"], "required": [
"name",
"content",
"type"
],
"additionalProperties": false "additionalProperties": false
}, },
"tap": { "tap": {
@@ -160,13 +185,26 @@
"$ref": "#/$defs/content" "$ref": "#/$defs/content"
}, },
"service": { "service": {
"$ref": "#/$defs/entity", "$ref": "#/$defs/service",
"deprecated": true, "deprecated": true,
"title": "Schema change:", "title": "Schema change:",
"description": "Use 'tap_action' instead to mirror Home Assistant." "description": "Use 'tap_action' instead to mirror Home Assistant."
}, },
"tap_action": { "tap_action": {
"$ref": "#/$defs/tap_action" "$ref": "#/$defs/tap_action",
"properties": {
"service": {
"$ref": "#/$defs/service"
},
"data": {
"type": "object",
"title": "Your services's parameters",
"description": "The object containing the parameters and their values to be passed to the entity. No schema checking can be done here, you are on your own! On application crash, remove the parameters."
}
},
"required": [
"service"
]
}, },
"enabled": { "enabled": {
"$ref": "#/$defs/enabled" "$ref": "#/$defs/enabled"
@@ -175,7 +213,10 @@
"$ref": "#/$defs/exit" "$ref": "#/$defs/exit"
} }
}, },
"required": ["name", "type"], "required": [
"name",
"type"
],
"additionalProperties": false "additionalProperties": false
}, },
"group": { "group": {
@@ -210,9 +251,301 @@
"$ref": "#/$defs/enabled" "$ref": "#/$defs/enabled"
} }
}, },
"required": ["name", "title", "type", "items"], "required": [
"name",
"title",
"type",
"items"
],
"additionalProperties": false "additionalProperties": false
}, },
"numeric": {
"type": "object",
"allOf": [
{
"properties": {
"name": {
"$ref": "#/$defs/name"
},
"type": {
"$ref": "#/$defs/type",
"const": "numeric"
},
"content": {
"$ref": "#/$defs/content"
},
"tap_action": {
"$ref": "#/$defs/tap_action"
},
"enabled": {
"$ref": "#/$defs/enabled"
},
"exit": {
"$ref": "#/$defs/exit"
},
"entity": {
"$ref": "#/$defs/entity"
}
},
"required": [
"name",
"type",
"entity"
],
"additionalProperties": false
},
{
"properties": {
"entity": {
"pattern": "^(light|input_number|number|fan|valve|cover|media_player|climate)\\.[^.]+$"
}
},
"if": {
"properties": {
"entity": {
"pattern": "^light\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "brightness"
},
"service": {
"const": "light.turn_on"
},
"data_attribute": {
"const": "brightness"
},
"min": {
"const": 0
},
"max": {
"const": 255,
"description": "Lights are not a percentage."
}
}
},
"else": {
"if": {
"properties": {
"entity": {
"pattern": "^input_number\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "value"
},
"service": {
"const": "input_number.set_value"
},
"data_attribute": {
"const": "value"
}
},
"not": {
"required": [
"attribute"
]
}
},
"else": {
"if": {
"properties": {
"entity": {
"pattern": "^number\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "value"
},
"service": {
"const": "number.set_value"
},
"data_attribute": {
"const": "value"
}
},
"not": {
"required": [
"attribute"
]
}
},
"else": {
"if": {
"properties": {
"entity": {
"pattern": "^fan\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "percentage"
},
"service": {
"const": "fan.set_percentage"
},
"data_attribute": {
"const": "percentage"
},
"min": {
"const": 0
},
"max": {
"const": 100
}
}
},
"else": {
"if": {
"properties": {
"entity": {
"pattern": "^valve\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "position"
},
"service": {
"const": "valve.set_valve_position"
},
"data_attribute": {
"const": "position"
},
"min": {
"const": 0
},
"max": {
"const": 100
}
}
},
"else": {
"if": {
"properties": {
"entity": {
"pattern": "^cover\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "position"
},
"service": {
"const": "cover.set_position"
},
"data_attribute": {
"const": "position"
},
"min": {
"const": 0
},
"max": {
"const": 100
}
}
},
"else": {
"if": {
"properties": {
"entity": {
"pattern": "^cover\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "tilt_position"
},
"service": {
"const": "cover.set_tilt_position"
},
"data_attribute": {
"const": "tilt_position"
},
"min": {
"const": 0
},
"max": {
"const": 100
}
}
},
"else": {
"if": {
"properties": {
"entity": {
"pattern": "^media_player\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "volume_level"
},
"service": {
"const": "media_player.volume_set"
},
"data_attribute": {
"const": "volume_level"
},
"min": {
"const": 0
},
"max": {
"const": 1
}
}
},
"else": {
"if": {
"properties": {
"entity": {
"pattern": "^climate\\.[^.]+$"
}
}
},
"then": {
"properties": {
"attribute": {
"const": "temperature"
},
"service": {
"const": "climate.set_temperature"
},
"data_attribute": {
"const": "temperature"
}
}
}
}
}
}
}
}
}
}
}
}
]
},
"type": { "type": {
"title": "Menu item type", "title": "Menu item type",
"description": "One of 'info', 'tap', 'toggle' or 'group'." "description": "One of 'info', 'tap', 'toggle' or 'group'."
@@ -220,21 +553,210 @@
"items": { "items": {
"type": "array", "type": "array",
"items": { "items": {
"oneOf": [ "examples": [
{ {
"$ref": "#/$defs/toggle" "type": "tap",
"name": "Example",
"tap_action": {
"service": "notify.notify",
"data": {
"message": "Example"
}
}
}, },
{ {
"$ref": "#/$defs/template" "type": "toggle",
"name": "Example",
"entity": "switch.example"
}, },
{ {
"$ref": "#/$defs/tap" "type": "group",
"name": "Example",
"title": "Example",
"items": []
}, },
{ {
"$ref": "#/$defs/info" "type": "numeric",
"name": "Example",
"entity": "light.example",
"attribute": "brightness",
"service": "light.turn_on",
"data_attribute": "brightness",
"min": 0,
"max": 255,
"step": 1
}, },
{ {
"$ref": "#/$defs/group" "type": "numeric",
"name": "Example",
"entity": "input_number.example",
"service": "input_number.set_value",
"data_attribute": "value",
"min": 0,
"max": 100,
"step": 1
},
{
"type": "numeric",
"name": "Example",
"entity": "number.example",
"service": "number.set_value",
"data_attribute": "value",
"min": 0,
"max": 100,
"step": 1
},
{
"type": "numeric",
"name": "Example",
"entity": "fan.example",
"attribute": "percentage",
"service": "fan.set_percentage",
"data_attribute": "percentage",
"min": 0,
"max": 100,
"step": 1,
"display_format": "%.0f%%"
},
{
"type": "numeric",
"name": "Example",
"entity": "valve.example",
"attribute": "position",
"service": "valve.set_valve_position",
"data_attribute": "position",
"min": 0,
"max": 100,
"step": 1
},
{
"type": "numeric",
"name": "Example",
"entity": "cover.example",
"attribute": "position",
"service": "cover.set_position",
"data_attribute": "position",
"min": 0,
"max": 100,
"step": 1
},
{
"type": "numeric",
"name": "Example",
"entity": "cover.example",
"attribute": "tilt_position",
"service": "cover.set_tilt_position",
"data_attribute": "tilt_position",
"min": 0,
"max": 100,
"step": 1
},
{
"type": "numeric",
"name": "Example",
"entity": "media_player.example",
"attribute": "volume_level",
"service": "media_player.volume_set",
"data_attribute": "volume_level",
"min": 0,
"max": 1,
"step": 0.01
},
{
"type": "numeric",
"name": "Example",
"entity": "climate.example",
"attribute": "temperature",
"service": "climate.set_temperature",
"data_attribute": "temperature"
}
],
"allOf": [
{
"properties": {
"type": {
"enum": [
"toggle",
"template",
"tap",
"info",
"group",
"numeric"
]
}
}
},
{
"if": {
"properties": {
"type": {
"const": "toggle"
}
}
},
"then": {
"$ref": "#/$defs/toggle"
},
"else": {
"if": {
"properties": {
"type": {
"const": "template"
}
}
},
"then": {
"$ref": "#/$defs/template"
},
"else": {
"if": {
"properties": {
"type": {
"const": "tap"
}
}
},
"then": {
"$ref": "#/$defs/tap"
},
"else": {
"if": {
"properties": {
"type": {
"const": "info"
}
}
},
"then": {
"$ref": "#/$defs/info"
},
"else": {
"if": {
"properties": {
"type": {
"const": "group"
}
}
},
"then": {
"$ref": "#/$defs/group"
},
"else": {
"if": {
"properties": {
"type": {
"const": "numeric"
}
}
},
"then": {
"$ref": "#/$defs/numeric"
}
}
}
}
}
}
} }
] ]
} }
@@ -258,22 +780,51 @@
"title": "Action", "title": "Action",
"description": "'confirm' field is optional.", "description": "'confirm' field is optional.",
"properties": { "properties": {
"service": { "picker": {
"$ref": "#/$defs/service" "$ref": "#/$defs/picker"
}, },
"confirm": { "confirm": {
"$ref": "#/$defs/confirm" "$ref": "#/$defs/confirm"
}, },
"pin": { "pin": {
"$ref": "#/$defs/pin" "$ref": "#/$defs/pin"
}
}
},
"picker": {
"type": "object",
"title": "Number picker configuration",
"description": "'attribute' field is optional.",
"properties": {
"min": {
"type": "number",
"title": "Minimum Value"
}, },
"data": { "max": {
"type": "object", "type": "number",
"title": "Your services's parameters", "title": "Maximum Value"
"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." },
"step": {
"type": "number",
"title": "Step Size"
},
"attribute": {
"type": "string",
"title": "Attribute on the entity",
"description": "Attribute on the entity with the current numeric value. To use the state of the entity, do not specify."
},
"data_attribute": {
"type": "string",
"title": "Attribute on the service data",
"description": "Attribute on the service data for the value to set."
} }
}, },
"required": ["service"] "required": [
"min",
"max",
"step",
"data_attribute"
]
}, },
"content": { "content": {
"title": "Home Assistant Template", "title": "Home Assistant Template",
@@ -307,7 +858,10 @@
"$ref": "#/$defs/content" "$ref": "#/$defs/content"
} }
}, },
"required": ["type", "content"] "required": [
"type",
"content"
]
}, },
{ {
"properties": { "properties": {
@@ -317,7 +871,9 @@
"const": "status" "const": "status"
} }
}, },
"required": ["type"] "required": [
"type"
]
} }
] ]
}, },

View File

@@ -13,206 +13,215 @@
Device Information & References: Device Information & References:
* https://developer.garmin.com/connect-iq/compatible-devices/ * https://developer.garmin.com/connect-iq/compatible-devices/
* https://developer.garmin.com/connect-iq/reference-guides/devices-reference/ * https://developer.garmin.com/connect-iq/device-reference/
philipabbey's Test App id="98c36259-498a-4458-9cef-74a273ad2bc3" type="watch-app" philipabbey's Test App id="98c36259-498a-4458-9cef-74a273ad2bc3" type="watch-app"
Live Application id="40131e87-31ff-454b-a8e2-92276ee399d6" type="watch-app" Live Application id="40131e87-31ff-454b-a8e2-92276ee399d6" type="watch-app"
--> -->
<iq:manifest version="3" xmlns:iq="http://www.garmin.com/xml/connectiq"> <iq:manifest version="3" xmlns:iq="http://www.garmin.com/xml/connectiq">
<!-- <!--
Use "Monkey C: Edit Application" from the Visual Studio Code command palette Use "Monkey C: Edit Application" from the Visual Studio Code command palette
to update the application attributes. to update the application attributes.
--> -->
<iq:application id="98c36259-498a-4458-9cef-74a273ad2bc3" type="watch-app" name="@Strings.AppName" entry="HomeAssistantApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="3.1.0"> <iq:application id="971834c4-e4fc-4825-801f-7ac9db0e3044" type="watch-app" name="@Strings.AppName" entry="HomeAssistantApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="3.1.0">
<!-- <!--
Use the following from the Visual Studio Code command palette to edit Use the following from the Visual Studio Code command palette to edit
the build targets: the build targets:
"Monkey C: Set Products by Product Category" - Lets you add all products "Monkey C: Set Products by Product Category" - Lets you add all products
that belong to the same product category that belong to the same product category
"Monkey C: Edit Products" - Lets you add or remove any product "Monkey C: Edit Products" - Lets you add or remove any product
--> -->
<iq:products> <iq:products>
<iq:product id="approachs50"/> <iq:product id="approachs50"/>
<iq:product id="approachs7042mm"/> <iq:product id="approachs7042mm"/>
<iq:product id="approachs7047mm"/> <iq:product id="approachs7047mm"/>
<iq:product id="d2air"/> <iq:product id="d2air"/>
<iq:product id="d2airx10"/> <iq:product id="d2airx10"/>
<iq:product id="d2delta"/> <iq:product id="d2delta"/>
<iq:product id="d2deltapx"/> <iq:product id="d2deltapx"/>
<iq:product id="d2deltas"/> <iq:product id="d2deltas"/>
<iq:product id="d2mach1"/> <iq:product id="d2mach1"/>
<iq:product id="descentg1"/> <iq:product id="descentg1"/>
<iq:product id="descentg2"/> <iq:product id="descentg2"/>
<iq:product id="descentmk1"/> <iq:product id="descentmk2"/>
<iq:product id="descentmk2"/> <iq:product id="descentmk2s"/>
<iq:product id="descentmk2s"/> <iq:product id="descentmk343mm"/>
<iq:product id="descentmk343mm"/> <iq:product id="descentmk351mm"/>
<iq:product id="descentmk351mm"/> <iq:product id="edge1030"/>
<iq:product id="edge1030"/> <iq:product id="edge1030bontrager"/>
<iq:product id="edge1030bontrager"/> <iq:product id="edge1030plus"/>
<iq:product id="edge1030plus"/> <iq:product id="edge1040"/>
<iq:product id="edge1040"/> <iq:product id="edge1050"/>
<iq:product id="edge1050"/> <iq:product id="edge520plus"/>
<iq:product id="edge520plus"/> <iq:product id="edge530"/>
<iq:product id="edge530"/> <iq:product id="edge540"/>
<iq:product id="edge540"/> <iq:product id="edge550"/>
<iq:product id="edge820"/> <iq:product id="edge820"/>
<iq:product id="edge830"/> <iq:product id="edge830"/>
<iq:product id="edge840"/> <iq:product id="edge840"/>
<iq:product id="edgeexplore"/> <iq:product id="edge850"/>
<iq:product id="edgeexplore2"/> <iq:product id="edgeexplore"/>
<iq:product id="enduro"/> <iq:product id="edgeexplore2"/>
<iq:product id="enduro3"/> <iq:product id="edgemtb"/>
<iq:product id="epix2"/> <iq:product id="enduro"/>
<iq:product id="epix2pro42mm"/> <iq:product id="enduro3"/>
<iq:product id="epix2pro47mm"/> <iq:product id="epix2"/>
<iq:product id="epix2pro51mm"/> <iq:product id="epix2pro42mm"/>
<iq:product id="fenix5"/> <iq:product id="epix2pro47mm"/>
<iq:product id="fenix5plus"/> <iq:product id="epix2pro47mmsystem7preview"/>
<iq:product id="fenix5s"/> <iq:product id="epix2pro51mm"/>
<iq:product id="fenix5splus"/> <iq:product id="fenix5"/>
<iq:product id="fenix5x"/> <iq:product id="fenix5plus"/>
<iq:product id="fenix5xplus"/> <iq:product id="fenix5s"/>
<iq:product id="fenix6"/> <iq:product id="fenix5splus"/>
<iq:product id="fenix6pro"/> <iq:product id="fenix5x"/>
<iq:product id="fenix6s"/> <iq:product id="fenix5xplus"/>
<iq:product id="fenix6spro"/> <iq:product id="fenix6"/>
<iq:product id="fenix6xpro"/> <iq:product id="fenix6pro"/>
<iq:product id="fenix7"/> <iq:product id="fenix6s"/>
<iq:product id="fenix7pro"/> <iq:product id="fenix6spro"/>
<iq:product id="fenix7pronowifi"/> <iq:product id="fenix6xpro"/>
<iq:product id="fenix7s"/> <iq:product id="fenix7"/>
<iq:product id="fenix7spro"/> <iq:product id="fenix7pro"/>
<iq:product id="fenix7x"/> <iq:product id="fenix7pronowifi"/>
<iq:product id="fenix7xpro"/> <iq:product id="fenix7s"/>
<iq:product id="fenix7xpronowifi"/> <iq:product id="fenix7spro"/>
<iq:product id="fenix843mm"/> <iq:product id="fenix7x"/>
<iq:product id="fenix847mm"/> <iq:product id="fenix7xpro"/>
<iq:product id="fenix8solar47mm"/> <iq:product id="fenix7xpronowifi"/>
<iq:product id="fenix8solar51mm"/> <iq:product id="fenix843mm"/>
<iq:product id="fenixchronos"/> <iq:product id="fenix847mm"/>
<iq:product id="fenixe"/> <iq:product id="fenix8pro47mm"/>
<iq:product id="fr165"/> <iq:product id="fenix8solar47mm"/>
<iq:product id="fr165m"/> <iq:product id="fenix8solar51mm"/>
<iq:product id="fr245"/> <iq:product id="fenixchronos"/>
<iq:product id="fr245m"/> <iq:product id="fenixe"/>
<iq:product id="fr255"/> <iq:product id="fr165"/>
<iq:product id="fr255m"/> <iq:product id="fr165m"/>
<iq:product id="fr255s"/> <iq:product id="fr245"/>
<iq:product id="fr255sm"/> <iq:product id="fr245m"/>
<iq:product id="fr265"/> <iq:product id="fr255"/>
<iq:product id="fr265s"/> <iq:product id="fr255m"/>
<iq:product id="fr55"/> <iq:product id="fr255s"/>
<iq:product id="fr57042mm"/> <iq:product id="fr255sm"/>
<iq:product id="fr57047mm"/> <iq:product id="fr265"/>
<iq:product id="fr645"/> <iq:product id="fr265s"/>
<iq:product id="fr645m"/> <iq:product id="fr55"/>
<iq:product id="fr745"/> <iq:product id="fr57042mm"/>
<iq:product id="fr935"/> <iq:product id="fr57047mm"/>
<iq:product id="fr945"/> <iq:product id="fr645"/>
<iq:product id="fr945lte"/> <iq:product id="fr645m"/>
<iq:product id="fr955"/> <iq:product id="fr745"/>
<iq:product id="fr965"/> <iq:product id="fr935"/>
<iq:product id="fr970"/> <iq:product id="fr945"/>
<iq:product id="gpsmap66"/> <iq:product id="fr945lte"/>
<iq:product id="gpsmap67"/> <iq:product id="fr955"/>
<iq:product id="instinct2"/> <iq:product id="fr965"/>
<iq:product id="instinct2s"/> <iq:product id="fr970"/>
<iq:product id="instinct2x"/> <iq:product id="gpsmap66"/>
<iq:product id="instinct3amoled45mm"/> <iq:product id="gpsmap67"/>
<iq:product id="instinct3amoled50mm"/> <iq:product id="gpsmaph1"/>
<iq:product id="instinct3solar45mm"/> <iq:product id="instinct2"/>
<iq:product id="instinctcrossover"/> <iq:product id="instinct2s"/>
<iq:product id="instincte40mm"/> <iq:product id="instinct2x"/>
<iq:product id="instincte45mm"/> <iq:product id="instinct3amoled45mm"/>
<iq:product id="legacyherocaptainmarvel"/> <iq:product id="instinct3amoled50mm"/>
<iq:product id="legacyherofirstavenger"/> <iq:product id="instinct3solar45mm"/>
<iq:product id="legacysagadarthvader"/> <iq:product id="instinctcrossover"/>
<iq:product id="legacysagarey"/> <iq:product id="instinctcrossoveramoled"/>
<iq:product id="marq2"/> <iq:product id="instincte40mm"/>
<iq:product id="marq2aviator"/> <iq:product id="instincte45mm"/>
<iq:product id="marqadventurer"/> <iq:product id="legacyherocaptainmarvel"/>
<iq:product id="marqathlete"/> <iq:product id="legacyherofirstavenger"/>
<iq:product id="marqaviator"/> <iq:product id="legacysagadarthvader"/>
<iq:product id="marqcaptain"/> <iq:product id="legacysagarey"/>
<iq:product id="marqcommander"/> <iq:product id="marq2"/>
<iq:product id="marqdriver"/> <iq:product id="marq2aviator"/>
<iq:product id="marqexpedition"/> <iq:product id="marqadventurer"/>
<iq:product id="marqgolfer"/> <iq:product id="marqathlete"/>
<iq:product id="montana7xx"/> <iq:product id="marqaviator"/>
<iq:product id="venu"/> <iq:product id="marqcaptain"/>
<iq:product id="venu2"/> <iq:product id="marqcommander"/>
<iq:product id="venu2plus"/> <iq:product id="marqdriver"/>
<iq:product id="venu2s"/> <iq:product id="marqexpedition"/>
<iq:product id="venu3"/> <iq:product id="marqgolfer"/>
<iq:product id="venu3s"/> <iq:product id="montana7xx"/>
<iq:product id="venud"/> <iq:product id="venu"/>
<iq:product id="venusq"/> <iq:product id="venu2"/>
<iq:product id="venusq2"/> <iq:product id="venu2plus"/>
<iq:product id="venusq2m"/> <iq:product id="venu2s"/>
<iq:product id="venusqm"/> <iq:product id="venu3"/>
<iq:product id="vivoactive3"/> <iq:product id="venu3s"/>
<iq:product id="vivoactive3m"/> <iq:product id="venu441mm"/>
<iq:product id="vivoactive3mlte"/> <iq:product id="venu445mm"/>
<iq:product id="vivoactive4"/> <iq:product id="venud"/>
<iq:product id="vivoactive4s"/> <iq:product id="venusq"/>
<iq:product id="vivoactive5"/> <iq:product id="venusq2"/>
<iq:product id="vivoactive6"/> <iq:product id="venusq2m"/>
</iq:products> <iq:product id="venusqm"/>
<!-- <iq:product id="venux1"/>
<iq:product id="vivoactive3"/>
<iq:product id="vivoactive3m"/>
<iq:product id="vivoactive3mlte"/>
<iq:product id="vivoactive4"/>
<iq:product id="vivoactive4s"/>
<iq:product id="vivoactive5"/>
<iq:product id="vivoactive6"/>
</iq:products>
<!--
Use "Monkey C: Edit Permissions" from the Visual Studio Code command Use "Monkey C: Edit Permissions" from the Visual Studio Code command
palette to update permissions. palette to update permissions.
--> -->
<iq:permissions> <iq:permissions>
<iq:uses-permission id="Background"/> <iq:uses-permission id="Background"/>
<iq:uses-permission id="BluetoothLowEnergy"/> <iq:uses-permission id="BluetoothLowEnergy"/>
<iq:uses-permission id="Communications"/> <iq:uses-permission id="Communications"/>
<iq:uses-permission id="Positioning"/> <iq:uses-permission id="Positioning"/>
</iq:permissions> </iq:permissions>
<!-- <!--
Use "Monkey C: Edit Languages" from the Visual Studio Code command Use "Monkey C: Edit Languages" from the Visual Studio Code command
palette to edit your compatible language list. palette to edit your compatible language list.
--> -->
<iq:languages> <iq:languages>
<iq:language>ara</iq:language> <iq:language>ara</iq:language>
<iq:language>bul</iq:language> <iq:language>bul</iq:language>
<iq:language>ces</iq:language> <iq:language>ces</iq:language>
<iq:language>dan</iq:language> <iq:language>dan</iq:language>
<iq:language>deu</iq:language> <iq:language>deu</iq:language>
<iq:language>dut</iq:language> <iq:language>dut</iq:language>
<iq:language>eng</iq:language> <iq:language>eng</iq:language>
<iq:language>est</iq:language> <iq:language>est</iq:language>
<iq:language>fin</iq:language> <iq:language>fin</iq:language>
<iq:language>fre</iq:language> <iq:language>fre</iq:language>
<iq:language>gre</iq:language> <iq:language>gre</iq:language>
<iq:language>heb</iq:language> <iq:language>heb</iq:language>
<iq:language>hrv</iq:language> <iq:language>hrv</iq:language>
<iq:language>hun</iq:language> <iq:language>hun</iq:language>
<iq:language>ind</iq:language> <iq:language>ind</iq:language>
<iq:language>ita</iq:language> <iq:language>ita</iq:language>
<iq:language>jpn</iq:language> <iq:language>jpn</iq:language>
<iq:language>kor</iq:language> <iq:language>kor</iq:language>
<iq:language>lav</iq:language> <iq:language>lav</iq:language>
<iq:language>lit</iq:language> <iq:language>lit</iq:language>
<iq:language>nob</iq:language> <iq:language>nob</iq:language>
<iq:language>pol</iq:language> <iq:language>pol</iq:language>
<iq:language>por</iq:language> <iq:language>por</iq:language>
<iq:language>ron</iq:language> <iq:language>ron</iq:language>
<!-- <iq:language>rus</iq:language> --> <!-- <iq:language>rus</iq:language> -->
<iq:language>slo</iq:language> <iq:language>slo</iq:language>
<iq:language>slv</iq:language> <iq:language>slv</iq:language>
<iq:language>spa</iq:language> <iq:language>spa</iq:language>
<iq:language>swe</iq:language> <iq:language>swe</iq:language>
<iq:language>tha</iq:language> <iq:language>tha</iq:language>
<iq:language>tur</iq:language> <iq:language>tur</iq:language>
<iq:language>ukr</iq:language> <iq:language>ukr</iq:language>
<iq:language>vie</iq:language> <iq:language>vie</iq:language>
<iq:language>zhs</iq:language> <iq:language>zhs</iq:language>
<iq:language>zht</iq:language> <iq:language>zht</iq:language>
<iq:language>zsm</iq:language> <iq:language>zsm</iq:language>
</iq:languages> </iq:languages>
<!-- <!--
Use "Monkey C: Configure Monkey Barrel" from the Visual Studio Code Use "Monkey C: Configure Monkey Barrel" from the Visual Studio Code
command palette to edit the included barrels. command palette to edit the included barrels.
--> -->

View File

@@ -20,7 +20,7 @@ project.manifest = manifest.xml
# Device References # Device References
# * https://developer.garmin.com/connect-iq/compatible-devices/ # * https://developer.garmin.com/connect-iq/compatible-devices/
# * https://developer.garmin.com/connect-iq/reference-guides/devices-reference/ # * https://developer.garmin.com/connect-iq/device-reference/
# #
# Widget launcher icon, multiple resolutions # Widget launcher icon, multiple resolutions
# https://forums.garmin.com/developer/connect-iq/f/discussion/255433/widget-launcher-icon-multiple-resolutions/1563305 # https://forums.garmin.com/developer/connect-iq/f/discussion/255433/widget-launcher-icon-multiple-resolutions/1563305
@@ -77,14 +77,20 @@ edge520plus.resourcePath = $(edge520plus.resourcePath);resources-launcher-35-35;
# Screen Size 246x322 launcher icon size 35x35 # Screen Size 246x322 launcher icon size 35x35
edge530.resourcePath = $(edge530.resourcePath);resources-launcher-35-35;resources-icons-28 edge530.resourcePath = $(edge530.resourcePath);resources-launcher-35-35;resources-icons-28
edge540.resourcePath = $(edge540.resourcePath);resources-launcher-35-35;resources-icons-28 edge540.resourcePath = $(edge540.resourcePath);resources-launcher-35-35;resources-icons-28
# Screen Size 420x600 launcher icon size 56x56
edge550.resourcePath = $(edge550.resourcePath);resources-launcher-56-56;resources-icons-55
# Screen Size 200x265 launcher icon size 35x35 # Screen Size 200x265 launcher icon size 35x35
edge820.resourcePath = $(edge820.resourcePath);resources-launcher-35-35;resources-icons-24 edge820.resourcePath = $(edge820.resourcePath);resources-launcher-35-35;resources-icons-24
# Screen Size 246x322 launcher icon size 35x35 # Screen Size 246x322 launcher icon size 35x35
edge830.resourcePath = $(edge830.resourcePath);resources-launcher-35-35;resources-icons-28 edge830.resourcePath = $(edge830.resourcePath);resources-launcher-35-35;resources-icons-28
edge840.resourcePath = $(edge840.resourcePath);resources-launcher-35-35;resources-icons-28 edge840.resourcePath = $(edge840.resourcePath);resources-launcher-35-35;resources-icons-28
# Screen Size 420x600 launcher icon size 56x56
edge850.resourcePath = $(edge850.resourcePath);resources-launcher-56-56;resources-icons-55
# Screen Size 240x400 launcher icon size 36x36 # Screen Size 240x400 launcher icon size 36x36
edgeexplore.resourcePath = $(edgeexplore.resourcePath);resources-launcher-36-36;resources-icons-28 edgeexplore.resourcePath = $(edgeexplore.resourcePath);resources-launcher-36-36;resources-icons-28
edgeexplore2.resourcePath = $(edgeexplore2.resourcePath);resources-launcher-36-36;resources-icons-28 edgeexplore2.resourcePath = $(edgeexplore2.resourcePath);resources-launcher-36-36;resources-icons-28
# Screen Size 240x320 launcher icon size 36x36
edgemtb.resourcePath = $(edgemtb.resourcePath);resources-launcher-36-36;resources-icons-32
# Screen Size 280x280 launcher icon size 40x40 # Screen Size 280x280 launcher icon size 40x40
enduro.resourcePath = $(enduro.resourcePath);resources-launcher-40-40;resources-icons-32 enduro.resourcePath = $(enduro.resourcePath);resources-launcher-40-40;resources-icons-32
enduro3.resourcePath = $(enduro3.resourcePath);resources-launcher-40-40;resources-icons-32 enduro3.resourcePath = $(enduro3.resourcePath);resources-launcher-40-40;resources-icons-32
@@ -128,6 +134,7 @@ fenix7xpronowifi.resourcePath = $(fenix7xpronowifi.resourcePath);resources-launc
fenix843mm.resourcePath = $(fenix843mm.resourcePath);resources-launcher-60-60;resources-icons-48 fenix843mm.resourcePath = $(fenix843mm.resourcePath);resources-launcher-60-60;resources-icons-48
# Screen Size 454x454 launcher icon size 65x65 # Screen Size 454x454 launcher icon size 65x65
fenix847mm.resourcePath = $(fenix847mm.resourcePath);resources-launcher-65-65;resources-icons-53 fenix847mm.resourcePath = $(fenix847mm.resourcePath);resources-launcher-65-65;resources-icons-53
fenix8pro47mm.resourcePath = $(fenix8pro47mm.resourcePath);resources-launcher-65-65;resources-icons-53
# Screen Size 260x260 launcher icon size 40x40 # Screen Size 260x260 launcher icon size 40x40
fenix8solar47mm.resourcePath = $(fenix8solar47mm.resourcePath);resources-launcher-40-40;resources-icons-30 fenix8solar47mm.resourcePath = $(fenix8solar47mm.resourcePath);resources-launcher-40-40;resources-icons-30
# Screen Size 280x280 launcher icon size 40x40 # Screen Size 280x280 launcher icon size 40x40
@@ -173,6 +180,7 @@ fr970.resourcePath = $(fr970.resourcePath);resources-launcher-65-65;resources-ic
# Screen Size 240x400 launcher icon size 38x33 # Screen Size 240x400 launcher icon size 38x33
gpsmap66.resourcePath = $(gpsmap66.resourcePath);resources-launcher-33-33;resources-icons-28 gpsmap66.resourcePath = $(gpsmap66.resourcePath);resources-launcher-33-33;resources-icons-28
gpsmap67.resourcePath = $(gpsmap67.resourcePath);resources-launcher-33-33;resources-icons-28 gpsmap67.resourcePath = $(gpsmap67.resourcePath);resources-launcher-33-33;resources-icons-28
gpsmaph1.resourcePath = $(gpsmaph1.resourcePath);resources-launcher-33-33;resources-icons-28
# Screen Size 176x176 launcher icon size 62x62 # Screen Size 176x176 launcher icon size 62x62
instinct2.resourcePath = $(instinct2.resourcePath);resources-launcher-62-62;resources-icons-21-w instinct2.resourcePath = $(instinct2.resourcePath);resources-launcher-62-62;resources-icons-21-w
# Screen Size 163x156 launcher icon size 54x54 # Screen Size 163x156 launcher icon size 54x54
@@ -187,6 +195,8 @@ instinct3amoled50mm.resourcePath = $(instinct3amoled50mm.resourcePath);resources
instinct3solar45mm.resourcePath = $(instinct3solar45mm.resourcePath);resources-launcher-62-62;resources-icons-18-w instinct3solar45mm.resourcePath = $(instinct3solar45mm.resourcePath);resources-launcher-62-62;resources-icons-18-w
# Screen Size 176x176 launcher icon size 26x26 # Screen Size 176x176 launcher icon size 26x26
instinctcrossover.resourcePath = $(instinctcrossover.resourcePath);resources-launcher-26-26;resources-icons-21-w instinctcrossover.resourcePath = $(instinctcrossover.resourcePath);resources-launcher-26-26;resources-icons-21-w
# Screen Size 390x390 launcher icon size 38x38
instinctcrossoveramoled.resourcePath = $(instinctcrossoveramoled.resourcePath);resources-launcher-38-38;resources-icons-46
# Screen Size 166x166 launcher icon size 52x52, but the icon size used here is reduced as the menu items were clipped. # Screen Size 166x166 launcher icon size 52x52, but the icon size used here is reduced as the menu items were clipped.
instincte40mm.resourcePath = $(instincte40mm.resourcePath);resources-launcher-52-52;resources-icons-18-w instincte40mm.resourcePath = $(instincte40mm.resourcePath);resources-launcher-52-52;resources-icons-18-w
# Screen Size 176x176 launcher icon size 62x62, but the icon size used here is reduced as the menu items were clipped. # Screen Size 176x176 launcher icon size 62x62, but the icon size used here is reduced as the menu items were clipped.
@@ -223,6 +233,10 @@ venu2s.resourcePath = $(venu2s.resourcePath);resources-launcher-61-61;resources-
venu3.resourcePath = $(venu3.resourcePath);resources-launcher-70-70;resources-icons-53 venu3.resourcePath = $(venu3.resourcePath);resources-launcher-70-70;resources-icons-53
# Screen Size 390x390 launcher icon size 70x70 # Screen Size 390x390 launcher icon size 70x70
venu3s.resourcePath = $(venu3s.resourcePath);resources-launcher-70-70;resources-icons-46 venu3s.resourcePath = $(venu3s.resourcePath);resources-launcher-70-70;resources-icons-46
# Screen Size 390x390 launcher icon size 54x54
venu441mm.resourcePath = $(venu441mm.resourcePath);resources-launcher-54-54;resources-icons-46
# Screen Size 454x454 launcher icon size 65x65
venu445mm.resourcePath = $(venu445mm.resourcePath);resources-launcher-65-65;resources-icons-53
# Screen Size 390x390 launcher icon size 60x60 # Screen Size 390x390 launcher icon size 60x60
venud.resourcePath = $(venud.resourcePath);resources-launcher-60-60;resources-icons-46 venud.resourcePath = $(venud.resourcePath);resources-launcher-60-60;resources-icons-46
# Screen Size 240x240 launcher icon size 36x36 # Screen Size 240x240 launcher icon size 36x36
@@ -232,6 +246,8 @@ venusq2.resourcePath = $(venusq2.resourcePath);resources-launcher-40-40;resource
venusq2m.resourcePath = $(venusq2m.resourcePath);resources-launcher-40-40;resources-icons-38 venusq2m.resourcePath = $(venusq2m.resourcePath);resources-launcher-40-40;resources-icons-38
# Screen Size 240x240 launcher icon size 36x36 # Screen Size 240x240 launcher icon size 36x36
venusqm.resourcePath = $(venusqm.resourcePath);resources-launcher-36-36;resources-icons-28 venusqm.resourcePath = $(venusqm.resourcePath);resources-launcher-36-36;resources-icons-28
# Screen Size 448x486 launcher icon size 65x65
venux1.resourcePath = $(venux1.resourcePath);resources-launcher-65-65;resources-icons-53
# Screen Size 240x240 launcher icon size 40x33 # Screen Size 240x240 launcher icon size 40x33
vivoactive3.resourcePath = $(vivoactive3.resourcePath);resources-launcher-33-33;resources-icons-28 vivoactive3.resourcePath = $(vivoactive3.resourcePath);resources-launcher-33-33;resources-icons-28
vivoactive3m.resourcePath = $(vivoactive3m.resourcePath);resources-launcher-33-33;resources-icons-28 vivoactive3m.resourcePath = $(vivoactive3m.resourcePath);resources-launcher-33-33;resources-icons-28

View File

@@ -0,0 +1,17 @@
<!--
Distributed under MIT Licence
See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
tested on a Venu 2 device. The source code is provided at:
https://github.com/house-of-abbey/GarminHomeAssistant.
P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
-->
<drawables>
<bitmap id="LauncherIcon" filename="launcher.svg" />
</drawables>

View File

@@ -0,0 +1,4 @@
<svg fill="none" height="38" viewBox="0 0 400 400" width="38" xmlns="http://www.w3.org/2000/svg">
<path d="M320 301.762C320 310.012 313.25 316.762 305 316.762H95C86.75 316.762 80 310.012 80 301.762V211.762C80 203.512 84.77 191.993 90.61 186.153L189.39 87.3725C195.22 81.5425 204.77 81.5425 210.6 87.3725L309.39 186.162C315.22 191.992 320 203.522 320 211.772V301.772V301.762Z" fill="#F2F4F9"/>
<path d="M309.39 186.153L210.61 87.3725C204.78 81.5425 195.23 81.5425 189.4 87.3725L90.61 186.153C84.78 191.983 80 203.512 80 211.762V301.762C80 310.012 86.75 316.762 95 316.762H187.27L146.64 276.132C144.55 276.852 142.32 277.262 140 277.262C128.7 277.262 119.5 268.062 119.5 256.762C119.5 245.462 128.7 236.262 140 236.262C151.3 236.262 160.5 245.462 160.5 256.762C160.5 259.092 160.09 261.322 159.37 263.412L191 295.042V179.162C184.2 175.822 179.5 168.842 179.5 160.772C179.5 149.472 188.7 140.272 200 140.272C211.3 140.272 220.5 149.472 220.5 160.772C220.5 168.842 215.8 175.822 209 179.162V260.432L240.46 228.972C239.84 227.012 239.5 224.932 239.5 222.772C239.5 211.472 248.7 202.272 260 202.272C271.3 202.272 280.5 211.472 280.5 222.772C280.5 234.072 271.3 243.272 260 243.272C257.5 243.272 255.12 242.802 252.91 241.982L209 285.892V316.772H305C313.25 316.772 320 310.022 320 301.772V211.772C320 203.522 315.23 192.002 309.39 186.162V186.153Z" fill="#18BCF2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -46,27 +46,27 @@ class Alert extends WatchUi.View {
function initialize(params as Lang.Dictionary) { function initialize(params as Lang.Dictionary) {
View.initialize(); View.initialize();
mText = params[:text] as Lang.String; mText = params[:text] as Lang.String?;
if (mText == null) { if (mText == null) {
mText = "Alert"; mText = "Alert";
} }
mFont = params[:font] as Graphics.FontType; mFont = params[:font] as Graphics.FontType?;
if (mFont == null) { if (mFont == null) {
mFont = Graphics.FONT_MEDIUM; mFont = Graphics.FONT_MEDIUM;
} }
mFgcolor = params[:fgcolor] as Graphics.ColorType; mFgcolor = params[:fgcolor] as Graphics.ColorType?;
if (mFgcolor == null) { if (mFgcolor == null) {
mFgcolor = Graphics.COLOR_BLACK; mFgcolor = Graphics.COLOR_BLACK;
} }
mBgcolor = params[:bgcolor] as Graphics.ColorType; mBgcolor = params[:bgcolor] as Graphics.ColorType?;
if (mBgcolor == null) { if (mBgcolor == null) {
mBgcolor = Graphics.COLOR_WHITE; mBgcolor = Graphics.COLOR_WHITE;
} }
mTimeout = params[:timeout] as Lang.Number; mTimeout = params[:timeout] as Lang.Number?;
if (mTimeout == null) { if (mTimeout == null) {
mTimeout = 2000; mTimeout = 2000;
} }

View File

@@ -37,7 +37,7 @@ class HomeAssistantApp extends Application.AppBase {
private var mGlanceTimer as Timer.Timer?; private var mGlanceTimer as Timer.Timer?;
private var mUpdateTimer as Timer.Timer?; private var mUpdateTimer as Timer.Timer?;
// Array initialised by onReturnFetchMenuConfig() // Array initialised by onReturnFetchMenuConfig()
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem>?; private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or HomeAssistantNumericMenuItem>?;
private var mIsApp as Lang.Boolean = false; // Or Widget private var mIsApp as Lang.Boolean = false; // Or Widget
private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates
private var mTemplates as Lang.Dictionary? = null; // Cache of compiled templates private var mTemplates as Lang.Dictionary? = null; // Cache of compiled templates
@@ -629,6 +629,12 @@ class HomeAssistantApp extends Application.AppBase {
if (item instanceof HomeAssistantToggleMenuItem) { if (item instanceof HomeAssistantToggleMenuItem) {
(item as HomeAssistantToggleMenuItem).updateToggleState(data[i.toString() + "t"]); (item as HomeAssistantToggleMenuItem).updateToggleState(data[i.toString() + "t"]);
} }
if (item instanceof HomeAssistantNumericMenuItem) {
var s = data[i.toString() + "n"];
if ((s instanceof Lang.Number) or (s instanceof Lang.Float)) {
(item as HomeAssistantNumericMenuItem).setValue(s);
}
}
} }
if (Settings.getMenuCheck() && Settings.getCacheConfig() && !mIsCacheChecked) { if (Settings.getMenuCheck() && Settings.getCacheConfig() && !mIsCacheChecked) {
// We are caching the menu configuration, so let's fetch it and check if its been updated. // We are caching the menu configuration, so let's fetch it and check if its been updated.
@@ -723,6 +729,11 @@ class HomeAssistantApp extends Application.AppBase {
"template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate() "template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate()
}); });
} }
if (item instanceof HomeAssistantNumericMenuItem) {
mTemplates.put(i.toString() + "n", {
"template" => (item as HomeAssistantNumericMenuItem).getNumericTemplate()
});
}
} }
} }
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates // https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
@@ -797,6 +808,7 @@ class HomeAssistantApp extends Application.AppBase {
break; break;
case 200: case 200:
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: 200.");
if ((data != null) && (data instanceof Lang.Dictionary) && data["message"].equals("API running.")) { if ((data != null) && (data instanceof Lang.Dictionary) && data["message"].equals("API running.")) {
mApiStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String; mApiStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
} else { } else {
@@ -821,7 +833,7 @@ class HomeAssistantApp extends Application.AppBase {
var phoneConnected = System.getDeviceSettings().phoneConnected; var phoneConnected = System.getDeviceSettings().phoneConnected;
var connectionAvailable = System.getDeviceSettings().connectionAvailable; var connectionAvailable = System.getDeviceSettings().connectionAvailable;
// System.println("API URL = " + Settings.getApiUrl()); // System.println("HomeAssistantApp fetchApiStatus(): API URL = " + Settings.getApiUrl());
if (Settings.getApiUrl().equals("")) { if (Settings.getApiUrl().equals("")) {
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String; mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
WatchUi.requestUpdate(); WatchUi.requestUpdate();

View File

@@ -148,7 +148,43 @@ class HomeAssistantMenuItemFactory {
); );
} }
} }
//! Numeric menu item.
//!
//! @param definition Items array from the JSON that defines this sub menu.
//! @param template Template for Home Assistant to render (optional)
//
function numeric(
label as Lang.String or Lang.Symbol,
entity_id as Lang.String?,
template as Lang.String?,
service as Lang.String?,
picker as Lang.Dictionary,
options as {
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
:pin as Lang.Boolean,
:icon as WatchUi.Bitmap
}
) as WatchUi.MenuItem {
var data = null;
if (entity_id != null) {
data = { "entity_id" => entity_id };
}
var keys = mMenuItemOptions.keys();
for (var i = 0; i < keys.size(); i++) {
options.put(keys[i], mMenuItemOptions.get(keys[i]));
}
options.put(:icon, mTapTypeIcon);
return new HomeAssistantNumericMenuItem(
label,
template,
service,
data,
picker,
options,
mHomeAssistantService
);
}
//! Group menu item. //! Group menu item.
//! //!
//! @param definition Items array from the JSON that defines this sub menu. //! @param definition Items array from the JSON that defines this sub menu.

View File

@@ -0,0 +1,100 @@
//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant.
//
// P A Abbey & J D Abbey & @thmichel, 13 October 2025
//
//------------------------------------------------------------
using Toybox.Graphics;
using Toybox.Lang;
using Toybox.WatchUi;
//! Factory that controls which numbers can be picked
class HomeAssistantNumericFactory extends WatchUi.PickerFactory {
// define default values in case not contained in data
private var mStart as Lang.Float = 0.0;
private var mStop as Lang.Float = 100.0;
private var mStep as Lang.Float = 1.0;
private var mFormatString as Lang.String = "%d";
//! Class Constructor
//
public function initialize(picker as Lang.Dictionary) {
PickerFactory.initialize();
// Get values from data
var val = picker["min"];
if (val != null) {
mStart = val.toString().toFloat();
}
val = picker["max"];
if (val != null) {
mStop = val.toString().toFloat();
}
val = picker["step"];
if (val != null) {
mStep = val.toString().toFloat();
}
if (mStep > 0.0) {
var s = mStep;
var dp = 0;
while (s < 1.0) {
s *= 10;
dp++;
// Assigned inside the loop and in each iteration to avoid clobbering the default '%d'.
mFormatString = "%." + dp.toString() + "f";
}
} else {
// The JSON menu definition defined a step size of 0, revert to the default.
mStep = 1.0;
}
}
//! Generate a Drawable instance for an item
//!
//! @param index The item index
//! @param selected true if the current item is selected, false otherwise
//! @return Drawable for the item
//
public function getDrawable(
index as Lang.Number,
selected as Lang.Boolean
) as WatchUi.Drawable? {
var value = getValue(index);
var text = "No item";
if (value instanceof Lang.Float) {
text = value.format(mFormatString);
}
return new WatchUi.Text({
:text => text,
:color => Graphics.COLOR_WHITE,
:locX => WatchUi.LAYOUT_HALIGN_CENTER,
:locY => WatchUi.LAYOUT_VALIGN_CENTER
});
}
//! Get the value of the item at the given index
//!
//! @param index Index of the item to get the value of
//! @return Value of the item
//
public function getValue(index as Lang.Number) as Lang.Object? {
return mStart + (index * mStep);
}
//! Get the number of picker items
//!
//! @return Number of items
//
public function getSize() as Lang.Number {
return ((mStop - mStart) / mStep).toNumber() + 1;
}
}

View File

@@ -0,0 +1,249 @@
//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant.
//
// P A Abbey & J D Abbey & @thmichel, 13 October 2025
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Graphics;
//! Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders
//! a Home Assistant Template.
//
class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem {
private var mHomeAssistantService as HomeAssistantService?;
private var mService as Lang.String?;
private var mConfirm as Lang.Boolean;
private var mExit as Lang.Boolean;
private var mPin as Lang.Boolean;
private var mData as Lang.Dictionary?;
private var mPicker as Lang.Dictionary?;
private var mValue as Lang.Number or Lang.Float = 0;
private var mFormatString as Lang.String = "%d";
//! Class Constructor
//!
//! @param label Menu item label.
//! @param template Menu item template.
//! @param 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 icon Icon to use for the menu item.
//! @param options Menu item options to be passed on, including both SDK and menu options, e.g. exit, confirm & pin.
//! @param haService Shared Home Assistant service object that will perform the required call. Only
//! one of these objects is created for all menu items to re-use.
//
function initialize(
label as Lang.String or Lang.Symbol,
template as Lang.String,
service as Lang.String?,
data as Lang.Dictionary?,
picker as Lang.Dictionary,
options as {
:alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol,
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
:pin as Lang.Boolean
}?,
haService as HomeAssistantService
) {
mService = service;
mData = data;
mPicker = picker;
mExit = options[:exit];
mConfirm = options[:confirm];
mPin = options[:pin];
mLabel = label;
mHomeAssistantService = haService;
HomeAssistantMenuItem.initialize(
label,
template,
{
:alignment => options[:alignment],
:icon => options[:icon]
}
);
if (picker != null) {
var s = picker["step"];
if (s != null) {
var step = s.toFloat() as Lang.Float;
var dp = 0;
while (step < 1.0) {
step *= 10;
dp++;
// Assigned inside the loop and in each iteration to avoid clobbering the default '%d'.
mFormatString = "%." + dp.toString() + "f";
}
}
}
}
function callService() as Void {
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
if (mPin && hasTouchScreen) {
var pin = Settings.getPin();
if (pin != null) {
var pinConfirmationView = new HomeAssistantPinConfirmationView();
WatchUi.pushView(
pinConfirmationView,
new HomeAssistantPinConfirmationDelegate({
:callback => method(:onConfirm),
:pin => pin,
:state => false,
:view => pinConfirmationView,
}),
WatchUi.SLIDE_IMMEDIATE
);
}
} else if (mConfirm) {
if ((! System.getDeviceSettings().phoneConnected ||
! System.getDeviceSettings().connectionAvailable) &&
Settings.getWifiLteExecutionEnabled()) {
var dialogMsg = WatchUi.loadResource($.Rez.Strings.WifiLtePrompt) as Lang.String;
var dialog = new WatchUi.Confirmation(dialogMsg);
WatchUi.pushView(
dialog,
new WifiLteExecutionConfirmDelegate({
:type => "service",
:service => mService,
:data => mData,
:exit => mExit,
}, dialog),
WatchUi.SLIDE_LEFT
);
} else {
var view = new HomeAssistantConfirmation();
WatchUi.pushView(
view,
new HomeAssistantConfirmationDelegate({
:callback => method(:onConfirm),
:confirmationView => view,
:state => false,
}),
WatchUi.SLIDE_IMMEDIATE
);
}
} else {
onConfirm(false);
}
}
//! Callback function after the menu items selection has been (optionally) confirmed.
//!
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
//
function onConfirm(b as Lang.Boolean) as Void {
var dataAttribute = mPicker["data_attribute"];
if (dataAttribute == null) {
//return without call service if no data attribute is set to avoid crash
WatchUi.popView(WatchUi.SLIDE_RIGHT);
return;
}
var entity_id = mData["entity_id"];
if (entity_id == null) {
//return without call service if no entity_id is set to avoid crash
WatchUi.popView(WatchUi.SLIDE_RIGHT);
return;
}
mHomeAssistantService.call(
mService,
{
"entity_id" => entity_id.toString(),
dataAttribute.toString() => mValue
},
mExit
);
WatchUi.popView(WatchUi.SLIDE_RIGHT);
}
//! Return a numeric menu item's fetch state template.
//!
//! @return A string with the menu item's template definition (or null).
//
function getNumericTemplate() as Lang.String? {
var entity_id = mData["entity_id"];
var attribute = mPicker["attribute"] as Lang.String;
if (entity_id == null) {
return null;
} else {
if (attribute == null) {
// Compiler says: "Statement is not reachable."
// This is wrong because a break point on the following line proves it is executed!
return "{{states('" + entity_id.toString() + "')}}";
} else {
return "{{state_attr('" + entity_id.toString() + "','" + attribute + "')}}";
}
}
}
//! Update the menu item's sub label to display the template rendered by Home Assistant.
//!
//! @param data The rendered template (typically a string) to be placed in the sub label. This may
//! unusually be a number if the SDK interprets the JSON returned by Home Assistant as such.
//
public function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
if (data == null) {
setSubLabel($.Rez.Strings.Empty);
} else if(data instanceof Lang.Float) {
var f = data as Lang.Float;
setSubLabel(f.format(mFormatString));
} else if(data instanceof Lang.Number) {
var f = data.toFloat() as Lang.Float;
setSubLabel(f.format(mFormatString));
} else if (data instanceof Lang.String) {
// This should not happen
setSubLabel(data);
} else {
// The template must return a Float on Numeric value, or the item cannot be formatted locally without error.
setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String);
}
WatchUi.requestUpdate();
}
//! Set the Picker's value. Needed to set new value via the Service call
//!
//! @param value New value to set.
//
public function setValue(value as Lang.Number or Lang.Float) as Void {
mValue = value;
}
//! Get the Picker's value.
//!
//! Needed to set new value via the Service call
//
public function getValue() as Lang.Number or Lang.Float {
return mValue;
}
//! Get the original 'data' field supplied by the JSON menu.
//!
//! @return Dictionary containing the 'data' field.
//
public function getData() as Lang.Dictionary {
return mData;
}
// Get the original 'picker' field supplied by the JSON menu.
//!
//! @return Dictionary containing the 'picker' field.
//
public function getPicker() as Lang.Dictionary {
return mPicker;
}
}

View File

@@ -0,0 +1,98 @@
//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant.
//
// P A Abbey & J D Abbey & @thmichel, 13 October 2025
//
//------------------------------------------------------------
using Toybox.Application;
using Toybox.Lang;
using Toybox.Graphics;
using Toybox.System;
using Toybox.WatchUi;
//! Picker that allows the user to choose a float value
//
class HomeAssistantNumericPicker extends WatchUi.Picker {
private var mItem as HomeAssistantNumericMenuItem;
//! Constructor
//
public function initialize(
factory as HomeAssistantNumericFactory,
haItem as HomeAssistantNumericMenuItem
) {
mItem = haItem;
var picker = mItem.getPicker();
var min = (picker.get("min") as Lang.String).toFloat();
var step = (picker.get("step") as Lang.String).toFloat();
var val = haItem.getValue();
if (min == null) {
min = 0.0;
}
if (step == null) {
step = 1.0;
}
WatchUi.Picker.initialize({
:title => new WatchUi.Text({
:text => haItem.getLabel(),
:locX => WatchUi.LAYOUT_HALIGN_CENTER,
:locY => WatchUi.LAYOUT_VALIGN_BOTTOM
}),
:pattern => [factory],
:defaults => [((val - min) / step).toNumber()]
});
}
//! Called when the user has completed picking.
//!
//! @param value Value user selected
//! @return true if user is done, false otherwise
//
public function onConfirm(value as Lang.Number or Lang.Float) as Void {
mItem.setValue(value);
mItem.callService();
}
}
//! Responds to a numeric picker selection or cancellation.
//
class HomeAssistantNumericPickerDelegate extends WatchUi.PickerDelegate {
private var mPicker as HomeAssistantNumericPicker;
//! Constructor
//
public function initialize(picker as HomeAssistantNumericPicker) {
PickerDelegate.initialize();
mPicker = picker;
}
//! Handle a cancel event from the picker
//!
//! @return true if handled, false otherwise
//
public function onCancel() as Lang.Boolean {
WatchUi.popView(WatchUi.SLIDE_RIGHT);
return true;
}
//! Handle a confirm event from the picker
//!
//! @param values The values chosen in the picker
//! @return true if handled, false otherwise
//
public function onAccept(values as Lang.Array) as Lang.Boolean {
mPicker.onConfirm(values[0]);
return true;
}
}

View File

@@ -128,7 +128,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
//! //!
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method. //! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
// //
function onConfirm(b as Lang.Boolean) as Void { public function onConfirm(b as Lang.Boolean) as Void {
if (mService != null) { if (mService != null) {
mHomeAssistantService.call(mService, mData, mExit); mHomeAssistantService.call(mService, mData, mExit);
} }

View File

@@ -23,7 +23,7 @@ using Toybox.Timer;
// //
class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
private var mData as Lang.Dictionary; private var mData as Lang.Dictionary;
private var mTemplate as Lang.String; private var mTemplate as Lang.String?;
private var mExit as Lang.Boolean; private var mExit as Lang.Boolean;
private var mConfirm as Lang.Boolean; private var mConfirm as Lang.Boolean;
private var mPin as Lang.Boolean; private var mPin as Lang.Boolean;

View File

@@ -126,6 +126,24 @@ class HomeAssistantView extends WatchUi.Menu2 {
} }
)); ));
} }
} else if (type.equals("numeric") && service != null) {
if (tap_action != null) {
var picker = tap_action.get("picker") as Lang.Dictionary?;
if (picker != null) {
addItem(HomeAssistantMenuItemFactory.create().numeric(
name,
entity,
content,
service,
picker,
{
:exit => exit,
:confirm => confirm,
:pin => pin
}
));
}
}
} else if (type.equals("info") && content != null) { } else if (type.equals("info") && content != null) {
// Cannot exit from a non-actionable information only menu item. // Cannot exit from a non-actionable information only menu item.
addItem(HomeAssistantMenuItemFactory.create().tap( addItem(HomeAssistantMenuItemFactory.create().tap(
@@ -154,7 +172,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
//! //!
//! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state. //! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state.
// //
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or Null> { function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or HomeAssistantNumericMenuItem or Null> {
var fullList = []; var fullList = [];
var lmi = mItems as Lang.Array<WatchUi.MenuItem>; var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
@@ -167,6 +185,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
fullList.add(item); fullList.add(item);
} }
fullList.addAll(item.getMenuView().getItemsToUpdate()); fullList.addAll(item.getMenuView().getItemsToUpdate());
} else if (item instanceof HomeAssistantNumericMenuItem) {
// Numeric items can have an optional template to evaluate
var nmi = item as HomeAssistantNumericMenuItem;
if (nmi.hasTemplate()) {
fullList.add(item);
}
} else if (item instanceof HomeAssistantToggleMenuItem) { } else if (item instanceof HomeAssistantToggleMenuItem) {
fullList.add(item); fullList.add(item);
} else if (item instanceof HomeAssistantTapMenuItem) { } else if (item instanceof HomeAssistantTapMenuItem) {
@@ -183,8 +207,8 @@ class HomeAssistantView extends WatchUi.Menu2 {
//! Called when this View is brought to the foreground. Restore //! Called when this View is brought to the foreground. Restore
//! the state of this View and prepare it to be shown. This includes //! the state of this View and prepare it to be shown. This includes
//! loading resources into memory. //! loading resources into memory.
//
function onShow() as Void {} function onShow() as Void {}
} }
//! Delegate for the HomeAssistantView. //! Delegate for the HomeAssistantView.
@@ -248,12 +272,17 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
var haItem = item as HomeAssistantTapMenuItem; var haItem = item as HomeAssistantTapMenuItem;
// System.println(haItem.getLabel() + " " + haItem.getId()); // System.println(haItem.getLabel() + " " + haItem.getId());
haItem.callService(); haItem.callService();
} else if (item instanceof HomeAssistantNumericMenuItem) {
var haItem = item as HomeAssistantNumericMenuItem;
// System.println(haItem.getLabel() + " " + haItem.getId());
// create new view to select new value
var mPickerFactory = new HomeAssistantNumericFactory((haItem as HomeAssistantNumericMenuItem).getPicker());
var mPicker = new HomeAssistantNumericPicker(mPickerFactory,haItem);
var mPickerDelegate = new HomeAssistantNumericPickerDelegate(mPicker);
WatchUi.pushView(mPicker,mPickerDelegate,WatchUi.SLIDE_LEFT);
} else if (item instanceof HomeAssistantGroupMenuItem) { } else if (item instanceof HomeAssistantGroupMenuItem) {
var haMenuItem = item as HomeAssistantGroupMenuItem; var haMenuItem = item as HomeAssistantGroupMenuItem;
// System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());
WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT); WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT);
// } else {
// System.println(item.getLabel() + " " + item.getId());
} }
} }

View File

@@ -29,10 +29,10 @@ using Toybox.Time;
// //
(:glance, :background) (:glance, :background)
class Settings { class Settings {
private static var mApiKey as Lang.String = ""; private static var mApiKey as Lang.String? = "";
private static var mWebhookId as Lang.String = ""; private static var mWebhookId as Lang.String? = "";
private static var mApiUrl as Lang.String = ""; private static var mApiUrl as Lang.String? = "";
private static var mConfigUrl as Lang.String = ""; private static var mConfigUrl as Lang.String? = "";
private static var mCacheConfig as Lang.Boolean = false; private static var mCacheConfig as Lang.Boolean = false;
private static var mClearCache as Lang.Boolean = false; private static var mClearCache as Lang.Boolean = false;
private static var mMenuCheck as Lang.Boolean = false; private static var mMenuCheck as Lang.Boolean = false;

View File

@@ -30,7 +30,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
:url as Lang.String?, :url as Lang.String?,
:id as Lang.Number?, :id as Lang.Number?,
:exit as Lang.Boolean :exit as Lang.Boolean
}; }?;
private static var mTimer as Timer.Timer?; private static var mTimer as Timer.Timer?;
private var mHasToast as Lang.Boolean = false; private var mHasToast as Lang.Boolean = false;