Compare commits

...

44 Commits

Author SHA1 Message Date
Restyled.io
cf6552410e Restyled by whitespace 2024-01-27 15:39:45 +00:00
Joseph Abbey
6b3a17bea3 Add GPS accuracy to update_location webhook and changed activity reporting 2024-01-27 15:39:22 +00:00
Philip Abbey
8f372c03e3 Merge branch 'main' into 94-add-gps-reporting-to-the-background-service 2024-01-27 15:06:06 +00:00
Philip Abbey
f5f88ced4e Added web-based editor to documentation 2024-01-27 14:59:48 +00:00
Joseph Abbey
96abff9339 Fix spacing of arrows 2024-01-27 14:02:05 +00:00
Philip Abbey
4ff1509046 API level fix
Added 'has' test for Activity.getProfileInfo(). Reverted automatic changes to XML prettiness.

Co-Authored-By: Joseph Abbey <me@josephabbey.dev>
2024-01-27 13:57:21 +00:00
Joseph Abbey
1ec80a1704 Update restyled.yml 2024-01-27 13:39:44 +00:00
Joseph Abbey
e3288c9353 GPS and current activity in background service 2024-01-27 13:11:24 +00:00
Joseph Abbey
2ba102f8dd Merge pull request #92 from house-of-abbey/91-enhanced-web-editor 2024-01-25 19:56:40 +00:00
Joseph Abbey
3e0789e808 Update config.schema.json 2024-01-25 19:34:51 +00:00
Joseph Abbey
bd56c6b4e6 Titles for buttons 2024-01-25 15:19:39 +00:00
Joseph Abbey
6063ae8ba3 Titles for form elements 2024-01-25 14:56:28 +00:00
Joseph Abbey
f00bbdcb13 Merge pull request #93 from house-of-abbey/restyled/91-enhanced-web-editor 2024-01-25 14:30:59 +00:00
Restyled.io
2b98ed885e Restyled by whitespace 2024-01-25 14:28:38 +00:00
Restyled.io
4a8185a937 Restyled by prettier-json 2024-01-25 14:28:38 +00:00
Restyled.io
bb55ed4c69 Restyled by prettier 2024-01-25 14:28:36 +00:00
Restyled.io
4ddf8339b8 Restyled by jq 2024-01-25 14:28:34 +00:00
Restyled.io
7b227499c8 Restyled by clang-format 2024-01-25 14:28:32 +00:00
Joseph Abbey
0849524ea9 formatting 2024-01-25 14:28:03 +00:00
Joseph Abbey
b54b1d8cae Add autocomplete for entity names in templates 2024-01-25 13:40:02 +00:00
Joseph Abbey
17b1e38145 Merge branch 'main' into 91-enhanced-web-editor 2024-01-25 10:43:44 +00:00
Joseph Abbey
2afd0295b4 Error handling 2024-01-25 10:40:27 +00:00
Joseph Abbey
83f8b7bf26 Create _config.yml 2024-01-23 21:54:51 +00:00
Joseph Abbey
c4066e9fe3 Enhanced web editor 2024-01-23 21:19:07 +00:00
Philip Abbey
c062a6fcff Update HISTORY.md
Added V2.5 description.
2024-01-23 19:29:29 +00:00
Philip Abbey
7ab8246197 Merge pull request #88 from house-of-abbey/87-look-for-memory-usage-efficiencies
87 look for memory usage efficiencies
2024-01-23 07:13:22 +00:00
Philip Abbey
776134e842 Merge pull request #89 from house-of-abbey/restyled/87-look-for-memory-usage-efficiencies
Restyle 87 look for memory usage efficiencies
2024-01-22 22:25:46 +00:00
Restyled.io
a95736ebed Restyled by whitespace 2024-01-22 22:20:26 +00:00
Philip Abbey
9c001f3402 Cosmetic 2024-01-22 10:28:35 +00:00
Philip Abbey
7786efd883 Update HomeAssistantApp.mc
Removed memory metrics
2024-01-21 20:46:55 +00:00
Philip Abbey
0b80e4546d Corrected a previous incomplete commit
All now "WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String"
2024-01-21 20:43:11 +00:00
Philip Abbey
6e67c4cf2a Removed RezStrings.mc
And in-lined the resource strings fetching.
2024-01-21 20:38:01 +00:00
Philip Abbey
b80227e484 Update Settings.mc
Cosmetic
2024-01-21 20:11:47 +00:00
Philip Abbey
d9b345e5b8 Update Settings.mc
Cosmetic
2024-01-21 20:04:12 +00:00
Philip Abbey
fc7302ad3b Update HomeAssistantView.mc
Removed empty else clause.
2024-01-21 20:00:52 +00:00
Philip Abbey
d9ecaf34ee Removed Debug
On some devices it looks like removing the System.println() statements from inside an 'if' clause whose condition is a constant (static constant Globals.scDebug) makes a memory saving. This would suggest the compiler does not propagate constants and prune unreachable code. However in the device of greatest interest debug removal has made no difference to the memory usage. Here the conditional clauses have been turned into comments that can be removed on a case-by-case basis otherwise the debug printing is too voluminous anyway.
2024-01-21 17:53:37 +00:00
Joseph Abbey
62b8f0fccf Add max size to menus 2024-01-21 13:06:44 +00:00
Philip Abbey
26954cbc60 Update RootView.mc
Memory usage decimal place was never used due to integer arithmetic. A single character changes fixed that.
2024-01-21 13:00:31 +00:00
Philip Abbey
a5b2af81bc Merge pull request #81 from house-of-abbey/80-examples
Added examples for templates and custom switches
2024-01-20 15:07:32 +00:00
Joseph Abbey
10c64c0fdc Merge pull request #86 from house-of-abbey/restyled/80-examples 2024-01-20 15:06:25 +00:00
Restyled.io
ed3dce8827 Restyled by prettier-json 2024-01-20 15:05:50 +00:00
Restyled.io
35a65ebdf4 Restyled by jq 2024-01-20 15:05:49 +00:00
Joseph Abbey
1448f6b0c2 Update config.schema.json 2024-01-20 12:10:17 +00:00
Joseph Abbey
5620ea6695 Update config.schema.json 2024-01-19 21:17:24 +00:00
31 changed files with 2834 additions and 991 deletions

View File

@@ -1,2 +1,5 @@
exclude:
- "**/*.md"
- '**/*.md'
- '**/pnpm-lock.yaml'
- 'manifest.xml'
- 'manifest-widget.xml'

10
.prettierrc Normal file
View File

@@ -0,0 +1,10 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"bracketSameLine": true,
"arrowParens": "always",
"experimentalTernaries": true,
"semi": true,
"trailingComma": "es5"
}

View File

@@ -17,3 +17,4 @@
| 2.2 | Adds a feature to cache the menu configuration and save the time taken for an HTTP request to fetch it. You as the user are responsible for managing the cache by clearing it when you update your configuration. Improvement to widget root display updates. Bug fix for battery level reporting when in the glance carousel. Fixed an uninternationalised string, "Execute". Unfixed issue with battery level updates when the user is not an administrator. |
| 2.3 | Fix for battery level updates where previously the function only worked for administrator accounts. The new solution is based on Webhooks and is simpler to implement on Home Assistant. Language support fix where an automatic translation produced an inappropriate word, possibly in more than one language. |
| 2.4 | Sensor status reporting via Home Assistant 'templates'. This provides a generalised way of viewing the status of any entity as long as the result can be rendered as text, e.g. 'uncovered', 'open', '76%', '21 °C'. Removal of the menu style option. The original style was kept after the introduction of the icon style solely to keep the code for a possible re-use for sensor statuses. This version delivers that new feature, hence the style option has been removed. The new JSON configuration file format allows for the old style to be replicated if you are desperate! Added a feature to provide parameters to actions (`tap` or `template`). Added a feature to confirm `toggle` menu items. |
| 2.5 | A small memory efficiency of about 1kB by removing `RezStrings.mc`. This will aid widgets on old watches that only have 60kB available to an application and are using about 45kB before the menu is fetched, hence 1kB is more significant to those devices. |

View File

@@ -184,9 +184,10 @@ This allows the `confirm` field to be accommodated in the `tap_action` along sid
## Editing the JSON file
You have options. The first is what we use.
1. **Best!** Use the [Studio Code Server](https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863) addon for Home Assistant. You can then edit your JSON file in place.
2. Locally installed VSCode, or if not installed,
3. try the on-line version at https://vscode.dev/, which works really well.
1. **Best!** Use the GarminHomeAssistant [Web-based Editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) which includes `entity` and `service` name completion and validation by fetching data from your own Home Assistant instance. _Pretty nifty eh?_ The other method listed below do not add this convenience and checking.
2. Use the [Studio Code Server](https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863) addon for Home Assistant. You can then edit your JSON file in place.
3. Locally installed VSCode, or if not installed, try
4. The on-line version at https://vscode.dev/, which works really well.
Paste in your JSON (and change the file type to JSON if not saving), it will then verify your file format and schema for you, highlighting any errors for you to fix.

View File

@@ -2,6 +2,19 @@
# Troubleshooting Guides
## Check your JSON Schema
Before [raising an issue](https://github.com/house-of-abbey/GarminHomeAssistant/issues) about a possible bug, _please, please_ check your JSON is compliant with both the JSON format and our schema. To do this you have options. The first is what we use.
1. **Best!** Use the GarminHomeAssistant [Web-based Editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) which includes `entity` and `service` name completion and validation by fetching data from your own Home Assistant instance. _Pretty nifty eh?_ The other method listed below do not add this convenience and checking.
2. Use the [Studio Code Server](https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863) addon for Home Assistant. You can then edit your JSON file in place.
3. Locally installed VSCode, or if not installed, try
4. The on-line version at https://vscode.dev/, which works really well. Paste in your JSON (and change the file type to JSON if not saving), it will then verify your file format and schema for you, highlighting any errors for you to fix.
A failure to get the file format right tends to mean that the response to the application errors with `INVALID_HTTP_BODY_IN_NETWORK_RESPONSE` (code of -400). This means the response did not contain JSON, it was probably an error message in plain text that could not be parsed by the Connect IQ API call. See [Toybox.Communications](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html) for the list of error code you might be presented with on your device.
Make sure you can browse to the URL of your JSON file in a standard web browser to make sure it is accessible.
## Watch Menu and API
With either of the following setups, there are inevitably some problems along the way. GarminHomeAssistant is careful to rely only on having working URLs. Getting them working is the user's responsibility. However, we have developed some fault finding tools.

3
_config.yml Normal file
View File

@@ -0,0 +1,3 @@
exclude:
- examples
- source

View File

@@ -12,10 +12,7 @@
"type": "string"
}
},
"required": [
"title",
"items"
],
"required": ["title", "items"],
"additionalProperties": false,
"$defs": {
"toggle": {
@@ -43,42 +40,64 @@
"additionalProperties": false
}
},
"required": [
"entity",
"name",
"type"
],
"required": ["entity", "name", "type"],
"additionalProperties": false
},
"template": {
"type": "object",
"properties": {
"entity": {
"$ref": "#/$defs/entity"
"oneOf": [
{
"type": "object",
"properties": {
"entity": {
"$ref": "#/$defs/entity",
"deprecated": true,
"title": "Schema change:",
"description": "Use 'tap_action' instead to mirror Home Assistant."
},
"name": {
"title": "Your familiar name",
"type": "string"
},
"content": {
"title": "What to display (template)",
"type": "string"
},
"type": {
"title": "Menu item type",
"description": "One of 'tap', 'template', 'toggle' or 'group'.",
"const": "template"
}
},
"required": ["name", "content", "type"],
"additionalProperties": false
},
"name": {
"title": "Your familiar name",
"type": "string"
},
"content": {
"title": "What to display (template)",
"type": "string"
},
"type": {
"title": "Menu item type",
"description": "One of 'tap', 'template', 'toggle' or 'group'.",
"const": "template"
},
"tap_action": {
"$ref": "#/$defs/tap_action"
{
"type": "object",
"properties": {
"entity": {
"$ref": "#/$defs/entity"
},
"name": {
"title": "Your familiar name",
"type": "string"
},
"content": {
"title": "What to display (template)",
"type": "string"
},
"type": {
"title": "Menu item type",
"description": "One of 'tap', 'template', 'toggle' or 'group'.",
"const": "template"
},
"tap_action": {
"$ref": "#/$defs/tap_action"
}
},
"required": ["name", "content", "type", "tap_action"],
"additionalProperties": false
}
},
"required": [
"name",
"content",
"type"
],
"additionalProperties": false
]
},
"tap": {
"type": "object",
@@ -107,18 +126,10 @@
},
"oneOf": [
{
"required": [
"name",
"type",
"service"
]
"required": ["name", "type", "service"]
},
{
"required": [
"name",
"type",
"tap_action"
]
"required": ["name", "type", "tap_action"]
}
],
"additionalProperties": false
@@ -129,7 +140,6 @@
"entity": {
"$ref": "#/$defs/entity",
"type": "string",
"pattern": "^[^.]+\\.[^.]+$",
"deprecated": true,
"title": "Schema change:",
"description": "'entity' is no longer necessary and should now be removed."
@@ -151,16 +161,12 @@
"$ref": "#/$defs/items"
}
},
"required": [
"name",
"title",
"type",
"items"
],
"required": ["name", "title", "type", "items"],
"additionalProperties": false
},
"items": {
"type": "array",
"maxItems": 16,
"items": {
"oneOf": [
{
@@ -205,9 +211,7 @@
"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"
]
"required": ["service"]
},
"confirm": {
"type": "boolean",

View File

@@ -34,115 +34,116 @@
"Monkey C: Edit Products" - Lets you add or remove any product
-->
<iq:products>
<iq:product id="approachs7042mm"/>
<iq:product id="approachs7047mm"/>
<iq:product id="d2air"/>
<iq:product id="d2airx10"/>
<iq:product id="d2delta"/>
<iq:product id="d2deltapx"/>
<iq:product id="d2deltas"/>
<iq:product id="d2mach1"/>
<iq:product id="descentg1"/>
<iq:product id="descentmk2"/>
<iq:product id="descentmk2s"/>
<iq:product id="edge1030"/>
<iq:product id="edge1030bontrager"/>
<iq:product id="edge1030plus"/>
<iq:product id="edge1040"/>
<iq:product id="edge520plus"/>
<iq:product id="edge530"/>
<iq:product id="edge820"/>
<iq:product id="edge830"/>
<iq:product id="edgeexplore"/>
<iq:product id="edgeexplore2"/>
<iq:product id="enduro"/>
<iq:product id="epix2"/>
<iq:product id="epix2pro42mm"/>
<iq:product id="epix2pro47mm"/>
<iq:product id="epix2pro51mm"/>
<iq:product id="fenix5"/>
<iq:product id="fenix5plus"/>
<iq:product id="fenix5s"/>
<iq:product id="fenix5splus"/>
<iq:product id="fenix5x"/>
<iq:product id="fenix5xplus"/>
<iq:product id="fenix6"/>
<iq:product id="fenix6pro"/>
<iq:product id="fenix6s"/>
<iq:product id="fenix6spro"/>
<iq:product id="fenix6xpro"/>
<iq:product id="fenix7"/>
<iq:product id="fenix7pro"/>
<iq:product id="fenix7pronowifi"/>
<iq:product id="fenix7s"/>
<iq:product id="fenix7spro"/>
<iq:product id="fenix7x"/>
<iq:product id="fenix7xpro"/>
<iq:product id="fenix7xpronowifi"/>
<iq:product id="fenixchronos"/>
<iq:product id="fr245"/>
<iq:product id="fr245m"/>
<iq:product id="fr255"/>
<iq:product id="fr255m"/>
<iq:product id="fr255s"/>
<iq:product id="fr255sm"/>
<iq:product id="fr265"/>
<iq:product id="fr265s"/>
<iq:product id="fr55"/>
<iq:product id="fr645"/>
<iq:product id="fr645m"/>
<iq:product id="fr745"/>
<iq:product id="fr935"/>
<iq:product id="fr945"/>
<iq:product id="fr945lte"/>
<iq:product id="fr955"/>
<iq:product id="fr965"/>
<iq:product id="gpsmap67"/>
<iq:product id="instinct2"/>
<iq:product id="instinct2s"/>
<iq:product id="instinct2x"/>
<iq:product id="instinctcrossover"/>
<iq:product id="legacyherocaptainmarvel"/>
<iq:product id="legacyherofirstavenger"/>
<iq:product id="legacysagadarthvader"/>
<iq:product id="legacysagarey"/>
<iq:product id="marq2"/>
<iq:product id="marq2aviator"/>
<iq:product id="marqadventurer"/>
<iq:product id="marqathlete"/>
<iq:product id="marqaviator"/>
<iq:product id="marqcaptain"/>
<iq:product id="marqcommander"/>
<iq:product id="marqdriver"/>
<iq:product id="marqexpedition"/>
<iq:product id="marqgolfer"/>
<iq:product id="montana7xx"/>
<iq:product id="venu"/>
<iq:product id="venu2"/>
<iq:product id="venu2plus"/>
<iq:product id="venu2s"/>
<iq:product id="venu3"/>
<iq:product id="venu3s"/>
<iq:product id="venud"/>
<iq:product id="venusq"/>
<iq:product id="venusq2"/>
<iq:product id="venusq2m"/>
<iq:product id="venusqm"/>
<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="approachs7042mm" />
<iq:product id="approachs7047mm" />
<iq:product id="d2air" />
<iq:product id="d2airx10" />
<iq:product id="d2delta" />
<iq:product id="d2deltapx" />
<iq:product id="d2deltas" />
<iq:product id="d2mach1" />
<iq:product id="descentg1" />
<iq:product id="descentmk2" />
<iq:product id="descentmk2s" />
<iq:product id="edge1030" />
<iq:product id="edge1030bontrager" />
<iq:product id="edge1030plus" />
<iq:product id="edge1040" />
<iq:product id="edge520plus" />
<iq:product id="edge530" />
<iq:product id="edge820" />
<iq:product id="edge830" />
<iq:product id="edgeexplore" />
<iq:product id="edgeexplore2" />
<iq:product id="enduro" />
<iq:product id="epix2" />
<iq:product id="epix2pro42mm" />
<iq:product id="epix2pro47mm" />
<iq:product id="epix2pro51mm" />
<iq:product id="fenix5" />
<iq:product id="fenix5plus" />
<iq:product id="fenix5s" />
<iq:product id="fenix5splus" />
<iq:product id="fenix5x" />
<iq:product id="fenix5xplus" />
<iq:product id="fenix6" />
<iq:product id="fenix6pro" />
<iq:product id="fenix6s" />
<iq:product id="fenix6spro" />
<iq:product id="fenix6xpro" />
<iq:product id="fenix7" />
<iq:product id="fenix7pro" />
<iq:product id="fenix7pronowifi" />
<iq:product id="fenix7s" />
<iq:product id="fenix7spro" />
<iq:product id="fenix7x" />
<iq:product id="fenix7xpro" />
<iq:product id="fenix7xpronowifi" />
<iq:product id="fenixchronos" />
<iq:product id="fr245" />
<iq:product id="fr245m" />
<iq:product id="fr255" />
<iq:product id="fr255m" />
<iq:product id="fr255s" />
<iq:product id="fr255sm" />
<iq:product id="fr265" />
<iq:product id="fr265s" />
<iq:product id="fr55" />
<iq:product id="fr645" />
<iq:product id="fr645m" />
<iq:product id="fr745" />
<iq:product id="fr935" />
<iq:product id="fr945" />
<iq:product id="fr945lte" />
<iq:product id="fr955" />
<iq:product id="fr965" />
<iq:product id="gpsmap67" />
<iq:product id="instinct2" />
<iq:product id="instinct2s" />
<iq:product id="instinct2x" />
<iq:product id="instinctcrossover" />
<iq:product id="legacyherocaptainmarvel" />
<iq:product id="legacyherofirstavenger" />
<iq:product id="legacysagadarthvader" />
<iq:product id="legacysagarey" />
<iq:product id="marq2" />
<iq:product id="marq2aviator" />
<iq:product id="marqadventurer" />
<iq:product id="marqathlete" />
<iq:product id="marqaviator" />
<iq:product id="marqcaptain" />
<iq:product id="marqcommander" />
<iq:product id="marqdriver" />
<iq:product id="marqexpedition" />
<iq:product id="marqgolfer" />
<iq:product id="montana7xx" />
<iq:product id="venu" />
<iq:product id="venu2" />
<iq:product id="venu2plus" />
<iq:product id="venu2s" />
<iq:product id="venu3" />
<iq:product id="venu3s" />
<iq:product id="venud" />
<iq:product id="venusq" />
<iq:product id="venusq2" />
<iq:product id="venusq2m" />
<iq:product id="venusqm" />
<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:products>
<!--
Use "Monkey C: Edit Permissions" from the Visual Studio Code command
palette to update permissions.
-->
<iq:permissions>
<iq:uses-permission id="Background"/>
<iq:uses-permission id="BluetoothLowEnergy"/>
<iq:uses-permission id="Communications"/>
<iq:uses-permission id="Background" />
<iq:uses-permission id="BluetoothLowEnergy" />
<iq:uses-permission id="Communications" />
<iq:uses-permission id="Positioning" />
</iq:permissions>
<!--
Use "Monkey C: Edit Languages" from the Visual Studio Code command
@@ -190,6 +191,6 @@
Use "Monkey C: Configure Monkey Barrel" from the Visual Studio Code
command palette to edit the included barrels.
-->
<iq:barrels/>
<iq:barrels />
</iq:application>
</iq:manifest>

View File

@@ -40,115 +40,116 @@
"Monkey C: Edit Products" - Lets you add or remove any product
-->
<iq:products>
<iq:product id="approachs7042mm"/>
<iq:product id="approachs7047mm"/>
<iq:product id="d2air"/>
<iq:product id="d2airx10"/>
<iq:product id="d2delta"/>
<iq:product id="d2deltapx"/>
<iq:product id="d2deltas"/>
<iq:product id="d2mach1"/>
<iq:product id="descentg1"/>
<iq:product id="descentmk2"/>
<iq:product id="descentmk2s"/>
<iq:product id="edge1030"/>
<iq:product id="edge1030bontrager"/>
<iq:product id="edge1030plus"/>
<iq:product id="edge1040"/>
<iq:product id="edge520plus"/>
<iq:product id="edge530"/>
<iq:product id="edge820"/>
<iq:product id="edge830"/>
<iq:product id="edgeexplore"/>
<iq:product id="edgeexplore2"/>
<iq:product id="enduro"/>
<iq:product id="epix2"/>
<iq:product id="epix2pro42mm"/>
<iq:product id="epix2pro47mm"/>
<iq:product id="epix2pro51mm"/>
<iq:product id="fenix5"/>
<iq:product id="fenix5plus"/>
<iq:product id="fenix5s"/>
<iq:product id="fenix5splus"/>
<iq:product id="fenix5x"/>
<iq:product id="fenix5xplus"/>
<iq:product id="fenix6"/>
<iq:product id="fenix6pro"/>
<iq:product id="fenix6s"/>
<iq:product id="fenix6spro"/>
<iq:product id="fenix6xpro"/>
<iq:product id="fenix7"/>
<iq:product id="fenix7pro"/>
<iq:product id="fenix7pronowifi"/>
<iq:product id="fenix7s"/>
<iq:product id="fenix7spro"/>
<iq:product id="fenix7x"/>
<iq:product id="fenix7xpro"/>
<iq:product id="fenix7xpronowifi"/>
<iq:product id="fenixchronos"/>
<iq:product id="fr245"/>
<iq:product id="fr245m"/>
<iq:product id="fr255"/>
<iq:product id="fr255m"/>
<iq:product id="fr255s"/>
<iq:product id="fr255sm"/>
<iq:product id="fr265"/>
<iq:product id="fr265s"/>
<iq:product id="fr55"/>
<iq:product id="fr645"/>
<iq:product id="fr645m"/>
<iq:product id="fr745"/>
<iq:product id="fr935"/>
<iq:product id="fr945"/>
<iq:product id="fr945lte"/>
<iq:product id="fr955"/>
<iq:product id="fr965"/>
<iq:product id="gpsmap67"/>
<iq:product id="instinct2"/>
<iq:product id="instinct2s"/>
<iq:product id="instinct2x"/>
<iq:product id="instinctcrossover"/>
<iq:product id="legacyherocaptainmarvel"/>
<iq:product id="legacyherofirstavenger"/>
<iq:product id="legacysagadarthvader"/>
<iq:product id="legacysagarey"/>
<iq:product id="marq2"/>
<iq:product id="marq2aviator"/>
<iq:product id="marqadventurer"/>
<iq:product id="marqathlete"/>
<iq:product id="marqaviator"/>
<iq:product id="marqcaptain"/>
<iq:product id="marqcommander"/>
<iq:product id="marqdriver"/>
<iq:product id="marqexpedition"/>
<iq:product id="marqgolfer"/>
<iq:product id="montana7xx"/>
<iq:product id="venu"/>
<iq:product id="venu2"/>
<iq:product id="venu2plus"/>
<iq:product id="venu2s"/>
<iq:product id="venu3"/>
<iq:product id="venu3s"/>
<iq:product id="venud"/>
<iq:product id="venusq"/>
<iq:product id="venusq2"/>
<iq:product id="venusq2m"/>
<iq:product id="venusqm"/>
<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="approachs7042mm" />
<iq:product id="approachs7047mm" />
<iq:product id="d2air" />
<iq:product id="d2airx10" />
<iq:product id="d2delta" />
<iq:product id="d2deltapx" />
<iq:product id="d2deltas" />
<iq:product id="d2mach1" />
<iq:product id="descentg1" />
<iq:product id="descentmk2" />
<iq:product id="descentmk2s" />
<iq:product id="edge1030" />
<iq:product id="edge1030bontrager" />
<iq:product id="edge1030plus" />
<iq:product id="edge1040" />
<iq:product id="edge520plus" />
<iq:product id="edge530" />
<iq:product id="edge820" />
<iq:product id="edge830" />
<iq:product id="edgeexplore" />
<iq:product id="edgeexplore2" />
<iq:product id="enduro" />
<iq:product id="epix2" />
<iq:product id="epix2pro42mm" />
<iq:product id="epix2pro47mm" />
<iq:product id="epix2pro51mm" />
<iq:product id="fenix5" />
<iq:product id="fenix5plus" />
<iq:product id="fenix5s" />
<iq:product id="fenix5splus" />
<iq:product id="fenix5x" />
<iq:product id="fenix5xplus" />
<iq:product id="fenix6" />
<iq:product id="fenix6pro" />
<iq:product id="fenix6s" />
<iq:product id="fenix6spro" />
<iq:product id="fenix6xpro" />
<iq:product id="fenix7" />
<iq:product id="fenix7pro" />
<iq:product id="fenix7pronowifi" />
<iq:product id="fenix7s" />
<iq:product id="fenix7spro" />
<iq:product id="fenix7x" />
<iq:product id="fenix7xpro" />
<iq:product id="fenix7xpronowifi" />
<iq:product id="fenixchronos" />
<iq:product id="fr245" />
<iq:product id="fr245m" />
<iq:product id="fr255" />
<iq:product id="fr255m" />
<iq:product id="fr255s" />
<iq:product id="fr255sm" />
<iq:product id="fr265" />
<iq:product id="fr265s" />
<iq:product id="fr55" />
<iq:product id="fr645" />
<iq:product id="fr645m" />
<iq:product id="fr745" />
<iq:product id="fr935" />
<iq:product id="fr945" />
<iq:product id="fr945lte" />
<iq:product id="fr955" />
<iq:product id="fr965" />
<iq:product id="gpsmap67" />
<iq:product id="instinct2" />
<iq:product id="instinct2s" />
<iq:product id="instinct2x" />
<iq:product id="instinctcrossover" />
<iq:product id="legacyherocaptainmarvel" />
<iq:product id="legacyherofirstavenger" />
<iq:product id="legacysagadarthvader" />
<iq:product id="legacysagarey" />
<iq:product id="marq2" />
<iq:product id="marq2aviator" />
<iq:product id="marqadventurer" />
<iq:product id="marqathlete" />
<iq:product id="marqaviator" />
<iq:product id="marqcaptain" />
<iq:product id="marqcommander" />
<iq:product id="marqdriver" />
<iq:product id="marqexpedition" />
<iq:product id="marqgolfer" />
<iq:product id="montana7xx" />
<iq:product id="venu" />
<iq:product id="venu2" />
<iq:product id="venu2plus" />
<iq:product id="venu2s" />
<iq:product id="venu3" />
<iq:product id="venu3s" />
<iq:product id="venud" />
<iq:product id="venusq" />
<iq:product id="venusq2" />
<iq:product id="venusq2m" />
<iq:product id="venusqm" />
<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:products>
<!--
Use "Monkey C: Edit Permissions" from the Visual Studio Code command
palette to update permissions.
-->
<iq:permissions>
<iq:uses-permission id="Background"/>
<iq:uses-permission id="BluetoothLowEnergy"/>
<iq:uses-permission id="Communications"/>
<iq:uses-permission id="Background" />
<iq:uses-permission id="BluetoothLowEnergy" />
<iq:uses-permission id="Communications" />
<iq:uses-permission id="Positioning" />
</iq:permissions>
<!--
Use "Monkey C: Edit Languages" from the Visual Studio Code command
@@ -196,6 +197,6 @@
Use "Monkey C: Configure Monkey Barrel" from the Visual Studio Code
command palette to edit the included barrels.
-->
<iq:barrels/>
<iq:barrels />
</iq:application>
</iq:manifest>

View File

@@ -32,45 +32,98 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
}
function onReturnBatteryUpdate(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
if (Globals.scDebug) {
System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Code: " + responseCode);
System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Data: " + data);
}
// System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Code: " + responseCode);
// System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Data: " + data);
Background.exit(null);
}
function onTemporalEvent() as Void {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("BackgroundServiceDelegate onTemporalEvent(): No Phone connection, skipping API call.");
}
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("BackgroundServiceDelegate onTemporalEvent(): No Internet connection, skipping API call.");
}
if (!System.getDeviceSettings().phoneConnected) {
// System.println("BackgroundServiceDelegate onTemporalEvent(): No Phone connection, skipping API call.");
} else if (!System.getDeviceSettings().connectionAvailable) {
// System.println("BackgroundServiceDelegate onTemporalEvent(): No Internet connection, skipping API call.");
} else {
// System.println("BackgroundServiceDelegate onTemporalEvent(): Making API call.");
var position = Position.getInfo();
// System.println("BackgroundServiceDelegate onTemporalEvent(): gps: " + position.position.toDegrees());
// System.println("BackgroundServiceDelegate onTemporalEvent(): speed: " + position.speed);
// System.println("BackgroundServiceDelegate onTemporalEvent(): course: " + position.heading + "rad (" + (position.heading * 180 / Math.PI) + "°)");
// System.println("BackgroundServiceDelegate onTemporalEvent(): altitude: " + position.altitude);
// System.println("BackgroundServiceDelegate onTemporalEvent(): battery: " + System.getSystemStats().battery);
// System.println("BackgroundServiceDelegate onTemporalEvent(): charging: " + System.getSystemStats().charging);
// System.println("BackgroundServiceDelegate onTemporalEvent(): activity: " + Activity.getProfileInfo().name);
// Don't use Settings.* here as the object lasts < 30 secs and is recreated each time the background service is run
if (position.accuracy != Position.QUALITY_NOT_AVAILABLE && position.accuracy != Position.QUALITY_LAST_KNOWN) {
var accuracy = 0;
switch (position.accuracy) {
case Position.QUALITY_POOR:
accuracy = 500;
break;
case Position.QUALITY_USABLE:
accuracy = 100;
break;
case Position.QUALITY_GOOD:
accuracy = 10;
break;
}
Communications.makeWebRequest(
(Properties.getValue("api_url") as Lang.String) + "/webhook/" + (Properties.getValue("webhook_id") as Lang.String),
{
"type" => "update_location",
"data" => {
"gps" => position.position.toDegrees(),
"gps_accuracy" => accuracy,
"speed" => Math.round(position.speed),
"course" => Math.round(position.heading * 180 / Math.PI),
"altitude" => Math.round(position.altitude),
}
},
{
:method => Communications.HTTP_REQUEST_METHOD_POST,
:headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
},
method(:onReturnBatteryUpdate)
);
}
var data = [
{
"state" => System.getSystemStats().battery,
"type" => "sensor",
"unique_id" => "battery_level"
},
{
"state" => System.getSystemStats().charging,
"type" => "binary_sensor",
"unique_id" => "battery_is_charging"
}
];
if (Activity has :getProfileInfo) {
data.add({
"state" => Activity.getProfileInfo().sport,
"type" => "sensor",
"unique_id" => "activity"
});
data.add({
"state" => Activity.getProfileInfo().subSport,
"type" => "sensor",
"unique_id" => "sub_activity"
});
}
Communications.makeWebRequest(
(Properties.getValue("api_url") as Lang.String) + "/webhook/" + (Properties.getValue("webhook_id") as Lang.String),
{
"type" => "update_sensor_states",
"data" => [
{
"state" => System.getSystemStats().battery,
"type" => "sensor",
"unique_id" => "battery_level"
},
{
"state" => System.getSystemStats().charging,
"type" => "binary_sensor",
"unique_id" => "battery_is_charging"
}
]
"data" => data
},
{
:method => Communications.HTTP_REQUEST_METHOD_POST,
:headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
},

View File

@@ -22,9 +22,6 @@ using Toybox.Lang;
(:glance)
class Globals {
// Enable printing of messages to the debug console (don't make this a Property
// as the messages can't be read from a watch!)
static const scDebug = false;
static const scAlertTimeout = 2000; // ms
static const scTapTimeout = 1000; // ms
// Time to let the existing HTTP responses get serviced after a
@@ -34,5 +31,5 @@ class Globals {
// an ErrorView.
static const scApiResume = 200; // ms
// Warn the user after fetching the menu if their watch is low on memory before the device crashes.
static const scLowMem = 0.95; // percent as a fraction.
static const scLowMem = 0.90; // percent as a fraction.
}

View File

@@ -32,8 +32,9 @@ class HomeAssistantApp extends Application.AppBase {
private var mHaMenu as HomeAssistantView or Null;
private var mQuitTimer as QuitTimer or Null;
private var mTimer as Timer.Timer or Null;
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTemplateMenuItem> or Null; // Array initialised by onReturnFetchMenuConfig()
private var mNextItemToUpdate as Lang.Number = 0; // Index into the above array
// Array initialised by onReturnFetchMenuConfig()
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTemplateMenuItem> or Null;
private var mNextItemToUpdate as Lang.Number = 0; // Index into the above array
private var mIsGlance as Lang.Boolean = false;
private var mIsApp as Lang.Boolean = false; // Or Widget
@@ -88,41 +89,29 @@ class HomeAssistantApp extends Application.AppBase {
function getInitialView() as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>? {
mIsApp = true;
mQuitTimer = new QuitTimer();
RezStrings.update();
mApiStatus = RezStrings.getChecking();
mMenuStatus = RezStrings.getChecking();
// RezStrings.update();
mApiStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
Settings.update();
if (Settings.getApiKey().length() == 0) {
if (Globals.scDebug) {
System.println("HomeAssistantApp getInitialView(): No API key in the application Settings.");
}
return ErrorView.create(RezStrings.getNoApiKey() + ".");
// System.println("HomeAssistantApp getInitialView(): No API key in the application Settings.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoAPIKey) as Lang.String + ".");
} else if (Settings.getApiUrl().length() == 0) {
if (Globals.scDebug) {
System.println("HomeAssistantApp getInitialView(): No API URL in the application Settings.");
}
return ErrorView.create(RezStrings.getNoApiUrl() + ".");
// System.println("HomeAssistantApp getInitialView(): No API URL in the application Settings.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoApiUrl) as Lang.String + ".");
} else if (Settings.getApiUrl().substring(-1, Settings.getApiUrl().length()).equals("/")) {
if (Globals.scDebug) {
System.println("HomeAssistantApp getInitialView(): API URL must not have a trailing slash '/'.");
}
return ErrorView.create(RezStrings.getTrailingSlashErr() + ".");
// System.println("HomeAssistantApp getInitialView(): API URL must not have a trailing slash '/'.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.TrailingSlashErr) as Lang.String + ".");
} else if (Settings.getConfigUrl().length() == 0) {
if (Globals.scDebug) {
System.println("HomeAssistantApp getInitialView(): No configuration URL in the application settings.");
}
return ErrorView.create(RezStrings.getNoConfigUrl() + ".");
// System.println("HomeAssistantApp getInitialView(): No configuration URL in the application settings.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoConfigUrl) as Lang.String + ".");
} else if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantApp fetchMenuConfig(): No Phone connection, skipping API call.");
}
return ErrorView.create(RezStrings.getNoPhone() + ".");
// System.println("HomeAssistantApp fetchMenuConfig(): No Phone connection, skipping API call.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantApp fetchMenuConfig(): No Internet connection, skipping API call.");
}
return ErrorView.create(RezStrings.getNoInternet() + ".");
// System.println("HomeAssistantApp fetchMenuConfig(): No Internet connection, skipping API call.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + ".");
} else {
var isCached = fetchMenuConfig();
fetchApiStatus();
@@ -142,65 +131,53 @@ class HomeAssistantApp extends Application.AppBase {
//
(:glance)
function onReturnFetchMenuConfig(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: " + responseCode);
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Data: " + data);
}
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: " + responseCode);
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Data: " + data);
mMenuStatus = RezStrings.getUnavailable();
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getNoPhone() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
}
break;
case Communications.BLE_QUEUE_FULL:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getApiFlood());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
}
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getNoResponse());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
}
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
}
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getNoJson());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
}
break;
case 404:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: 404, page not found. Check Configuration URL setting.");
}
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: 404, page not found. Check Configuration URL setting.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getConfigUrlNotFound());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String);
}
break;
case 200:
if (Settings.getCacheConfig()) {
Storage.setValue("menu", data as Lang.Dictionary);
mMenuStatus = RezStrings.getCached();
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String;
} else {
mMenuStatus = RezStrings.getAvailable();
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
}
if (!mIsGlance) {
buildMenu(data);
@@ -211,11 +188,9 @@ class HomeAssistantApp extends Application.AppBase {
break;
default:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode);
}
// System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode);
if (!mIsGlance) {
ErrorView.show(RezStrings.getUnhandledHttpErr() + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
break;
}
@@ -227,7 +202,7 @@ class HomeAssistantApp extends Application.AppBase {
(:glance)
function fetchMenuConfig() as Lang.Boolean {
if (Settings.getConfigUrl().equals("")) {
mMenuStatus = RezStrings.getUnconfigured();
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
WatchUi.requestUpdate();
} else {
var menu = Storage.getValue("menu") as Lang.Dictionary;
@@ -238,25 +213,21 @@ class HomeAssistantApp extends Application.AppBase {
}
if (menu == null) {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
}
// System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
if (mIsGlance) {
WatchUi.requestUpdate();
} else {
ErrorView.show(RezStrings.getNoPhone() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
}
mMenuStatus = RezStrings.getUnavailable();
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
// System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
if (mIsGlance) {
WatchUi.requestUpdate();
} else {
ErrorView.show(RezStrings.getNoInternet() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + ".");
}
mMenuStatus = RezStrings.getUnavailable();
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
} else {
Communications.makeWebRequest(
Settings.getConfigUrl(),
@@ -269,7 +240,7 @@ class HomeAssistantApp extends Application.AppBase {
);
}
} else {
mMenuStatus = RezStrings.getCached();
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String;
WatchUi.requestUpdate();
if (!mIsGlance) {
buildMenu(menu);
@@ -301,56 +272,44 @@ class HomeAssistantApp extends Application.AppBase {
//
(:glance)
function onReturnFetchApiStatus(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: " + responseCode);
System.println("HomeAssistantApp onReturnFetchApiStatus() Response Data: " + data);
}
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: " + responseCode);
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Data: " + data);
mApiStatus = RezStrings.getUnavailable();
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getNoPhone() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
}
break;
case Communications.BLE_QUEUE_FULL:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getApiFlood());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
}
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getNoResponse());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
}
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
}
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getNoJson());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
}
break;
case 404:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: 404, page not found. Check Configuration URL setting.");
}
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: 404, page not found. Check Configuration URL setting.");
if (!mIsGlance) {
ErrorView.show(RezStrings.getConfigUrlNotFound());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String);
}
break;
@@ -360,7 +319,7 @@ class HomeAssistantApp extends Application.AppBase {
msg = data.get("message");
}
if (msg.equals("API running.")) {
mApiStatus = RezStrings.getAvailable();
mApiStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
} else {
if (!mIsGlance) {
ErrorView.show("API " + mApiStatus + ".");
@@ -369,11 +328,9 @@ class HomeAssistantApp extends Application.AppBase {
break;
default:
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchApiStatus(): Unhandled HTTP response code = " + responseCode);
}
// System.println("HomeAssistantApp onReturnFetchApiStatus(): Unhandled HTTP response code = " + responseCode);
if (!mIsGlance) {
ErrorView.show(RezStrings.getUnhandledHttpErr() + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
}
WatchUi.requestUpdate();
@@ -382,28 +339,24 @@ class HomeAssistantApp extends Application.AppBase {
(:glance)
function fetchApiStatus() as Void {
if (Settings.getApiUrl().equals("")) {
mApiStatus = RezStrings.getUnconfigured();
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
WatchUi.requestUpdate();
} else {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
}
mApiStatus = RezStrings.getUnavailable();
// System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
if (mIsGlance) {
WatchUi.requestUpdate();
} else {
ErrorView.show(RezStrings.getNoPhone() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
}
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
mApiStatus = RezStrings.getUnavailable();
// System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
if (mIsGlance) {
WatchUi.requestUpdate();
} else {
ErrorView.show(RezStrings.getNoInternet() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + ".");
}
} else {
Communications.makeWebRequest(
@@ -449,11 +402,9 @@ class HomeAssistantApp extends Application.AppBase {
function updateNextMenuItem() as Void {
var itu = mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem>;
if (itu == null) {
if (Globals.scDebug) {
System.println("HomeAssistantApp updateNextMenuItem(): No menu items to update");
}
// System.println("HomeAssistantApp updateNextMenuItem(): No menu items to update");
if (!mIsGlance) {
ErrorView.show(RezStrings.getConfigUrlNotFound());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String);
}
} else {
itu[mNextItemToUpdate].getState();
@@ -467,9 +418,9 @@ class HomeAssistantApp extends Application.AppBase {
function getGlanceView() as Lang.Array<WatchUi.GlanceView or WatchUi.GlanceViewDelegate> or Null {
mIsGlance = true;
RezStrings.update_glance();
mApiStatus = RezStrings.getChecking();
mMenuStatus = RezStrings.getChecking();
// RezStrings.update_glance();
mApiStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
updateStatus();
Settings.update();
mTimer = new Timer.Timer();
@@ -486,9 +437,7 @@ class HomeAssistantApp extends Application.AppBase {
// Replace this functionality with a more central settings class as proposed in
// https://github.com/house-of-abbey/GarminHomeAssistant/pull/17.
function onSettingsChanged() as Void {
if (Globals.scDebug) {
System.println("HomeAssistantApp onSettingsChanged()");
}
// System.println("HomeAssistantApp onSettingsChanged()");
Settings.update();
}

View File

@@ -28,7 +28,7 @@ using Toybox.Application.Properties;
class HomeAssistantConfirmation extends WatchUi.Confirmation {
function initialize() {
WatchUi.Confirmation.initialize(RezStrings.getConfirm());
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
}
}

View File

@@ -44,10 +44,10 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
function onLayout(dc as Graphics.Dc) as Void {
var h = dc.getHeight();
var tw = dc.getTextWidthInPixels(RezStrings.getGlanceMenu(), Graphics.FONT_XTINY);
var tw = dc.getTextWidthInPixels(WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String, Graphics.FONT_XTINY);
mTitle = new WatchUi.Text({
:text => RezStrings.getAppName(),
:text => WatchUi.loadResource($.Rez.Strings.AppName) as Lang.String,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_TINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -64,7 +64,7 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
:locY => 3 * h / 6
});
mApiStatus = new WatchUi.Text({
:text => RezStrings.getChecking(),
:text => WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -72,7 +72,7 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
:locY => 3 * h / 6
});
mMenuText = new WatchUi.Text({
:text => RezStrings.getGlanceMenu() + ":",
:text => WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String + ":",
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -80,7 +80,7 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
:locY => 5 * h / 6
});
mMenuStatus = new WatchUi.Text({
:text => RezStrings.getChecking(),
:text => WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,

View File

@@ -44,60 +44,44 @@ class HomeAssistantService {
context as Lang.Object
) as Void {
var entity_id = context as Lang.String or Null;
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: " + responseCode);
System.println("HomeAssistantService onReturnCall() Response Data: " + data);
}
// System.println("HomeAssistantService onReturnCall() Response Code: " + responseCode);
// System.println("HomeAssistantService onReturnCall() Response Data: " + data);
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
ErrorView.show(RezStrings.getNoPhone() + ".");
// System.println("HomeAssistantService onReturnCall() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
break;
case Communications.BLE_QUEUE_FULL:
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
ErrorView.show(RezStrings.getApiFlood());
// System.println("HomeAssistantService onReturnCall() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
ErrorView.show(RezStrings.getNoResponse());
// System.println("HomeAssistantService onReturnCall() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
break;
case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY:
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
}
// System.println("HomeAssistantService onReturnCall() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
// Ignore and see if we can carry on
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
}
ErrorView.show(RezStrings.getNoJson());
// System.println("HomeAssistantService onReturnCall() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
break;
case 404:
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: 404, page not found. Check API URL setting.");
}
ErrorView.show(RezStrings.getApiUrlNotFound());
// System.println("HomeAssistantService onReturnCall() Response Code: 404, page not found. Check API URL setting.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String);
break;
case 200:
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall(): Service executed.");
}
// System.println("HomeAssistantService onReturnCall(): Service executed.");
var d = data as Lang.Array;
var toast = RezStrings.getExecuted();
var toast = WatchUi.loadResource($.Rez.Strings.Executed) as Lang.String;
for(var i = 0; i < d.size(); i++) {
if ((d[i].get("entity_id") as Lang.String).equals(entity_id)) {
toast = (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String;
@@ -117,10 +101,8 @@ class HomeAssistantService {
break;
default:
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall(): Unhandled HTTP response code = " + responseCode);
}
ErrorView.show(RezStrings.getUnhandledHttpErr() + responseCode);
// System.println("HomeAssistantService onReturnCall(): Unhandled HTTP response code = " + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
}
@@ -129,22 +111,16 @@ class HomeAssistantService {
data as Lang.Dictionary or Null
) as Void {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantService call(): No Phone connection, skipping API call.");
}
ErrorView.show(RezStrings.getNoPhone() + ".");
// System.println("HomeAssistantService call(): No Phone connection, skipping API call.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantService call(): No Internet connection, skipping API call.");
}
ErrorView.show(RezStrings.getNoInternet() + ".");
// System.println("HomeAssistantService call(): No Internet connection, skipping API call.");
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());
if (Globals.scDebug) {
System.println("HomeAssistantService call() URL=" + url);
System.println("HomeAssistantService call() service=" + service);
}
// System.println("HomeAssistantService call() URL=" + url);
// System.println("HomeAssistantService call() service=" + service);
var entity_id = data.get("entity_id");
if (entity_id == null) {

View File

@@ -84,46 +84,34 @@ class HomeAssistantTemplateMenuItem extends WatchUi.IconMenuItem {
// error. The ErrorView cancellation will resume the call chain.
//
function onReturnGetState(responseCode as Lang.Number, data as Lang.String) as Void {
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: " + responseCode);
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Data: " + data);
}
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: " + responseCode);
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Data: " + data);
var status = RezStrings.getUnavailable();
var status = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
ErrorView.show(RezStrings.getNoPhone() + ".");
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
break;
case Communications.BLE_QUEUE_FULL:
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
ErrorView.show(RezStrings.getApiFlood());
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
ErrorView.show(RezStrings.getNoResponse());
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
}
ErrorView.show(RezStrings.getNoJson());
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
break;
case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY:
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
}
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
var myTimer = new Timer.Timer();
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false);
@@ -132,21 +120,17 @@ class HomeAssistantTemplateMenuItem extends WatchUi.IconMenuItem {
break;
case 404:
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting.");
}
ErrorView.show(RezStrings.getApiUrlNotFound());
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String);
break;
case 400:
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 400, bad request. Template error.");
}
ErrorView.show(RezStrings.getTemplateError());
// System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 400, bad request. Template error.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String);
break;
case 200:
status = RezStrings.getAvailable();
status = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
setSubLabel(data);
requestUpdate();
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
@@ -154,32 +138,24 @@ class HomeAssistantTemplateMenuItem extends WatchUi.IconMenuItem {
break;
default:
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode);
}
ErrorView.show(RezStrings.getUnhandledHttpErr() + responseCode);
// System.println("HomeAssistantTemplateMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
getApp().setApiStatus(status);
}
function getState() as Void {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem getState(): No Phone connection, skipping API call.");
}
ErrorView.show(RezStrings.getNoPhone() + ".");
getApp().setApiStatus(RezStrings.getUnavailable());
// System.println("HomeAssistantTemplateMenuItem getState(): No Phone connection, skipping API call.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem getState(): No Internet connection, skipping API call.");
}
ErrorView.show(RezStrings.getNoInternet() + ".");
getApp().setApiStatus(RezStrings.getUnavailable());
// System.println("HomeAssistantTemplateMenuItem getState(): No Internet connection, skipping API call.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + ".");
getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
} else {
var url = Settings.getApiUrl() + "/template";
if (Globals.scDebug) {
System.println("HomeAssistantTemplateMenuItem getState() URL=" + url + ", Template='" + mTemplate + "'");
}
// System.println("HomeAssistantTemplateMenuItem getState() URL=" + url + ", Template='" + mTemplate + "'");
Communications.makeWebRequest(
url,
{ "template" => mTemplate },

View File

@@ -59,46 +59,34 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
// error. The ErrorView cancellation will resume the call chain.
//
function onReturnGetState(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: " + responseCode);
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Data: " + data);
}
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: " + responseCode);
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Data: " + data);
var status = RezStrings.getUnavailable();
var status = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
ErrorView.show(RezStrings.getNoPhone() + ".");
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
break;
case Communications.BLE_QUEUE_FULL:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
ErrorView.show(RezStrings.getApiFlood());
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
ErrorView.show(RezStrings.getNoResponse());
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
}
ErrorView.show(RezStrings.getNoJson());
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
break;
case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
}
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
var myTimer = new Timer.Timer();
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false);
@@ -113,32 +101,24 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
}
if (msg != null) {
// Should be an HTTP 404 according to curl queries
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404. " + mData.get("entity_id") + " " + msg);
}
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404. " + mData.get("entity_id") + " " + msg);
ErrorView.show("HTTP 404, " + mData.get("entity_id") + ". " + data.get("message"));
} else {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting.");
}
ErrorView.show(RezStrings.getApiUrlNotFound());
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String);
}
break;
case 405:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 405. " + mData.get("entity_id") + " " + data.get("message"));
}
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 405. " + mData.get("entity_id") + " " + data.get("message"));
ErrorView.show("HTTP 405, " + mData.get("entity_id") + ". " + data.get("message"));
break;
case 200:
status = RezStrings.getAvailable();
status = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
var state = data.get("state") as Lang.String;
if (Globals.scDebug) {
System.println((data.get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state);
}
// System.println((data.get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state);
if (getLabel().equals("...")) {
setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String);
}
@@ -148,32 +128,24 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
break;
default:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode);
}
ErrorView.show(RezStrings.getUnhandledHttpErr() + responseCode);
// System.println("HomeAssistantToggleMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
getApp().setApiStatus(status);
}
function getState() as Void {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
}
ErrorView.show(RezStrings.getNoPhone() + ".");
getApp().setApiStatus(RezStrings.getUnavailable());
// System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
ErrorView.show(RezStrings.getNoInternet() + ".");
getApp().setApiStatus(RezStrings.getUnavailable());
// System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + ".");
getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
} else {
var url = Settings.getApiUrl() + "/states/" + mData.get("entity_id");
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState() URL=" + url);
}
// System.println("HomeAssistantToggleMenuItem getState() URL=" + url);
Communications.makeWebRequest(
url,
null,
@@ -192,47 +164,35 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
// Callback function after completing the POST request to set the status.
//
function onReturnSetState(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: " + responseCode);
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Data: " + data);
}
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: " + responseCode);
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Data: " + data);
var status = RezStrings.getUnavailable();
var status = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
ErrorView.show(RezStrings.getNoPhone() + ".");
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
break;
case Communications.BLE_QUEUE_FULL:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
ErrorView.show(RezStrings.getApiFlood());
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
ErrorView.show(RezStrings.getNoResponse());
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
}
ErrorView.show(RezStrings.getNoJson());
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
break;
case 404:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: 404, page not found. Check API URL setting.");
}
ErrorView.show(RezStrings.getApiUrlNotFound());
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: 404, page not found. Check API URL setting.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String);
break;
case 200:
@@ -241,39 +201,31 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
for(var i = 0; i < d.size(); i++) {
if ((d[i].get("entity_id") as Lang.String).equals(mData.get("entity_id"))) {
state = d[i].get("state") as Lang.String;
if (Globals.scDebug) {
System.println((d[i].get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state);
}
// System.println((d[i].get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state);
setUiToggle(state);
}
}
status = RezStrings.getAvailable();
status = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
break;
default:
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState(): Unhandled HTTP response code = " + responseCode);
}
ErrorView.show(RezStrings.getUnhandledHttpErr() + responseCode);
// System.println("HomeAssistantToggleMenuItem onReturnSetState(): Unhandled HTTP response code = " + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
getApp().setApiStatus(status);
}
function setState(s as Lang.Boolean) as Void {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
}
// System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
// Toggle the UI back
setEnabled(!isEnabled());
ErrorView.show(RezStrings.getNoPhone() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
// System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
// Toggle the UI back
setEnabled(!isEnabled());
ErrorView.show(RezStrings.getNoInternet() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + ".");
} else {
// Updated SDK and got a new error
// ERROR: venu: Cannot find symbol ':substring' on type 'PolyType<Null or $.Toybox.Lang.Object>'.
@@ -284,10 +236,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
} else {
url = url + id.substring(0, id.find(".")) + "/turn_off";
}
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem setState() URL = " + url);
System.println("HomeAssistantToggleMenuItem setState() entity_id = " + id);
}
// System.println("HomeAssistantToggleMenuItem setState() URL = " + url);
// System.println("HomeAssistantToggleMenuItem setState() entity_id = " + id);
Communications.makeWebRequest(
url,
mData,

View File

@@ -34,11 +34,8 @@ class HomeAssistantView extends WatchUi.Menu2 {
:theme as WatchUi.MenuTheme or Null
} or Null
) {
if (options == null) {
options = {
:title => definition.get("title") as Lang.String
};
options = { :title => definition.get("title") as Lang.String };
} else {
options.put(:title, definition.get("title") as Lang.String);
}
@@ -146,32 +143,22 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
mTimer.reset();
if (item instanceof HomeAssistantToggleMenuItem) {
var haToggleItem = item as HomeAssistantToggleMenuItem;
if (Globals.scDebug) {
System.println(haToggleItem.getLabel() + " " + haToggleItem.getId() + " " + haToggleItem.isEnabled());
}
// System.println(haToggleItem.getLabel() + " " + haToggleItem.getId() + " " + haToggleItem.isEnabled());
haToggleItem.callService(haToggleItem.isEnabled());
} else if (item instanceof HomeAssistantTapMenuItem) {
var haItem = item as HomeAssistantTapMenuItem;
if (Globals.scDebug) {
System.println(haItem.getLabel() + " " + haItem.getId());
}
// System.println(haItem.getLabel() + " " + haItem.getId());
haItem.callService();
} else if (item instanceof HomeAssistantTemplateMenuItem) {
var haItem = item as HomeAssistantTemplateMenuItem;
if (Globals.scDebug) {
System.println(haItem.getLabel() + " " + haItem.getId());
}
// System.println(haItem.getLabel() + " " + haItem.getId());
haItem.callService();
} else if (item instanceof HomeAssistantGroupMenuItem) {
var haMenuItem = item as HomeAssistantGroupMenuItem;
if (Globals.scDebug) {
System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());
}
// System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());
WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT);
} else {
if (Globals.scDebug) {
System.println(item.getLabel() + " " + item.getId());
}
// } else {
// System.println(item.getLabel() + " " + item.getId());
}
}

View File

@@ -30,9 +30,7 @@ class QuitTimer extends Timer.Timer {
}
function exitApp() as Void {
if (Globals.scDebug) {
System.println("QuitTimer exitApp(): Exiting");
}
// System.println("QuitTimer exitApp(): Exiting");
// This will exit the system cleanly from any point within an app.
System.exit();
}
@@ -45,9 +43,7 @@ class QuitTimer extends Timer.Timer {
}
function reset() {
if (Globals.scDebug) {
System.println("QuitTimer reset(): Restarted quit timer");
}
// System.println("QuitTimer reset(): Restarted quit timer");
stop();
begin();
}

View File

@@ -1,206 +0,0 @@
//-----------------------------------------------------------------------------------
//
// 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
//
//
// Description:
//
// Load the strings centrally once rather than initialising locally within separate
// classes. This is to solve a problem with out of memory errors in some devices,
// e.g. Vivoactive 3.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
class RezStrings {
(:glance)
private static var strAppName as Lang.String or Null;
private static var strConfirm as Lang.String or Null;
private static var strExecuted as Lang.String or Null;
(:glance)
private static var strNoPhone as Lang.String or Null;
private static var strNoInternet as Lang.String or Null;
private static var strNoResponse as Lang.String or Null;
(:glance)
private static var strNoApiKey as Lang.String or Null;
(:glance)
private static var strNoApiUrl as Lang.String or Null;
(:glance)
private static var strNoConfigUrl as Lang.String or Null;
private static var strApiFlood as Lang.String or Null;
private static var strApiUrlNotFound as Lang.String or Null;
private static var strConfigUrlNotFound as Lang.String or Null;
private static var strNoJson as Lang.String or Null;
private static var strUnhandledHttpErr as Lang.String or Null;
private static var strTrailingSlashErr as Lang.String or Null;
private static var strWebhookFailed as Lang.String or Null;
private static var strTemplateError as Lang.String or Null;
(:glance)
private static var strAvailable as Lang.String or Null;
(:glance)
private static var strChecking as Lang.String or Null;
(:glance)
private static var strUnavailable as Lang.String or Null;
(:glance)
private static var strUnconfigured as Lang.String or Null;
(:glance)
private static var strCached as Lang.String or Null;
(:glance)
private static var strGlanceMenu as Lang.String or Null;
private static var strMemory as Lang.String or Null;
// Can't initialise a constant directly, have to be initialised via a function
// for 'WatchUi.loadResource' to be available.
(:glance)
static function update_glance() {
strAppName = WatchUi.loadResource($.Rez.Strings.AppName);
strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone);
strNoApiKey = WatchUi.loadResource($.Rez.Strings.NoAPIKey);
strNoApiUrl = WatchUi.loadResource($.Rez.Strings.NoApiUrl);
strNoConfigUrl = WatchUi.loadResource($.Rez.Strings.NoConfigUrl);
strAvailable = WatchUi.loadResource($.Rez.Strings.Available);
strChecking = WatchUi.loadResource($.Rez.Strings.Checking);
strUnavailable = WatchUi.loadResource($.Rez.Strings.Unavailable);
strUnconfigured = WatchUi.loadResource($.Rez.Strings.Unconfigured);
strCached = WatchUi.loadResource($.Rez.Strings.Cached);
strGlanceMenu = WatchUi.loadResource($.Rez.Strings.GlanceMenu);
}
// Can't initialise a constant directly, have to be initialised via a function
// for 'WatchUi.loadResource' to be available.
static function update() {
strAppName = WatchUi.loadResource($.Rez.Strings.AppName);
strConfirm = WatchUi.loadResource($.Rez.Strings.Confirm);
strExecuted = WatchUi.loadResource($.Rez.Strings.Executed);
strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone);
strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet);
strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse);
strNoApiKey = WatchUi.loadResource($.Rez.Strings.NoAPIKey);
strNoApiUrl = WatchUi.loadResource($.Rez.Strings.NoApiUrl);
strNoConfigUrl = WatchUi.loadResource($.Rez.Strings.NoConfigUrl);
strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood);
strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound);
strConfigUrlNotFound = WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound);
strNoJson = WatchUi.loadResource($.Rez.Strings.NoJson);
strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr);
strTrailingSlashErr = WatchUi.loadResource($.Rez.Strings.TrailingSlashErr);
strWebhookFailed = WatchUi.loadResource($.Rez.Strings.WebhookFailed);
strTemplateError = WatchUi.loadResource($.Rez.Strings.TemplateError);
strAvailable = WatchUi.loadResource($.Rez.Strings.Available);
strChecking = WatchUi.loadResource($.Rez.Strings.Checking);
strUnavailable = WatchUi.loadResource($.Rez.Strings.Unavailable);
strUnconfigured = WatchUi.loadResource($.Rez.Strings.Unconfigured);
strCached = WatchUi.loadResource($.Rez.Strings.Cached);
strGlanceMenu = WatchUi.loadResource($.Rez.Strings.GlanceMenu);
strMemory = WatchUi.loadResource($.Rez.Strings.Memory);
}
static function getAppName() as Lang.String {
return strAppName;
}
static function getConfirm() as Lang.String {
return strConfirm;
}
static function getExecuted() as Lang.String {
return strExecuted;
}
static function getNoPhone() as Lang.String {
return strNoPhone;
}
static function getNoInternet() as Lang.String {
return strNoInternet;
}
static function getNoResponse() as Lang.String {
return strNoResponse;
}
static function getNoApiKey() as Lang.String {
return strNoApiKey;
}
static function getNoApiUrl() as Lang.String {
return strNoApiUrl;
}
static function getNoConfigUrl() as Lang.String {
return strNoConfigUrl;
}
static function getApiFlood() as Lang.String {
return strApiFlood;
}
static function getApiUrlNotFound() as Lang.String {
return strApiUrlNotFound;
}
static function getConfigUrlNotFound() as Lang.String {
return strConfigUrlNotFound;
}
static function getNoJson() as Lang.String {
return strNoJson;
}
static function getUnhandledHttpErr() as Lang.String {
return strUnhandledHttpErr;
}
static function getTrailingSlashErr() as Lang.String {
return strTrailingSlashErr;
}
static function getWebhookFailed() as Lang.String {
return strWebhookFailed;
}
static function getTemplateError() as Lang.String {
return strTemplateError;
}
static function getAvailable() as Lang.String {
return strAvailable;
}
static function getChecking() as Lang.String {
return strChecking;
}
static function getUnavailable() as Lang.String {
return strUnavailable;
}
static function getUnconfigured() as Lang.String {
return strUnconfigured;
}
static function getCached() as Lang.String {
return strCached;
}
static function getGlanceMenu() as Lang.String {
return strGlanceMenu;
}
static function getMemory() as Lang.String {
return strMemory;
}
}

View File

@@ -61,7 +61,7 @@ class RootView extends ScalableView {
var w = dc.getWidth();
mTitle = new WatchUi.Text({
:text => RezStrings.getAppName(),
:text => WatchUi.loadResource($.Rez.Strings.AppName) as Lang.String,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_TINY,
:justification => Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -78,7 +78,7 @@ class RootView extends ScalableView {
:locY => pixelsForScreen(50.0)
});
mApiStatus = new WatchUi.Text({
:text => RezStrings.getChecking(),
:text => WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -86,7 +86,7 @@ class RootView extends ScalableView {
:locY => pixelsForScreen(50.0)
});
mMenuText = new WatchUi.Text({
:text => RezStrings.getGlanceMenu() + ":",
:text => WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String + ":",
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_RIGHT | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -94,7 +94,7 @@ class RootView extends ScalableView {
:locY => pixelsForScreen(60.0)
});
mMenuStatus = new WatchUi.Text({
:text => RezStrings.getChecking(),
:text => WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -102,7 +102,7 @@ class RootView extends ScalableView {
:locY => pixelsForScreen(60.0)
});
mMemText = new WatchUi.Text({
:text => RezStrings.getMemory() + ":",
:text => WatchUi.loadResource($.Rez.Strings.Memory) as Lang.String + ":",
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_RIGHT | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -110,7 +110,7 @@ class RootView extends ScalableView {
:locY => pixelsForScreen(70.0)
});
mMemStatus = new WatchUi.Text({
:text => RezStrings.getChecking(),
:text => WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String,
:color => Graphics.COLOR_WHITE,
:font => Graphics.FONT_XTINY,
:justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER,
@@ -139,7 +139,7 @@ class RootView extends ScalableView {
mMenuStatus.draw(dc);
mMemText.draw(dc);
var stats = System.getSystemStats();
var memUsed = (100 * stats.usedMemory) / stats.totalMemory;
var memUsed = (100f * stats.usedMemory) / stats.totalMemory;
mMemStatus.setText(memUsed.format("%.1f") + "%");
if (stats.usedMemory > Globals.scLowMem * stats.totalMemory) {
mMemStatus.setColor(Graphics.COLOR_RED);

View File

@@ -42,7 +42,6 @@ class Settings {
private static var mBatteryRefreshRate as Lang.Number = 15; // minutes
private static var mIsApp as Lang.Boolean = false;
private static var mHasService as Lang.Boolean = false;
// Must keep the object so it doesn't get garbage collected.
private static var mWebhookManager as WebhookManager or Null;
@@ -75,7 +74,8 @@ class Settings {
} else if (
mHasService and
((Background.getTemporalEventRegisteredTime() == null) or
(Background.getTemporalEventRegisteredTime() != (mBatteryRefreshRate * 60)))) {
(Background.getTemporalEventRegisteredTime() != (mBatteryRefreshRate * 60)))
) {
Background.registerForTemporalEvent(new Time.Duration(mBatteryRefreshRate * 60)); // Convert to seconds
}
} else {
@@ -86,14 +86,12 @@ class Settings {
unsetWebhookId();
}
}
if (Globals.scDebug) {
System.println("Settings update(): getTemporalEventRegisteredTime() = " + Background.getTemporalEventRegisteredTime());
if (Background.getTemporalEventRegisteredTime() != null) {
System.println("Settings update(): getTemporalEventRegisteredTime().value() = " + Background.getTemporalEventRegisteredTime().value().format("%d") + " seconds");
} else {
System.println("Settings update(): getTemporalEventRegisteredTime() = null");
}
}
// System.println("Settings update(): getTemporalEventRegisteredTime() = " + Background.getTemporalEventRegisteredTime());
// if (Background.getTemporalEventRegisteredTime() != null) {
// System.println("Settings update(): getTemporalEventRegisteredTime().value() = " + Background.getTemporalEventRegisteredTime().value().format("%d") + " seconds");
// } else {
// System.println("Settings update(): getTemporalEventRegisteredTime() = null");
// }
}
static function getApiKey() as Lang.String {

View File

@@ -78,9 +78,7 @@ class WebLog {
var myTime = System.getClockTime();
buffer += myTime.hour.format("%02d") + ":" + myTime.min.format("%02d") + ":" + myTime.sec.format("%02d") + " " + str;
numCalls++;
if (Globals.scDebug) {
System.println("WebLog print() str = " + str);
}
// System.println("WebLog print() str = " + str);
if (numCalls >= callsbuffer) {
doPrint();
}
@@ -97,9 +95,7 @@ class WebLog {
// submission level set by 'callsbuffer'.
//
function flush() {
if (Globals.scDebug) {
System.println("WebLog flush()");
}
// System.println("WebLog flush()");
if (numCalls > 0) {
doPrint();
}
@@ -108,15 +104,11 @@ class WebLog {
// Perform the submission to the online logger.
//
function doPrint() {
if (Globals.scDebug) {
System.println("WebLog doPrint()");
System.println(buffer);
}
// System.println("WebLog doPrint()");
// System.println(buffer);
Communications.makeWebRequest(
ClientId.webLogUrl,
{
"log" => buffer
},
{ "log" => buffer },
{
:method => Communications.HTTP_REQUEST_METHOD_GET,
:headers => {
@@ -134,9 +126,7 @@ class WebLog {
// execution.
//
function clear() {
if (Globals.scDebug) {
System.println("WebLog clear()");
}
// System.println("WebLog clear()");
Communications.makeWebRequest(
ClientId.webLogClearUrl,
{},
@@ -156,24 +146,20 @@ class WebLog {
// Callback function to print the outcome of a doPrint() method.
//
function onLog(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
if (Globals.scDebug) {
if (responseCode != 200) {
System.println("WebLog onLog() Failed");
System.println("WebLog onLog() Response Code: " + responseCode);
System.println("WebLog onLog() Response Data: " + data);
}
}
// if (responseCode != 200) {
// System.println("WebLog onLog() Failed");
// System.println("WebLog onLog() Response Code: " + responseCode);
// System.println("WebLog onLog() Response Data: " + data);
// }
}
// Callback function to print the outcome of a clear() method.
//
function onClear(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
if (Globals.scDebug) {
if (responseCode != 200) {
System.println("WebLog onClear() Failed");
System.println("WebLog onClear() Response Code: " + responseCode);
System.println("WebLog onClear() Response Data: " + data);
}
}
// if (responseCode != 200) {
// System.println("WebLog onClear() Failed");
// System.println("WebLog onClear() Response Code: " + responseCode);
// System.println("WebLog onClear() Response Data: " + data);
// }
}
}

View File

@@ -32,52 +32,42 @@ class WebhookManager {
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getNoPhone() + ".");
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
break;
case Communications.BLE_QUEUE_FULL:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getApiFlood());
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getNoResponse());
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
break;
case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
}
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
// Ignore and see if we can carry on
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
}
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getNoJson());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
break;
case 404:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId() Response Code: 404, page not found. Check API URL setting.");
}
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: 404, page not found. Check API URL setting.");
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getApiUrlNotFound());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String);
break;
case 200:
case 201:
var id = data.get("webhook_id") as Lang.String or Null;
if (id != null) {
Settings.setWebhookId(id);
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering first sensor: Battery Level");
registerWebhookSensor({
"device_class" => "battery",
"name" => "Battery Level",
@@ -88,44 +78,29 @@ class WebhookManager {
"state_class" => "measurement",
"entity_category" => "diagnostic",
"disabled" => false
});
registerWebhookSensor({
"device_class" => "battery_charging",
"name" => "Battery is Charging",
"state" => System.getSystemStats().charging,
"type" => "binary_sensor",
"unique_id" => "battery_is_charging",
"entity_category" => "diagnostic",
"disabled" => false
});
}, 0);
} else {
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId(): No webhook id in response data.");
}
// System.println("WebhookManager onReturnRequestWebhookId(): No webhook id in response data.");
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(RezStrings.getWebhookFailed());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String);
}
break;
default:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId(): Unhandled HTTP response code = " + responseCode);
}
// System.println("WebhookManager onReturnRequestWebhookId(): Unhandled HTTP response code = " + responseCode);
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getUnhandledHttpErr() + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
}
function requestWebhookId() {
if (Globals.scDebug) {
System.println("WebhookManager requestWebhookId(): Requesting webhook id");
}
// System.println("WebhookManager requestWebhookId(): Requesting webhook id");
Communications.makeWebRequest(
Settings.getApiUrl() + "/mobile_app/registrations",
{
"device_id" => System.getDeviceSettings().uniqueIdentifier,
"app_id" => "garmin_home_assistant",
"app_name" => RezStrings.getAppName(),
"app_name" => WatchUi.loadResource($.Rez.Strings.AppName) as Lang.String,
"app_version" => "",
"device_name" => "Garmin Watch",
"manufacturer" => "Garmin",
@@ -147,87 +122,108 @@ class WebhookManager {
);
}
function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String, step as Lang.Number) as Void {
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
Settings.unsetWebhookId();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getNoPhone() + ".");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + ".");
break;
case Communications.BLE_QUEUE_FULL:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
Settings.unsetWebhookId();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getApiFlood());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
Settings.unsetWebhookId();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getNoResponse());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
break;
case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
}
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
Settings.unsetWebhookId();
// Ignore and see if we can carry on
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
}
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
Settings.unsetWebhookId();
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getNoJson());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
break;
case 404:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId() Response Code: 404, page not found. Check API URL setting.");
}
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: 404, page not found. Check API URL setting.");
Settings.unsetWebhookId();
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getApiUrlNotFound());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String);
break;
case 200:
case 201:
if ((data.get("success") as Lang.Boolean or Null) == true) {
if (Globals.scDebug) {
System.println("WebhookManager onReturnRegisterWebhookSensor(): Success");
if ((data.get("success") as Lang.Boolean or Null) != false) {
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Success");
switch (step) {
case 0:
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering next sensor: Battery is Charging");
registerWebhookSensor({
"device_class" => "battery_charging",
"name" => "Battery is Charging",
"state" => System.getSystemStats().charging,
"type" => "binary_sensor",
"unique_id" => "battery_is_charging",
"entity_category" => "diagnostic",
"disabled" => false
}, 1);
break;
case 1:
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering next sensor: Activity");
if (Activity has :getProfileInfo) {
registerWebhookSensor({
"name" => "Activity",
"state" => Activity.getProfileInfo().sport,
"type" => "sensor",
"unique_id" => "activity",
"disabled" => false
}, 2);
break;
}
case 2:
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering next sensor: Activity");
if (Activity has :getProfileInfo) {
registerWebhookSensor({
"name" => "Sub-activity",
"state" => Activity.getProfileInfo().subSport,
"type" => "sensor",
"unique_id" => "sub_activity",
"disabled" => false
}, 3);
break;
}
default:
}
} else {
if (Globals.scDebug) {
System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure");
}
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure");
Settings.unsetWebhookId();
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(RezStrings.getWebhookFailed());
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String);
}
break;
default:
if (Globals.scDebug) {
System.println("WebhookManager onReturnRequestWebhookId(): Unhandled HTTP response code = " + responseCode);
}
// System.println("WebhookManager onReturnRequestWebhookId(): Unhandled HTTP response code = " + responseCode);
Settings.unsetWebhookId();
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(RezStrings.getWebhookFailed() + "\n" + RezStrings.getUnhandledHttpErr() + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
}
function registerWebhookSensor(sensor as Lang.Object) {
if (Globals.scDebug) {
System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
}
function registerWebhookSensor(sensor as Lang.Object, step as Lang.Number) {
// System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
Communications.makeWebRequest(
Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(),
{
@@ -239,7 +235,8 @@ class WebhookManager {
:headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
:context => step
},
method(:onReturnRegisterWebhookSensor)
);

1
web/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

298
web/index.html Normal file
View File

@@ -0,0 +1,298 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GarminHomeAssistant</title>
<link
rel="stylesheet"
data-name="vs/editor/editor.main"
href="https://unpkg.com/monaco-editor@0.45.0/min/vs/editor/editor.main.css" />
<link
rel="stylesheet"
type="text/css"
href="https://unpkg.com/toastify-js@1.12.0/src/toastify.css" />
<style>
@import url('https://unpkg.com/@catppuccin/palette/css/catppuccin.css');
html,
body,
#container {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
background-color: var(--ctp-mocha-base);
}
.tap {
background-image: url(../resources-icons-48/tap_type.svg);
background-size: contain;
margin-left: 0.5em;
filter: grayscale() invert();
}
.template {
background-image: url(../resources-icons-48/info_type.svg);
background-size: contain;
margin-left: 0.5em;
filter: grayscale() invert();
}
.toggle_on {
background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/toggle_on/default/48px.svg);
background-size: contain;
margin-left: 0.5em;
filter: grayscale() invert();
rotate: -90deg;
}
.toggle_off {
background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/toggle_off/default/48px.svg);
background-size: contain;
margin-left: 0.5em;
filter: grayscale() invert();
rotate: -90deg;
}
.group {
background-image: url(../resources-icons-48/group_type.svg);
background-size: contain;
margin-left: 0.5em;
filter: grayscale() invert();
}
:root {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#settings {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
padding: 0.4em;
gap: 0.25em;
background-color: var(--ctp-mocha-mantle);
color: var(--ctp-mocha-text);
}
dialog {
background-color: var(--ctp-mocha-base);
color: var(--ctp-mocha-text);
border: 1px solid var(--ctp-mocha-surface1);
border-radius: 0.25em;
padding: 0.5em;
user-select: none;
&:focus-within,
&:focus-visible {
outline: none;
border: 1px solid var(--ctp-mocha-teal);
}
&::backdrop {
background-color: black;
opacity: 0.5;
}
& h2 {
margin: 0;
padding: 0;
}
}
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0.4em;
gap: 0.25em;
}
#settings:has(.invalid, :invalid) + #container {
opacity: 0.5;
pointer-events: none;
}
#settings,
dialog {
& input {
background-color: var(--ctp-mocha-surface1);
color: var(--ctp-mocha-text);
border: 1px solid var(--ctp-mocha-surface1);
border-radius: 0.25em;
padding: 0.25em;
&::placeholder {
color: var(--ctp-mocha-text);
opacity: 0.4;
}
&:focus-visible {
outline: none;
border: 1px solid var(--ctp-mocha-teal);
}
&.outofsync {
border: 1px solid var(--ctp-mocha-yellow);
}
&.invalid,
&:invalid {
border: 1px solid var(--ctp-mocha-red);
}
flex-grow: 1;
&#api_token {
flex-grow: 0;
}
}
button {
background-color: var(--ctp-mocha-surface1);
color: var(--ctp-mocha-text);
border: 1px solid var(--ctp-mocha-surface1);
border-radius: 0.25em;
padding-inline: 0.5em;
padding-block: 0.25em;
user-select: none;
&:focus-visible {
outline: none;
border: 1px solid var(--ctp-mocha-teal);
}
&:hover {
cursor: pointer;
background-color: var(--ctp-mocha-surface0);
}
}
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
}
button.icon {
border: none;
padding: 0.1em;
margin: 0;
width: 1.8em;
height: 1.8em;
background-color: var(--ctp-mocha-surface1);
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&:hover {
background-color: var(--ctp-mocha-overlay1);
}
&::before {
display: block;
content: '';
background-size: contain;
background-repeat: no-repeat;
background-position: center;
filter: grayscale() invert();
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
&[icon='close'] {
&:hover {
background-color: var(--ctp-mocha-red);
}
&::before {
background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/close/default/48px.svg);
}
}
&[icon='download']::before {
background-image: url(https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/download/default/48px.svg);
}
}
}
</style>
</head>
<body>
<div id="settings">
<input
required
placeholder="https://<home-assistant>/api"
title="Home Assistant API URL `https://<home-assistant>/api`"
type="url"
name="api_url"
id="api_url"
pattern="https://.*/api" />
<input
placeholder="https://<home-assistant>/local/garmin/menu.json"
title="Menu JSON URL `https://<home-assistant>/local/garmin/menu.json`"
type="url"
name="menu_url"
id="menu_url"
pattern="https://.*\.json" />
<button
title="Download content of menu url and put it in the editor"
class="icon"
icon="download"
id="download"
type="button"></button>
<input
required
autocomplete="new-password"
placeholder="API Token"
title="Home Assistant Long-lived Access Token"
type="password"
name="api_token"
id="api_token" />
<button id="troubleshooting" type="button">Troubleshooting</button>
</div>
<div id="container"></div>
<dialog id="troubleshooting-dialog">
<div>
<div class="row">
<h2>GarminHomeAssistant Troubleshooting</h2>
<button
title="Close"
class="icon"
icon="close"
onclick="this.parentElement.parentElement.parentElement.close()"
type="button"></button>
</div>
<p>
This is a troubleshooting tool for the GarminHomeAssistant watch app.
It allows you to test your Home Assistant API connection.
</p>
<div class="row">
<div id="test-api-response">Check now!</div>
<button
title="Check the status of the API"
id="test-api"
type="button">
Test API
</button>
</div>
<div class="row">
<div id="test-menu-response">Check now!</div>
<button
title="Check the availability of the menu configuration"
id="test-menu"
type="button">
Test menu
</button>
</div>
</div>
</dialog>
<script src="https://unpkg.com/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script src="https://unpkg.com/json-ast-comments@1.1.1/lib/json.js"></script>
<script src="https://unpkg.com/toastify-js@1.12.0/src/toastify.js"></script>
<script type="module" src="./main.js"></script>
</body>
</html>

12
web/jsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"lib": ["esnext", "dom"],
"types": [
"./node_modules/monaco-editor/monaco.d.ts",
"./node_modules/json-ast-comments/lib/index.d.ts",
"./node_modules/@types/toastify-js/index.d.ts",
"./types.d.ts"
]
},
"include": ["."]
}

1138
web/main.js Normal file

File diff suppressed because one or more lines are too long

19
web/package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "web",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "serve .."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/toastify-js": "^1.12.3",
"@vscode/webview-ui-toolkit": "1.4.0",
"json-ast-comments": "1.1.1",
"monaco-editor": "0.45.0",
"serve": "^14.2.1"
}
}

676
web/pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,676 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
devDependencies:
'@types/toastify-js':
specifier: ^1.12.3
version: 1.12.3
'@vscode/webview-ui-toolkit':
specifier: 1.4.0
version: 1.4.0(react@18.2.0)
json-ast-comments:
specifier: 1.1.1
version: 1.1.1
monaco-editor:
specifier: 0.45.0
version: 0.45.0
serve:
specifier: ^14.2.1
version: 14.2.1
packages:
/@microsoft/fast-element@1.12.0:
resolution: {integrity: sha512-gQutuDHPKNxUEcQ4pypZT4Wmrbapus+P9s3bR/SEOLsMbNqNoXigGImITygI5zhb+aA5rzflM6O8YWkmRbGkPA==}
dev: true
/@microsoft/fast-foundation@2.49.4:
resolution: {integrity: sha512-5I2tSPo6bnOfVAIX7XzX+LhilahwvD7h+yzl3jW0t5IYmMX9Lci9VUVyx5f8hHdb1O9a8Y9Atb7Asw7yFO/u+w==}
dependencies:
'@microsoft/fast-element': 1.12.0
'@microsoft/fast-web-utilities': 5.4.1
tabbable: 5.3.3
tslib: 1.14.1
dev: true
/@microsoft/fast-react-wrapper@0.3.22(react@18.2.0):
resolution: {integrity: sha512-XhlX4m6znh7XW92oPvlKoG9USUn9JtF9rP1qtUoIbkaDaFtUS+H8o1Jn6/oK/rS44LbBLJXrvRkInmSWlDiGFw==}
peerDependencies:
react: '>=16.9.0'
dependencies:
'@microsoft/fast-element': 1.12.0
'@microsoft/fast-foundation': 2.49.4
react: 18.2.0
dev: true
/@microsoft/fast-web-utilities@5.4.1:
resolution: {integrity: sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==}
dependencies:
exenv-es6: 1.1.1
dev: true
/@types/toastify-js@1.12.3:
resolution: {integrity: sha512-9RjLlbAHMSaae/KZNHGv19VG4gcLIm3YjvacCXBtfMfYn26h76YP5oxXI8k26q4iKXCB9LNfv18lsoS0JnFPTg==}
dev: true
/@vscode/webview-ui-toolkit@1.4.0(react@18.2.0):
resolution: {integrity: sha512-modXVHQkZLsxgmd5yoP3ptRC/G8NBDD+ob+ngPiWNQdlrH6H1xR/qgOBD85bfU3BhOB5sZzFWBwwhp9/SfoHww==}
peerDependencies:
react: '>=16.9.0'
dependencies:
'@microsoft/fast-element': 1.12.0
'@microsoft/fast-foundation': 2.49.4
'@microsoft/fast-react-wrapper': 0.3.22(react@18.2.0)
react: 18.2.0
tslib: 2.6.2
dev: true
/@zeit/schemas@2.29.0:
resolution: {integrity: sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==}
dev: true
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
dev: true
/ajv@8.11.0:
resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==}
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
uri-js: 4.4.1
dev: true
/ansi-align@3.0.1:
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
dependencies:
string-width: 4.2.3
dev: true
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
dev: true
/ansi-regex@6.0.1:
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
engines: {node: '>=12'}
dev: true
/ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: true
/ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
dev: true
/arch@2.2.0:
resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
dev: true
/arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
dev: true
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/boxen@7.0.0:
resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==}
engines: {node: '>=14.16'}
dependencies:
ansi-align: 3.0.1
camelcase: 7.0.1
chalk: 5.0.1
cli-boxes: 3.0.0
string-width: 5.1.2
type-fest: 2.19.0
widest-line: 4.0.1
wrap-ansi: 8.1.0
dev: true
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/bytes@3.0.0:
resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
engines: {node: '>= 0.8'}
dev: true
/camelcase@7.0.1:
resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
engines: {node: '>=14.16'}
dev: true
/chalk-template@0.4.0:
resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==}
engines: {node: '>=12'}
dependencies:
chalk: 4.1.2
dev: true
/chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: true
/chalk@5.0.1:
resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
dev: true
/cli-boxes@3.0.0:
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
engines: {node: '>=10'}
dev: true
/clipboardy@3.0.0:
resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
arch: 2.2.0
execa: 5.1.1
is-wsl: 2.2.0
dev: true
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: true
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/compressible@2.0.18:
resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: true
/compression@1.7.4:
resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==}
engines: {node: '>= 0.8.0'}
dependencies:
accepts: 1.3.8
bytes: 3.0.0
compressible: 2.0.18
debug: 2.6.9
on-headers: 1.0.2
safe-buffer: 5.1.2
vary: 1.1.2
transitivePeerDependencies:
- supports-color
dev: true
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/content-disposition@0.5.2:
resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
engines: {node: '>= 0.6'}
dev: true
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
dev: true
/debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.0.0
dev: true
/deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
dev: true
/eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
/emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
dev: true
/execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
dependencies:
cross-spawn: 7.0.3
get-stream: 6.0.1
human-signals: 2.1.0
is-stream: 2.0.1
merge-stream: 2.0.0
npm-run-path: 4.0.1
onetime: 5.1.2
signal-exit: 3.0.7
strip-final-newline: 2.0.0
dev: true
/exenv-es6@1.1.1:
resolution: {integrity: sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==}
dev: true
/fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true
/fast-url-parser@1.1.3:
resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==}
dependencies:
punycode: 1.4.1
dev: true
/get-stream@6.0.1:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
dev: true
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
dev: true
/human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
dev: true
/ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
dev: true
/is-docker@2.2.1:
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
engines: {node: '>=8'}
hasBin: true
dev: true
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: true
/is-port-reachable@4.0.0:
resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: true
/is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
dev: true
/is-wsl@2.2.0:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
dependencies:
is-docker: 2.2.1
dev: true
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true
/json-ast-comments@1.1.1:
resolution: {integrity: sha512-UOHlf7ns5t1GiI3+T5tf9SN2OepXTo/sqhd+cQj++DaUMKQOOCbX+eRlIoHcEv9m902reDTc1mCJD4J69xFJSg==}
dev: true
/json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
dev: true
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
dependencies:
js-tokens: 4.0.0
dev: true
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true
/mime-db@1.33.0:
resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}
engines: {node: '>= 0.6'}
dev: true
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: true
/mime-types@2.1.18:
resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.33.0
dev: true
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: true
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
dev: true
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
/monaco-editor@0.45.0:
resolution: {integrity: sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==}
dev: true
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: true
/negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
dev: true
/npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
dependencies:
path-key: 3.1.1
dev: true
/on-headers@1.0.2:
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
engines: {node: '>= 0.8'}
dev: true
/onetime@5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
dependencies:
mimic-fn: 2.1.0
dev: true
/path-is-inside@1.0.2:
resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
dev: true
/path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
dev: true
/path-to-regexp@2.2.1:
resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==}
dev: true
/punycode@1.4.1:
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
dev: true
/punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
dev: true
/range-parser@1.2.0:
resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==}
engines: {node: '>= 0.6'}
dev: true
/rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
dependencies:
deep-extend: 0.6.0
ini: 1.3.8
minimist: 1.2.8
strip-json-comments: 2.0.1
dev: true
/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
dev: true
/registry-auth-token@3.3.2:
resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==}
dependencies:
rc: 1.2.8
safe-buffer: 5.2.1
dev: true
/registry-url@3.1.0:
resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
engines: {node: '>=0.10.0'}
dependencies:
rc: 1.2.8
dev: true
/require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
dev: true
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: true
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: true
/serve-handler@6.1.5:
resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==}
dependencies:
bytes: 3.0.0
content-disposition: 0.5.2
fast-url-parser: 1.1.3
mime-types: 2.1.18
minimatch: 3.1.2
path-is-inside: 1.0.2
path-to-regexp: 2.2.1
range-parser: 1.2.0
dev: true
/serve@14.2.1:
resolution: {integrity: sha512-48er5fzHh7GCShLnNyPBRPEjs2I6QBozeGr02gaacROiyS/8ARADlj595j39iZXAqBbJHH/ivJJyPRWY9sQWZA==}
engines: {node: '>= 14'}
hasBin: true
dependencies:
'@zeit/schemas': 2.29.0
ajv: 8.11.0
arg: 5.0.2
boxen: 7.0.0
chalk: 5.0.1
chalk-template: 0.4.0
clipboardy: 3.0.0
compression: 1.7.4
is-port-reachable: 4.0.0
serve-handler: 6.1.5
update-check: 1.5.4
transitivePeerDependencies:
- supports-color
dev: true
/shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
dependencies:
shebang-regex: 3.0.0
dev: true
/shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
dev: true
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: true
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: true
/string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.1.0
dev: true
/strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
dev: true
/strip-ansi@7.1.0:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
dependencies:
ansi-regex: 6.0.1
dev: true
/strip-final-newline@2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'}
dev: true
/strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
dev: true
/supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
dev: true
/tabbable@5.3.3:
resolution: {integrity: sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==}
dev: true
/tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: true
/type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
dev: true
/update-check@1.5.4:
resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==}
dependencies:
registry-auth-token: 3.3.2
registry-url: 3.1.0
dev: true
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
punycode: 2.3.1
dev: true
/vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
dev: true
/which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
dependencies:
isexe: 2.0.0
dev: true
/widest-line@4.0.1:
resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
engines: {node: '>=12'}
dependencies:
string-width: 5.1.2
dev: true
/wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
dependencies:
ansi-styles: 6.2.1
string-width: 5.1.2
strip-ansi: 7.1.0
dev: true

3
web/types.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
declare namespace json {
export function parse(text: string): import('json-ast-comments').JsonDocument;
}