diff --git a/.gitignore b/.gitignore index 3605b50..dbd8c63 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ bin/ export/ **/Thumbs.db settings.txt -export.cmd diff --git a/README.md b/README.md index f17aaab..b65b30d 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,20 @@ A Garmin application to provide a "dashboard" to control your devices via [Home The application is designed around a simple scrollable menu where menu items have been extended to interface with the [Home Assistant API](https://developers.home-assistant.io/docs/api/rest/), e.g. to get the status of switches or lights for display on the toggle menu item. It is possible to nest menus, so there is a menu item to open a sub-menu. This can be arbitrarily deep and nested in the format of a tree of items, although you need to consider if reaching for your phone becomes quicker to select the device what you want to control. -It is important to note that your Home Assistant instance will need to be accessible via HTTPS with public SSL or all requests from the Garmin will not work. This cannot be a self-signed certificate, it must be a public certificate (You can get one for free from [Let's Encrypt](https://letsencrypt.org/) or you can pay for [Home Assistant cloud](https://www.nabucasa.com/)). +It is important to note that your Home Assistant instance will need to be accessible via HTTPS with public SSL or all requests from the Garmin will not work. This cannot be a self-signed certificate, it must be a public certificate. You can get one for free from [Let's Encrypt](https://letsencrypt.org/) or you can pay for [Home Assistant cloud](https://www.nabucasa.com/). -## Application Installation +**If you are struggling with getting the application to work, please consult the [trouble shooting](Troubleshooting.md) guide first.** -Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/61c91d28-ec5e-438d-9f83-39e9f45b199d) application page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the application. +## Widget or Application? + +As of version 2.0, there are now two installable versions. For older devices before applications supported 'glances', there is a now widget version. These two version must be downloaded separately due to the way the Connect IQ App Store requires them to have separate application IDs. Therefore you need to choose which you want up front. Here how they compare. + +| Version | Explanation | +|------------------------|-------------| +| Application (original) | For newer devices that allow glance views in their applications (e.g. Venu2), the GarminHomeAssistant application can be started either from a glance (with only the application name presently, no status) or from the list of applications and activities. Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/61c91d28-ec5e-438d-9f83-39e9f45b199d) application page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the application. The application can be started two different ways, either from the glance in the carousel, or as an application from the list of applications & activities. With the latter, it is worth marking the application as a favourite.


If you place the application on your list of favourites, and rearrange it to appear near the top, then the item is just one button press away from the watch face. This second picture here shows the application menu on a Vivoactive 3 watch.

On newer watches, you can also start the application from the glance carousel. The glance view here typically displays some trackable status, so ours provides some early indication of availability. Older watches will still allow you to start this application from the list of applications and activities. | +| Widget | For older devices that use widgets (e.g. Venu1) as opposed to applications with "glances", the GarminHomeAssistant application can instead be started from the widget carousel. This is a separate item in the Connect IQ AppStore and with this installation, the application will no longer appear in the list of applications and activities. Head over to the [GarminHomeAssistant](https://apps.garmin.com/en-US/apps/) widget page on the [Connect IQ application store](https://apps.garmin.com/en-US/) to download the widget.


Typically the widget view implements something similar to the glance view, e.g. status, and exists in a widget carousel to allow you to select an application to launch. | + +As the source code base for both is the same, the version numbers of each will be kept in step. that means that the widget version starts version numbering at 2.0 too. ## Dashboard Definition @@ -161,8 +170,8 @@ This allows the `confirm` field to be accommodated in the `tap_action` along sid 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/ +2. Locally installed VSCode, or if not installed, +3. try the on-line version at https://vscode.dev/. 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. @@ -184,12 +193,23 @@ Having created that token, before you dismiss the dialogue box with the value yo -1. Paste your API key you've just created into the top field. +1. Copy and paste your API key you've just created into the top field. 2. Add the URL for your Home Assistant API, e.g. `https:///api`. (No trailing slash `/`` character as one gets appended when creating the URL and you do not want two.) 3. Add the URL of your JSON file, e.g. `https:///local/garmin/.json`. You should now have a working application on your watch and be able to operate your Home Assistant devices for as long as your watch is within Bluetooth range of your phone. +The first toggle option selects between two menu presentations as follows: + +| Menu Type | Image | Description | +|-----------------|------------------------------------------------------------------------|-------------| +| Icons (default) | | "Lean User Interface" version removing the second row of text in favour of icons. Tap icons are blue with a pointing finger, menu items has three dots as favours by many graphical user interfaces. | +| Labels | | Initial version that had a second row of text. This extra text has yet to add much value. Menu and Tap actions are distinguished by text, and the toggle status is duplicated by text. A future version could possibly offer the means to customise the toggle menu item text, hence this option has not been deprecated yet. | + +The second toggle setting is for "text alignment" and provides finer adjustment for right-to-left languages. Perhaps this could be made automatic based on device language. + +The third toggle setting is for the Widget version of the application only. It allows the user to select a non-standard user interface behaviour. As soon as the menu is retrieved the widget view is replaced by the menu without waiting for a user selection. This has been included as requested by a user, but defaults to off which retains the expected user interactions. + ## Tap Item Response Its obvious that a toggle menu item has been triggered as the visible switch changes position and colour. Less obvious is that you have successfully triggered a tap operation. @@ -202,7 +222,7 @@ The application will display a 'toast' showing Home Assistant's friendly name of Home Assistant will inevitably change the state of devices you are also controlling via your Garmin. The Garmin application does not maintain a web socket to listen for changes. Instead it must poll the Home Assistant API with your key. Therefore the application is not that responsive to changes. Instead there will be a delay of multiples of 100 ms per item whose status needs to be checked and amended. -The per toggle item delay is caused by a queue of responses to web requests filling up a queue and giving a [Communications](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html).`BLE_QUEUE_FULL` response code. For a Venu 2 Garmin watch an API call delay of 600 ms was found to be sustainable (500 ms was still too fast). The code now chains a sequence of updates, so as one finishes it invokes the next item's update. The more items requiring a status update that you pack into your dashboard, the slower each individual item will be updated! +The per toggle item delay is caused by a queue of responses to web requests filling up a queue and giving a [`Communications.BLE_QUEUE_FULL`](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html). response code. For a Venu 2 Garmin watch an API call delay of 600 ms was found to be sustainable (500 ms was still too fast). The code now chains a sequence of updates, so as one finishes it invokes the next item's update. The more items requiring a status update that you pack into your dashboard, the slower each individual item will be updated! The thinking here is that the watch application will only ever be open briefly not persistently, so the delay in picking up state changes won't be observed often for any race condition between two controllers. @@ -240,6 +260,11 @@ The `id` attribute values are taken from the same names used in [`strings.xml`]( | 1.2 | Do not crash on zero items to update. Report unreachable URLs. Verify API URL does not have a trailing slash '/'. Increased HTTP response diagnosis. Reduced minimum API Level required from 3.3.0 to 3.1.0 to allow more device "part numbers" to be satisfied. | | 1.3 | Tap for scripts was working in emulation but not on some phones. Decision is to make the 'service' field in the JSON compulsory for 'tap' menu items. This is a breaking change, but for many might be a fix for something not working correctly. Improve language support, we can now accept language corrections and prevent the automated translation of strings from clobbering manually refined entries. Thank you to two new contributors. | | 1.4 | New lean user Interface with thanks to [Someone0nEarth](https://github.com/Someone0nEarth) for their contribution which is now the default. If you prefer the old style you can still select it in the settings. The provision of a 'service' tag is now not just heavily suggested by the JSON schema, it is enforced in code. With apologies to anyone suffering a breakage as a result. | -| 1.5 | Added an optional confirmation dialogue view to prevent accidental execution of actions on mistaken tap. This also brings a change in the JSON schema to allow an optional field to specify that the confirmation should be used for a menu item. As we are now maturing and adding features we have decided to mitigate breaking changes to the JSON schema by being more careful to adopt the Home Assistant schema (noting there is a 1:1 mapping between YAML and JSON). This change does deprecate the top level `service` tag in favour of `tag_action` containing multiple fields including `service` & `confirm`. Users should migrate to the new format for the new functionality, but the timescale for actual deprecation are long and undecided. | +| 1.5 |
Added an optional confirmation dialogue view to prevent accidental execution of actions on mistaken tap. This also brings a change in the JSON schema to allow an optional field to specify that the confirmation should be used for a menu item. As we are now maturing and adding features we have decided to mitigate breaking changes to the JSON schema by being more careful to adopt the Home Assistant schema (noting there is a 1:1 mapping between YAML and JSON). This change does deprecate the top level `service` tag in favour of `tag_action` containing multiple fields including `service` & `confirm`. Users should migrate to the new format for the new functionality, but the timescale for actual deprecation are long and undecided. | | 1.6 | Added a user configurable 'timeout' in seconds so that when no action is taken the application automatically closes, stopping the continuous polling for changes of status and hence saving the drain on the battery. This can be disabled with timeout=0. | | 1.7 | Added timeout to confirmation views so that when used for security devices it does not linger when left unconfirmed. Thanks to [Jan Schneider](https://github.com/j-a-n) for the contribution. Known bug for devices not supporting [`WatchUi.getCurrentView()`](https://developer.garmin.com/connect-iq/api-docs/Toybox/WatchUi.html#getCurrentView-instance_function) API call which is only available on API Level 3.4.0, e.g. Vivoactive 4S. | +| 2.0 | A significant code base change to enable both a 'widget' version for older devices, e.g. Venu (1), and an application with a glance, e.g. Venu2. These two versions must now be distributed under separate application IDs, but they have the same code base. A further 20 more devices are now supported, the settings have been internationalised, and there's a bug fix for older devices when trying to display a helpful error message but instead the application crashed. This version has come from a significant collaboration with [Someone0nEarth](https://github.com/Someone0nEarth). | + +## Known Issues + +1. On some (old) devices (e.g. Vivoactive 3, Fexix 5s & Edge 520+), the menu does not update correctly to reflect changes in state effected by an external Home Assistant control. E.g. when the phone application changes the toggle status of a switch, the Garmin application does not reflect that change until the menu is touched or scrolled a little. This is a [known issue](https://forums.garmin.com/developer/connect-iq/i/bug-reports/menu2-doesn-t-allow-live-updates) already reported without a suggested software fix. diff --git a/Troubleshooting.md b/Troubleshooting.md new file mode 100644 index 0000000..623150d --- /dev/null +++ b/Troubleshooting.md @@ -0,0 +1,107 @@ +# Troubleshooting Guide + +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. + +## Nabu Casa Setup + +You can purchase cloud-based access to your Home Assistant from [Nabu Casa](https://www.nabucasa.com/), and then your setup will look something like this. + +![Nabu Casa Setup](images/nabu_casa_setup.png) + +* Your API URL would be of the format `https://.ui.nabu.casa/api` +* Your Garmin Watch Menu would be of the format Menu: `https://.ui.nabu.casa/local/garmin/menu.json` + +Where `` is your personal Nabu Casa account ID. + +## Do It Yourself Setup + +Before Nabu Casa, or if you wanted to manage your own infrastructure, you might have something like the following: + +![Do It Yourself Setup](images/do_it_yourself_setup.png) + +Now you have to manage: + +* Dynamic DNS +* Public access via router port forwarding +* Security via HTTPS and URL forwarding +* Certificates for HTTPS via say [Let's Encrypt](https://letsencrypt.org/) (Nginx web server helps here) +* Proxy allow list in `configuration.yaml` as follows: + +```yaml +http: + use_x_forwarded_for: true + trusted_proxies: + - 127.0.0.1 + - 192.168.xx.xx # Server IP - AMEND THIS + - 172.30.32.0/23 # Docker IPs for NGINX + - 172.30.33.0/24 # SSL proxy server + - 172.16.0.0/12 # +``` + +## Menu Configuration URL + +This URL is very simple, you should be able to read the contents returned in a standard web browser. + +![Browser Address Bar URL](images/menu_url.png) + +(Other browsers are available...) + +The browser page should then display the JSON string you saved to the file on the web server. The point is this is a simple HTTP GET request with no bells and whistles. + +The menu configuration can be hosted anywhere, it does not have to be on the Home Assistant web server. Just as long as it is reachable from your phone from which you Bluetooth connect to your watch, or you watch if it has direct Internet access. + +## Home Assistant API URL + +This is slightly trickier owning to the need to supply the API key. Here are three ways you can test your API URL is correctly configured. If successful, each of these should produce a JSON string output looking like: + +```json +{"message":"API running."} +``` + +### Linux, MacOS, UNIX, Cygwin etc + +Save the following as a file called `api_test.sh`, edit to include your personal values for the variables, `chmod +x api_test.sh` and then execute with `./api_test.sh`. + +```shell +#!/bin/bash + +API_KEY="" +URL="https:///api" + +curl -s -X GET \ + -H "Authorization: Bearer ${API_KEY}" \ + -H "Content-Type: application/json" \ + ${URL}/ +``` + +### MS Windows + +Save the following as a file called `api_test.cmd`, edit to include your personal values for the variables and then double click. + +```shell +@echo off + +set API_KEY= +set URL=https:///api + +curl -s -X GET ^ + -H "Authorization: Bearer %API_KEY%" ^ + -H "Content-Type: application/json" ^ + %URL%/ + +echo. +pause +``` + +![API Test MS-DOS Output](images/api_test_dos_output.png) + +### On-line + +There's an online way of testing the API URL too, thanks to [REQBIN](https://reqbin.com/post-online). This has less setup and it can be saved if you log into the web site. + +![API Test MS-DOS Output](images/api_test_online.png) + +## Top Problems + +1. Failure to copy & paste keys and URLs leading to minor and hard to see errors in strings, even with protestations they are the same! (No they weren't...) +2. Accessibility of URLs, hence the above help guide. diff --git a/export.cmd b/export.cmd new file mode 100644 index 0000000..8ce3b82 --- /dev/null +++ b/export.cmd @@ -0,0 +1,133 @@ +@echo off +rem ----------------------------------------------------------------------------------- +rem +rem Distributed under MIT Licence +rem See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE. +rem +rem ----------------------------------------------------------------------------------- +rem +rem GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely +rem tested on a Venu 2 device. The source code is provided at: +rem https://github.com/house-of-abbey/GarminHomeAssistant. +rem +rem J D Abbey & P A Abbey, 28 December 2022 +rem +rem Reference: +rem * Using Monkey C from the Command Line +rem * https://developer.garmin.com/connect-iq/reference-guides/monkey-c-command-line-setup/ +rem +rem ----------------------------------------------------------------------------------- + +rem Check this path is correct for your Java installation +set JAVA_PATH=C:\Program Files (x86)\Common Files\Oracle\Java\javapath +rem SDK_PATH should work for all users +set /p SDK_PATH=<"%USERPROFILE%\AppData\Roaming\Garmin\ConnectIQ\current-sdk.cfg" +set SDK_PATH=%SDK_PATH:~0,-1%\bin +rem Assume we can create and use this directory +set DEST=export + +rem C:\>java -jar %SDK_PATH%\monkeybrains.jar -h +rem usage: monkeyc [-a ] [-b ] [--build-stats ] [-c ] [-d ] +rem [--debug-log-level ] [--debug-log-output ] [-e] +rem [--Eno-invalid-symbol] [-f ] [-g] [-h] [-i ] [-k] [-l ] +rem [-m ] [--no-gen-styles] [-o ] [-O ] [-p ] [-r] [-s +rem ] [-t] [-u ] [-v] [-w] [-x ] [-y ] [-z ] +rem -a,--apidb API import file +rem -b,--apimir API MIR file +rem --build-stats Print build stats [0=basic] +rem -c,--api-level API Level to target +rem -d,--device Target device +rem --debug-log-level Debug logging verbosity [0=errors, 1=basic, +rem 2=intermediate, 3=verbose] +rem --debug-log-output Output log zip file +rem -e,--package-app Create an application package. +rem --Eno-invalid-symbol Do not error when a symbol is found to be invalid +rem -f,--jungles Jungle files +rem -g,--debug Print debug output +rem -h,--help Prints help information +rem -i,--import-dbg Import api.debug.xml +rem -k,--profile Enable profiling support +rem -l,--typecheck Type check [0=off, 1=gradual, 2=informative, +rem 3=strict] +rem -m,--manifest Manifest file (deprecated) +rem --no-gen-styles Do not generate Rez.Styles module +rem -o,--output Output file to create +rem -O,--optimization Optimization level [0=none, 1=basic, 2=fast +rem optimizations, 3=slow optimizations] [p=optimize +rem performance, z=optimize code space] +rem -p,--project-info projectInfo.xml file to use when compiling +rem -r,--release Strip debug information +rem -s,--sdk-version SDK version to target (deprecated, use -c +rem -t,--unit-test Enables compilation of unit tests +rem -u,--devices devices.xml file to use when compiling (deprecated) +rem -v,--version Prints the compiler version +rem -w,--warn Show compiler warnings +rem -x,--excludes Add annotations to the exclude list (deprecated) +rem -y,--private-key Private key to sign builds with +rem -z,--rez Resource files (deprecated) + +rem Batch file's directory where the source code is +set SRC=%~dp0 +rem drop last character '\' +set SRC=%SRC:~0,-1% + +if not exist %DEST% ( + md %DEST% +) + +if exist %SRC%\export\HomeAssistant*.iq ( + del /f /q %SRC%\export\HomeAssistant*.iq +) + +echo. +echo Starting export of HomeAssistant Application +echo. + +"%JAVA_PATH%\java.exe" ^ + -Xms1g ^ + -Dfile.encoding=UTF-8 ^ + -Dapple.awt.UIElement=true ^ + -jar %SDK_PATH%\monkeybrains.jar ^ + --api-level 3.1.0 ^ + --output %SRC%\export\HomeAssistant-app.iq ^ + --jungles %SRC%\monkey.jungle ^ + --private-key %SRC%\..\developer_key ^ + --package-app ^ + --release +rem --warn + +echo. +echo Starting export of HomeAssistant Widget +echo. + +"%JAVA_PATH%\java.exe" ^ + -Xms1g ^ + -Dfile.encoding=UTF-8 ^ + -Dapple.awt.UIElement=true ^ + -jar %SDK_PATH%\monkeybrains.jar ^ + --api-level 3.1.0 ^ + --output %SRC%\export\HomeAssistant-widget.iq ^ + --jungles %SRC%\monkey-widget.jungle ^ + --private-key %SRC%\..\developer_key ^ + --package-app ^ + --release +rem --warn + +echo. +echo Finished exporting HomeAssistant +dir %SRC%\export\HomeAssistant*.iq + +pause +exit /b + +rem Compile PRG for a single device for side loading +"%JAVA_PATH%\java.exe" ^ + -Xms1g ^ + -Dfile.encoding=UTF-8 ^ + -Dapple.awt.UIElement=true ^ + -jar %SDK_PATH%\monkeybrains.jar ^ + --output %SRC%\bin\HomeAssistant.prg ^ + --jungles %SRC%\monkey.jungle ^ + --private-key %SRC%\..\developer_key ^ + --device venu2_sim ^ + --warn diff --git a/images/1440x720_hero_image.png b/images/1440x720_hero_image.png index 3e3baac..fa76cd3 100644 Binary files a/images/1440x720_hero_image.png and b/images/1440x720_hero_image.png differ diff --git a/images/Actual_Venu2_LeanUI.jpg b/images/Actual_Venu2_LeanUI.jpg new file mode 100644 index 0000000..0318d38 Binary files /dev/null and b/images/Actual_Venu2_LeanUI.jpg differ diff --git a/images/Actual_Venu2_LeanUI_500.jpg b/images/Actual_Venu2_LeanUI_500.jpg new file mode 100644 index 0000000..30ce882 Binary files /dev/null and b/images/Actual_Venu2_LeanUI_500.jpg differ diff --git a/images/Actual_Venu2_Theme.jpg b/images/Actual_Venu2_Theme.jpg index 1cba712..559429e 100644 Binary files a/images/Actual_Venu2_Theme.jpg and b/images/Actual_Venu2_Theme.jpg differ diff --git a/images/Actual_Venu2_Theme_500.jpg b/images/Actual_Venu2_Theme_500.jpg new file mode 100644 index 0000000..9317828 Binary files /dev/null and b/images/Actual_Venu2_Theme_500.jpg differ diff --git a/images/GarminHomeAssistantSettings.png b/images/GarminHomeAssistantSettings.png index 81947c4..7d4e228 100644 Binary files a/images/GarminHomeAssistantSettings.png and b/images/GarminHomeAssistantSettings.png differ diff --git a/images/SimEpix.png b/images/SimEpix.png index a499fa7..6ea9c70 100644 Binary files a/images/SimEpix.png and b/images/SimEpix.png differ diff --git a/images/SimFenix.png b/images/SimFenix.png index d527bb8..d07b56c 100644 Binary files a/images/SimFenix.png and b/images/SimFenix.png differ diff --git a/images/SimHomeMenu.png b/images/SimHomeMenu.png index dae8bfe..0788c12 100644 Binary files a/images/SimHomeMenu.png and b/images/SimHomeMenu.png differ diff --git a/images/SimSubMenuItem.png b/images/SimSubMenuItem.png index 66f3dee..e1cd62b 100644 Binary files a/images/SimSubMenuItem.png and b/images/SimSubMenuItem.png differ diff --git a/images/SimSubmenu.png b/images/SimSubmenu.png index 6b8cf91..3c42af2 100644 Binary files a/images/SimSubmenu.png and b/images/SimSubmenu.png differ diff --git a/images/SimTapResponse.png b/images/SimTapResponse.png index 1522ab4..08b12fe 100644 Binary files a/images/SimTapResponse.png and b/images/SimTapResponse.png differ diff --git a/images/Venu2_LeanUI.png b/images/Venu2_LeanUI.png new file mode 100644 index 0000000..436e706 Binary files /dev/null and b/images/Venu2_LeanUI.png differ diff --git a/images/Venu2_Original.png b/images/Venu2_Original.png new file mode 100644 index 0000000..a0dfdc0 Binary files /dev/null and b/images/Venu2_Original.png differ diff --git a/images/Venu2_app_start.png b/images/Venu2_app_start.png new file mode 100644 index 0000000..f82c6de Binary files /dev/null and b/images/Venu2_app_start.png differ diff --git a/images/Venu2_glance_start.png b/images/Venu2_glance_start.png new file mode 100644 index 0000000..4d4a126 Binary files /dev/null and b/images/Venu2_glance_start.png differ diff --git a/images/Venu_Widget_sim.png b/images/Venu_Widget_sim.png new file mode 100644 index 0000000..363adbd Binary files /dev/null and b/images/Venu_Widget_sim.png differ diff --git a/images/Vivoactive3_app_start.jpg b/images/Vivoactive3_app_start.jpg new file mode 100644 index 0000000..a6daa48 Binary files /dev/null and b/images/Vivoactive3_app_start.jpg differ diff --git a/images/api_test_dos_output.png b/images/api_test_dos_output.png new file mode 100644 index 0000000..d5cc1ca Binary files /dev/null and b/images/api_test_dos_output.png differ diff --git a/images/api_test_online.png b/images/api_test_online.png new file mode 100644 index 0000000..02c27f0 Binary files /dev/null and b/images/api_test_online.png differ diff --git a/images/do_it_yourself_setup.png b/images/do_it_yourself_setup.png new file mode 100644 index 0000000..70f8ed0 Binary files /dev/null and b/images/do_it_yourself_setup.png differ diff --git a/images/menu_url.png b/images/menu_url.png new file mode 100644 index 0000000..3775fb0 Binary files /dev/null and b/images/menu_url.png differ diff --git a/images/nabu_casa_setup.png b/images/nabu_casa_setup.png new file mode 100644 index 0000000..8d10f5d Binary files /dev/null and b/images/nabu_casa_setup.png differ diff --git a/images/source/1440x720_hero_image.psd b/images/source/1440x720_hero_image.psd index 5aa68ff..e9dbb83 100644 Binary files a/images/source/1440x720_hero_image.psd and b/images/source/1440x720_hero_image.psd differ diff --git a/images/source/Network_Generic.pptx b/images/source/Network_Generic.pptx new file mode 100644 index 0000000..e2abcdb Binary files /dev/null and b/images/source/Network_Generic.pptx differ diff --git a/images/source/SimEpix.png b/images/source/SimEpix.png index e9ea7b8..ea9843b 100644 Binary files a/images/source/SimEpix.png and b/images/source/SimEpix.png differ diff --git a/images/source/SimFenix.png b/images/source/SimFenix.png index 51442d3..552665d 100644 Binary files a/images/source/SimFenix.png and b/images/source/SimFenix.png differ diff --git a/images/source/SimTapResponse.png b/images/source/SimTapResponse.png index 4b1c99b..732957b 100644 Binary files a/images/source/SimTapResponse.png and b/images/source/SimTapResponse.png differ diff --git a/images/source/SimVenu2HomeMenu.png b/images/source/SimVenu2HomeMenu.png index 967a61c..e242370 100644 Binary files a/images/source/SimVenu2HomeMenu.png and b/images/source/SimVenu2HomeMenu.png differ diff --git a/images/source/SimVenu2HomeSubMenu.png b/images/source/SimVenu2HomeSubMenu.png index 97f5cfd..8b81d1d 100644 Binary files a/images/source/SimVenu2HomeSubMenu.png and b/images/source/SimVenu2HomeSubMenu.png differ diff --git a/images/source/SimVenu2HomeSubMenuItem.png b/images/source/SimVenu2HomeSubMenuItem.png index c77ecfe..f520e5a 100644 Binary files a/images/source/SimVenu2HomeSubMenuItem.png and b/images/source/SimVenu2HomeSubMenuItem.png differ diff --git a/include/app/WidgetApp.mc b/include/app/WidgetApp.mc new file mode 100644 index 0000000..1234385 --- /dev/null +++ b/include/app/WidgetApp.mc @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------------- +// +// 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, 20 December 2023 +// +// +// Description: +// +// A tedious diversion intended to make it possible to have the same source code for +// both a widget and an application. This file provides a single constant to +// determine which, and then the source file is conditionally included by the each +// .jungle file. +// +//----------------------------------------------------------------------------------- + +using Toybox.Lang; + +class WidgetApp { + static const isWidget = false; +} diff --git a/include/widget/WidgetApp.mc b/include/widget/WidgetApp.mc new file mode 100644 index 0000000..b86ec4c --- /dev/null +++ b/include/widget/WidgetApp.mc @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------------- +// +// 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, 20 December 2023 +// +// +// Description: +// +// A tedious diversion intended to make it possible to have the same source code for +// both a widget and an application. This file provides a single constant to +// determine which, and then the source file is conditionally included by the each +// .jungle file. +// +//----------------------------------------------------------------------------------- + +using Toybox.Lang; + +class WidgetApp { + static const isWidget = true; +} diff --git a/manifest-widget.xml b/manifest-widget.xml new file mode 100644 index 0000000..6fdc41b --- /dev/null +++ b/manifest-widget.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ara + bul + zhs + zht + hrv + ces + dan + dut + deu + gre + eng + est + fin + fre + heb + hun + ind + ita + jpn + kor + lav + lit + zsm + nob + pol + por + slo + ron + + slv + spa + swe + tha + tur + ukr + vie + + + + + \ No newline at end of file diff --git a/manifest.xml b/manifest.xml index e099b04..5cf5860 100644 --- a/manifest.xml +++ b/manifest.xml @@ -11,7 +11,10 @@ P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 - Someone0nEarth's Test App id="bf69be91-5833-4d96-92ea-c5f1a9db5dcc" type="widget" + Device Information & References: + * https://developer.garmin.com/connect-iq/compatible-devices/ + * https://developer.garmin.com/connect-iq/reference-guides/devices-reference/ + philipabbey's Test App id="98c36259-498a-4458-9cef-74a273ad2bc3" type="watch-app" Live Application id="40131e87-31ff-454b-a8e2-92276ee399d6" type="watch-app" @@ -21,7 +24,14 @@ Use "Monkey C: Edit Application" from the Visual Studio Code command palette to update the application attributes. --> - + + + - - - - - - - - - - - + - + + + - + + + - - + + - + - - - - - + + + - - - - - - - - - - + - - - - - + + + - - - + - - - - - - - + + @@ -142,31 +122,23 @@ - - - - + - - - + + + - - - - - - - + + + + - - مفتاح API لـ HomeAssistant. رمز الوصول طويل الأمد. @@ -49,4 +51,5 @@ بعد هذا الوقت (بالثواني)، يتم إغلاق مربع حوار تأكيد الإجراء تلقائيًا ويتم إلغاء الإجراء. اضبط على 0 لتعطيل المهلة. تمثيل الأنواع بأيقونات (إيقاف) أو بالتسميات (تشغيل). محاذاة القائمة لليسار (إيقاف) أو لليمين (تشغيل). + (القطعة فقط) قم بتشغيل التطبيق تلقائيًا من الأداة دون انتظار نقرة واحدة. \ No newline at end of file diff --git a/resources-bul/strings/strings.xml b/resources-bul/strings/strings.xml index 93eb779..7c70a3c 100644 --- a/resources-bul/strings/strings.xml +++ b/resources-bul/strings/strings.xml @@ -25,21 +25,23 @@ Докоснете Меню Сигурен? - Няма телефонна връзка + Няма телефонна връзка Няма интернет връзка Няма отговор, проверете интернет връзката - Грешка при извличане на менюто - Няма API ключ в настройките на приложението - Няма URL адрес на API в настройките на приложението - Няма конфигурационен URL адрес в настройките на приложението + Няма API ключ в настройките на приложението + Няма URL адрес на API в настройките на приложението + Няма конфигурационен URL адрес в настройките на приложението Извикванията на API са твърде бързи. Моля, забавете вашите заявки. URL не е намерен. Потенциална грешка в URL адреса на API в настройките. URL не е намерен. Потенциална грешка в URL адреса на конфигурацията в настройките. + Няма върнат JSON от HTTP заявка. HTTP заявката върна код на грешка = URL адресът на API не трябва да има наклонена черта '/' в края - Извличане на конфигурацията на менюто.. - Плъзнете надясно, за да излезете\nДокоснете, за да останете - Натиснете Назад, за да излезете\nВлезте, за да останете + На разположение + Проверка... + Недостъпен + Неконфигуриран + Меню API ключ за HomeAssistant. Токен за дълготраен достъп. @@ -49,4 +51,5 @@ След това време (в секунди) диалоговият прозорец за потвърждение за действие се затваря автоматично и действието се отменя. Задайте 0, за да деактивирате изчакването. Представяне на типове с икони (изключено) или с етикети (включено). Ляво (изключено) или дясно (включено) подравняване на менюто. + (Само за джаджа) Автоматично стартирайте приложението от джаджата, без да чакате докосване. \ No newline at end of file diff --git a/resources-ces/strings/strings.xml b/resources-ces/strings/strings.xml index 691fbee..80217d0 100644 --- a/resources-ces/strings/strings.xml +++ b/resources-ces/strings/strings.xml @@ -25,21 +25,23 @@ Klepněte Jídelní lístek Tak určitě? - Žádné telefonní spojení + Žádné telefonní spojení Žádné internetové připojení Žádná odpověď, zkontrolujte připojení k internetu - Chyba načítání nabídky - V nastavení aplikace není žádný klíč API - V nastavení aplikace není žádná adresa URL API - V nastavení aplikace není žádná konfigurační URL + V nastavení aplikace není žádný klíč API + V nastavení aplikace není žádná adresa URL API + V nastavení aplikace není žádná konfigurační URL Příliš rychlá volání API. Zpomalte prosím své požadavky. - Adresa URL nenalezena. Potenciální chyba adresy URL rozhraní API v nastavení. + Adresa URL nenalezena. Potenciální chyba URL API v nastavení. Adresa URL nenalezena. Potenciální chyba konfigurační adresy URL v nastavení. + Z požadavku HTTP se nevrátil žádný JSON. Požadavek HTTP vrátil kód chyby = Adresa URL rozhraní API nesmí mít koncové lomítko „/“ - Načítání konfigurace nabídky... - Přejetím prstem doprava ukončíte\nKlepnutím zůstanete - Stisknutím Zpět ukončíte\nVstupte a zůstanete + Dostupný + Kontrola... + Není k dispozici + Nenakonfigurováno + Jídelní lístek Klíč API pro HomeAssistant. Přístupový token s dlouhou životností. @@ -49,4 +51,5 @@ Po uplynutí této doby (v sekundách) se dialog pro potvrzení akce automaticky zavře a akce se zruší. Nastavením na 0 deaktivujete časový limit. Znázornění typů pomocí ikon (vypnuto) nebo pomocí štítků (zapnuto). Zarovnání nabídky vlevo (vypnuto) nebo vpravo (zapnuto). + (Pouze widget) Automaticky spusťte aplikaci z widgetu bez čekání na klepnutí. \ No newline at end of file diff --git a/resources-dan/strings/strings.xml b/resources-dan/strings/strings.xml index 684669d..0eb465d 100644 --- a/resources-dan/strings/strings.xml +++ b/resources-dan/strings/strings.xml @@ -25,21 +25,23 @@ Tryk på Menu Jo da? - Ingen telefonforbindelse + Ingen telefonforbindelse Ingen internetforbindelse Intet svar, tjek internetforbindelse - Menuhentningsfejl - Ingen API-nøgle i applikationsindstillingerne - Ingen API-URL i applikationsindstillingerne - Ingen konfigurations-URL i applikationsindstillingerne + Ingen API-nøgle i applikationsindstillingerne + Ingen API-URL i applikationsindstillingerne + Ingen konfigurations-URL i applikationsindstillingerne API-kald for hurtigt. Sænk venligst dine anmodninger. URL ikke fundet. Potentiel API URL-fejl i indstillinger. URL ikke fundet. Potentiel konfigurations-URL-fejl i indstillinger. + Ingen JSON returneret fra HTTP-anmodning. HTTP-anmodning returnerede fejlkode = API URL må ikke have en efterfølgende skråstreg '/' - Henter menukonfiguration.. - Stryg til højre for at afslutte\nTryk for at blive - Tryk på Tilbage for at afslutte\nEnter for at blive + Ledig + Tjekker... + Ikke tilgængelig + Ukonfigureret + Menu API-nøgle til HomeAssistant. Adgangstoken med lang levetid. @@ -49,4 +51,5 @@ Efter dette tidspunkt (i sekunder) lukkes en bekræftelsesdialog for en handling automatisk, og handlingen annulleres. Indstil til 0 for at deaktivere timeout. Repræsenterer typer med ikoner (fra) eller med etiketter (til). Venstre (fra) eller Højre (til) menujustering. + (Kun widget) Start automatisk applikationen fra widgetten uden at vente på et tryk. \ No newline at end of file diff --git a/resources-deu/strings/corrections.xml b/resources-deu/strings/corrections.xml index 3c50a66..343b56d 100644 --- a/resources-deu/strings/corrections.xml +++ b/resources-deu/strings/corrections.xml @@ -22,7 +22,4 @@ Antippen Menü Die HTTP-Anfrage gab folgenden Fehlercode zurück = - Zum Beenden nach rechts swipen\nZum Bleiben antippen - Drücke „Zurück“, um zu Beenden.\n„Enter“, um zu bleiben - Lade Config vom Server ... diff --git a/resources-deu/strings/strings.xml b/resources-deu/strings/strings.xml index bb019af..057d090 100644 --- a/resources-deu/strings/strings.xml +++ b/resources-deu/strings/strings.xml @@ -23,23 +23,25 @@ An Aus Antippen - Menü + Menü Sicher? - Keine Telefonverbindung + Keine Telefonverbindung Keine Internetverbindung Keine Antwort, überprüfen Sie die Internetverbindung - Fehler beim Abrufen des Menüs - Kein API-Schlüssel in den Anwendungseinstellungen - Keine API-URL in den Anwendungseinstellungen - Keine Konfigurations-URL in den Anwendungseinstellungen + Kein API-Schlüssel in den Anwendungseinstellungen + Keine API-URL in den Anwendungseinstellungen + Keine Konfigurations-URL in den Anwendungseinstellungen API-Aufrufe zu schnell. Bitte verlangsamen Sie Ihre Anfragen. URL nicht gefunden. Möglicher API-URL-Fehler in den Einstellungen. URL nicht gefunden. Möglicher Konfigurations-URL-Fehler in den Einstellungen. - Die HTTP-Anfrage gab folgenden Fehlercode zurück = + Von der HTTP-Anfrage wurde kein JSON zurückgegeben. + Die HTTP-Anfrage gab folgenden Fehlercode zurück = Die API-URL darf keinen abschließenden Schrägstrich „/“ enthalten. - Lade Config vom Server ... - Zum Beenden nach rechts swipen\nZum Bleiben antippen - Drücke „Zurück“, um zu Beenden.\n„Enter“, um zu bleiben + Verfügbar + Überprüfung... + Nicht verfügbar + Unkonfiguriert + Speisekarte API-Schlüssel für HomeAssistant. Langlebiges Zugriffstoken. @@ -49,4 +51,5 @@ Nach dieser Zeit (in Sekunden) wird automatisch ein Bestätigungsdialog für eine Aktion geschlossen und die Aktion abgebrochen. Auf 0 setzen, um das Timeout zu deaktivieren. Darstellen von Typen mit Symbolen (aus) oder mit Beschriftungen (ein). Menüausrichtung links (aus) oder rechts (ein). + (Nur Widget) Starten Sie die Anwendung automatisch über das Widget, ohne auf einen Tipp warten zu müssen. \ No newline at end of file diff --git a/resources-dut/strings/strings.xml b/resources-dut/strings/strings.xml index 4821e85..20b60e1 100644 --- a/resources-dut/strings/strings.xml +++ b/resources-dut/strings/strings.xml @@ -25,21 +25,23 @@ Kraan Menu Zeker? - Geen telefoonverbinding + Geen telefoonverbinding Geen internet verbinding Geen reactie, controleer de internetverbinding - Fout bij ophalen van menu - Geen API-sleutel in de applicatie-instellingen - Geen API-URL in de applicatie-instellingen - Geen configuratie-URL in de applicatie-instellingen + Geen API-sleutel in de applicatie-instellingen + Geen API-URL in de applicatie-instellingen + Geen configuratie-URL in de applicatie-instellingen API-aanroepen te snel. Vertraag uw verzoeken. URL niet gevonden. Mogelijke API-URL-fout in instellingen. URL niet gevonden. Mogelijke configuratie-URL-fout in de instellingen. + Er is geen JSON geretourneerd door een HTTP-verzoek. HTTP-verzoek retourneerde foutcode = API-URL mag geen afsluitende slash '/' bevatten - Menuconfiguratie ophalen.. - Veeg naar rechts om af te sluiten\nTik om te blijven - Druk op Terug om af te sluiten\nEnter om te blijven + Beschikbaar + Controleren... + Niet beschikbaar + Niet geconfigureerd + Menu API-sleutel voor HomeAssistant. Toegangstoken met lange levensduur. @@ -49,4 +51,5 @@ Na deze tijd (in seconden) wordt automatisch een bevestigingsvenster voor een actie gesloten en wordt de actie geannuleerd. Stel in op 0 om de time-out uit te schakelen. Typen weergeven met pictogrammen (uit) of met labels (aan). Links (uit) of rechts (aan) Menu-uitlijning. + (Alleen Widget) Start de applicatie automatisch vanuit de widget zonder te wachten op een tik. \ No newline at end of file diff --git a/resources-est/strings/strings.xml b/resources-est/strings/strings.xml index 4d7d866..7ab95cc 100644 --- a/resources-est/strings/strings.xml +++ b/resources-est/strings/strings.xml @@ -25,21 +25,23 @@ Puudutage Menüü Muidugi? - Telefoniühendus puudub + Telefoniühendus puudub Interneti-ühendus puudub Ei reageeri, kontrollige Interneti-ühendust - Menüü toomise viga - Rakenduse seadetes pole API-võtit - Rakenduse seadetes pole API URL-i - Rakenduse seadetes pole konfiguratsiooni URL-i - API-kõned liiga kiired. Palun aeglustage taotluste esitamist. + Rakenduse seadetes pole API-võtit + Rakenduse seadetes pole API URL-i + Rakenduse seadetes pole konfiguratsiooni URL-i + API-kõned liiga kiired. Palun aeglustage oma taotlusi. URL-i ei leitud. Võimalik API URL-i viga seadetes. URL-i ei leitud. Võimalik konfiguratsiooni URL-i viga seadetes. + HTTP päringust ei tagastatud ühtegi JSON-i. HTTP päring tagastas veakoodi = API URL-i lõpus ei tohi olla kaldkriipsu „/” - Menüü konfiguratsiooni toomine... - Väljumiseks pühkige paremale\nPuudutage, et jääda - Väljumiseks vajutage Tagasi\nSisestage, et jääda + Saadaval + Kontrollimine... + Pole saadaval + Konfigureerimata + Menüü API-võti HomeAssistantile. Pikaealine juurdepääsuluba. @@ -49,4 +51,5 @@ Pärast seda aega (sekundites) suletakse automaatselt toimingu kinnitusdialoog ja toiming tühistatakse. Ajalõpu keelamiseks määrake väärtusele 0. Tüüpide tähistamine ikoonidega (väljas) või siltidega (sees). Vasak (väljas) või parem (sees) menüü joondamine. + (Ainult vidin) Käivitage rakendus automaatselt vidinast ilma puudutust ootamata. \ No newline at end of file diff --git a/resources-fin/strings/strings.xml b/resources-fin/strings/strings.xml index 395399f..fd70bc2 100644 --- a/resources-fin/strings/strings.xml +++ b/resources-fin/strings/strings.xml @@ -25,21 +25,23 @@ Napauta Valikko Varma? - Ei puhelinyhteyttä + Ei puhelinyhteyttä Ei Internet-yhteyttä Ei vastausta, tarkista Internet-yhteys - Valikkohakuvirhe - Sovellusasetuksissa ei ole API-avainta - Sovellusasetuksissa ei ole API URL-osoitetta - Sovelluksen asetuksissa ei ole konfigurointi-URL-osoitetta + Sovellusasetuksissa ei ole API-avainta + Sovellusasetuksissa ei ole API URL-osoitetta + Sovelluksen asetuksissa ei ole konfigurointi-URL-osoitetta API-kutsut liian nopeita. Hidasta pyyntöjäsi. URL-osoitetta ei löydy. Mahdollinen API URL -virhe asetuksissa. URL-osoitetta ei löydy. Mahdollinen konfigurointi-URL-virhe asetuksissa. + HTTP-pyynnöstä ei palautettu JSON-tiedostoja. HTTP-pyyntö palautti virhekoodin = API-URL-osoitteessa ei saa olla perässä olevaa kauttaviivaa '/' - Haetaan valikon asetuksia... - Poistu pyyhkäisemällä oikealle\nPysy napauttamalla - Poistu painamalla Takaisin\nSyötä jäädäksesi + Saatavilla + Tarkistetaan... + Ei saatavilla + Määrittämätön + Valikko API-avain HomeAssistantille. Pitkäikäinen pääsytunnus. @@ -49,4 +51,5 @@ Tämän ajan kuluttua (sekunneissa) toiminnon vahvistusikkuna suljetaan automaattisesti ja toiminto peruutetaan. Aseta arvoksi 0 poistaaksesi aikakatkaisun käytöstä. Esittää tyyppejä kuvakkeilla (pois päältä) tai tarroilla (päällä). Vasen (pois) tai oikea (päällä) valikon kohdistus. + (Vain widget) Käynnistä sovellus automaattisesti widgetistä odottamatta napautusta. \ No newline at end of file diff --git a/resources-fre/strings/strings.xml b/resources-fre/strings/strings.xml index 5de193d..ab02a1f 100644 --- a/resources-fre/strings/strings.xml +++ b/resources-fre/strings/strings.xml @@ -20,26 +20,28 @@ HomeAssistant - Activé + Activé Désactivé Clic Menu Bien sûr? - Pas de connexion téléphonique + Pas de connexion téléphonique Pas de connexion Internet Pas de réponse, vérifiez la connexion Internet - Erreur de récupération du menu - Pas de clé API dans les paramètres de l'application - Aucune URL API dans les paramètres de l'application - Aucune URL de configuration dans les paramètres de l'application - Appels API trop rapide. Veuillez signaler cette erreur avec les détails de l'appareil. + Pas de clé API dans les paramètres de l'application + Aucune URL API dans les paramètres de l'application + Aucune URL de configuration dans les paramètres de l'application + Appels API trop rapide. Veuillez signaler cette erreur avec les détails de l'appareil. URL introuvable. Erreur potentielle d'URL d'API dans les paramètres. URL introuvable. Erreur potentielle d'URL de configuration dans les paramètres. + Aucun JSON renvoyé par la requête HTTP. La requête HTTP a renvoyé un code d'erreur = L'URL de l'API ne doit pas comporter de barre oblique finale '/' - Récupération de la configuration du menu. - Balayez vers la droite pour quitter\nAppuyez pour rester - Appuyez sur Retour pour quitter\nEntrez pour rester + Disponible + Vérification... + Indisponible + Non configuré + Menu Clé API pour HomeAssistant. Jeton d'accès de longue durée. @@ -49,4 +51,5 @@ Passé ce délai (en secondes), une boîte de dialogue de confirmation d'une action se ferme automatiquement et l'action est annulée. Réglez sur 0 pour désactiver le délai d'attente. Représentation des types avec des icônes (off) ou avec des étiquettes (on). Alignement du menu à gauche (désactivé) ou à droite (activé). + (Widget uniquement) Démarrez automatiquement l'application à partir du widget sans attendre un clic. \ No newline at end of file diff --git a/resources-gre/strings/strings.xml b/resources-gre/strings/strings.xml index 4a88205..7e77b57 100644 --- a/resources-gre/strings/strings.xml +++ b/resources-gre/strings/strings.xml @@ -25,21 +25,23 @@ Παρακέντηση Μενού Σίγουρος? - Δεν υπάρχει σύνδεση τηλεφώνου + Δεν υπάρχει σύνδεση τηλεφώνου Δεν υπάρχει σύνδεση στο διαδίκτυο Καμία απάντηση, ελέγξτε τη σύνδεση στο Διαδίκτυο - Σφάλμα ανάκτησης μενού - Δεν υπάρχει κλειδί API στις ρυθμίσεις της εφαρμογής - Δεν υπάρχει URL API στις ρυθμίσεις της εφαρμογής - Δεν υπάρχει διεύθυνση URL διαμόρφωσης στις ρυθμίσεις της εφαρμογής + Δεν υπάρχει κλειδί API στις ρυθμίσεις της εφαρμογής + Δεν υπάρχει URL API στις ρυθμίσεις της εφαρμογής + Δεν υπάρχει διεύθυνση URL διαμόρφωσης στις ρυθμίσεις της εφαρμογής Κλήσεις API πολύ γρήγορες. Παρακαλώ επιβραδύνετε τα αιτήματά σας. Η διεύθυνση URL δεν βρέθηκε. Πιθανό σφάλμα διεύθυνσης URL API στις ρυθμίσεις. Η διεύθυνση URL δεν βρέθηκε. Πιθανό σφάλμα διεύθυνσης URL διαμόρφωσης στις ρυθμίσεις. + Δεν επιστράφηκε JSON από αίτημα HTTP. Το αίτημα HTTP επέστρεψε κωδικό σφάλματος = Η διεύθυνση URL του API δεν πρέπει να έχει τελική κάθετο "/" - Ανάκτηση παραμέτρων μενού.. - Σύρετε προς τα δεξιά για έξοδο\nΠατήστε για να μείνετε - Πατήστε Επιστροφή για Έξοδος\nEnter για να μείνετε + Διαθέσιμος + Ελεγχος... + Μη διαθέσιμο + Μη διαμορφωμένο + Μενού Κλειδί API για το HomeAssistant. Διακριτικό πρόσβασης μακράς διαρκείας. @@ -49,4 +51,5 @@ Μετά από αυτό το χρονικό διάστημα (σε δευτερόλεπτα), ένα παράθυρο διαλόγου επιβεβαίωσης για μια ενέργεια κλείνει αυτόματα και η ενέργεια ακυρώνεται. Ορίστε στο 0 για να απενεργοποιήσετε το χρονικό όριο. Αναπαράσταση τύπων με εικονίδια (απενεργοποίηση) ή με ετικέτες (ενεργό). Αριστερά (απενεργοποίηση) ή Δεξιά (ενεργό) Ευθυγράμμιση μενού. + (Μόνο widget) Αυτόματη εκκίνηση της εφαρμογής από το widget χωρίς να περιμένετε ένα πάτημα. \ No newline at end of file diff --git a/resources-heb/strings/strings.xml b/resources-heb/strings/strings.xml index c2e80a7..f3fbb14 100644 --- a/resources-heb/strings/strings.xml +++ b/resources-heb/strings/strings.xml @@ -25,21 +25,23 @@ בֶּרֶז תַפרִיט בטוח? - אין חיבור לטלפון + אין חיבור לטלפון אין חיבור אינטרנט אין תגובה, בדוק חיבור לאינטרנט - שגיאת אחזור תפריט - אין מפתח API בהגדרות האפליקציה - אין כתובת API בהגדרות האפליקציה - אין כתובת אתר תצורה בהגדרות האפליקציה + אין מפתח API בהגדרות האפליקציה + אין כתובת API בהגדרות האפליקציה + אין כתובת אתר תצורה בהגדרות האפליקציה קריאות API מהירות מדי. נא להאט את הבקשות שלך. כתובת האתר לא נמצאה. שגיאה פוטנציאלית של כתובת ה-API בהגדרות. כתובת האתר לא נמצאה. שגיאת כתובת אתר פוטנציאלית של תצורה בהגדרות. + לא הוחזר JSON מבקשת HTTP. בקשת HTTP החזירה קוד שגיאה = כתובת ה-API לא חייבת לכלול לוכסן אחורי '/' - מביא את תצורת התפריט... - החלק ימינה כדי לצאת\nהקש כדי להישאר - לחץ על חזרה ליציאה\nEnter כדי להישאר + זמין + בודק... + אינו זמין + לא מוגדר + תַפרִיט מפתח API עבור HomeAssistant. אסימון גישה ארוך-חיים. @@ -49,4 +51,5 @@ לאחר זמן זה (בשניות), תיבת דו-שיח לאישור פעולה נסגרת אוטומטית והפעולה מבוטלת. הגדר ל-0 כדי לבטל את הזמן הקצוב. ייצוג סוגים עם סמלים (כבוי) או עם תוויות (מופעל). יישור תפריט שמאלה (כבוי) או ימינה (מופעל). + (יישומון בלבד) הפעל אוטומטית את האפליקציה מהווידג'ט מבלי לחכות להקשה. \ No newline at end of file diff --git a/resources-hrv/strings/strings.xml b/resources-hrv/strings/strings.xml index 42219f1..79ac32c 100644 --- a/resources-hrv/strings/strings.xml +++ b/resources-hrv/strings/strings.xml @@ -25,21 +25,23 @@ Dodirnite Jelovnik Naravno? - Nema telefonske veze + Nema telefonske veze Nema internetske veze Nema odgovora, provjerite internetsku vezu - Pogreška dohvaćanja izbornika - Nema API ključa u postavkama aplikacije - Nema API URL-a u postavkama aplikacije - Nema konfiguracijskog URL-a u postavkama aplikacije + Nema API ključa u postavkama aplikacije + Nema API URL-a u postavkama aplikacije + Nema konfiguracijskog URL-a u postavkama aplikacije API pozivi su prebrzi. Molimo usporite svoje zahtjeve. URL nije pronađen. Potencijalna pogreška API URL-a u postavkama. URL nije pronađen. Potencijalna pogreška URL-a konfiguracije u postavkama. + HTTP zahtjev nije vratio JSON. HTTP zahtjev vratio je kod greške = API URL ne smije imati kosu crtu na kraju '/' - Dohvaćanje konfiguracije izbornika.. - Prijeđite prstom udesno za izlaz\nDodirnite za ostanak - Pritisnite Natrag za izlaz\nUnesite za ostanak + Dostupno + Provjera... + Nedostupan + Nekonfigurirano + Jelovnik API ključ za HomeAssistant. Dugotrajni pristupni token. @@ -49,4 +51,5 @@ Nakon tog vremena (u sekundama), dijaloški okvir za potvrdu radnje automatski se zatvara i radnja se poništava. Postavite na 0 da onemogućite vremensko ograničenje. Predstavljanje tipova ikonama (isključeno) ili oznakama (uključeno). Lijevo (isključeno) ili desno (uključeno) poravnanje izbornika. + (Samo widget) Automatski pokrenite aplikaciju iz widgeta bez čekanja na dodir. \ No newline at end of file diff --git a/resources-hun/strings/strings.xml b/resources-hun/strings/strings.xml index 217e91d..22da591 100644 --- a/resources-hun/strings/strings.xml +++ b/resources-hun/strings/strings.xml @@ -25,21 +25,23 @@ Koppintson a Menü Biztos? - Nincs telefonkapcsolat + Nincs telefonkapcsolat Nincs internetkapcsolat Nincs válasz, ellenőrizze az internetkapcsolatot - Menü Lekérési hiba - Nincs API kulcs az alkalmazás beállításaiban - Nincs API URL az alkalmazás beállításai között - Nincs konfigurációs URL az alkalmazás beállításai között + Nincs API kulcs az alkalmazás beállításaiban + Nincs API URL az alkalmazás beállításai között + Nincs konfigurációs URL az alkalmazás beállításai között Az API-hívások túl gyorsak. Kérjük, lassítsa a kérések teljesítését. Az URL nem található. Lehetséges API URL hiba a beállításokban. Az URL nem található. Lehetséges konfigurációs URL hiba a beállításokban. + A HTTP-kérésből nem érkezett vissza JSON. A HTTP-kérés = hibakódot adott vissza Az API URL-ben nem lehet perjel a „/” - Menükonfiguráció lekérése... - Csúsztassa jobbra a kilépéshez\nKoppintson a Maradáshoz - Nyomja meg a Vissza gombot a kilépéshez\nEnter a Maradáshoz + Elérhető + Ellenőrzés... + Nem érhető el + Nincs konfigurálva + Menü API-kulcs a HomeAssistant számára. Hosszú életű hozzáférési token. @@ -49,4 +51,5 @@ Ezen idő letelte után (másodpercben) a művelet megerősítő párbeszédablakja automatikusan bezárul, és a művelet megszakad. Állítsa 0-ra az időtúllépés letiltásához. A típusokat ikonokkal (kikapcsolva) vagy címkékkel (bekapcsolva) ábrázolja. Balra (ki) vagy Jobbra (be) Menüigazítás. + (Csak widget) Az alkalmazás automatikus indítása a widgetről anélkül, hogy egy érintésre várna. \ No newline at end of file diff --git a/resources-icons-18/group_type.svg b/resources-icons-18/group_type.svg index 112af83..b0301c3 100644 --- a/resources-icons-18/group_type.svg +++ b/resources-icons-18/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-21/group_type.svg b/resources-icons-21/group_type.svg index df98500..53d262a 100644 --- a/resources-icons-21/group_type.svg +++ b/resources-icons-21/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-24/group_type.svg b/resources-icons-24/group_type.svg index c00a567..6ae1634 100644 --- a/resources-icons-24/group_type.svg +++ b/resources-icons-24/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-26/group_type.svg b/resources-icons-26/group_type.svg index 42cd60b..c120aba 100644 --- a/resources-icons-26/group_type.svg +++ b/resources-icons-26/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-28/group_type.svg b/resources-icons-28/group_type.svg index 6275c28..fd6eb11 100644 --- a/resources-icons-28/group_type.svg +++ b/resources-icons-28/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-30/group_type.svg b/resources-icons-30/group_type.svg index 0011419..c36b512 100644 --- a/resources-icons-30/group_type.svg +++ b/resources-icons-30/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-32/group_type.svg b/resources-icons-32/group_type.svg index ba31d0d..b1199a8 100644 --- a/resources-icons-32/group_type.svg +++ b/resources-icons-32/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-38/group_type.svg b/resources-icons-38/group_type.svg index 463d1f3..0dd07c3 100644 --- a/resources-icons-38/group_type.svg +++ b/resources-icons-38/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-42/group_type.svg b/resources-icons-42/group_type.svg index 2ee5924..93b4995 100644 --- a/resources-icons-42/group_type.svg +++ b/resources-icons-42/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-46/group_type.svg b/resources-icons-46/group_type.svg index 6972e86..26c6b42 100644 --- a/resources-icons-46/group_type.svg +++ b/resources-icons-46/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-icons-48/group_type.svg b/resources-icons-48/group_type.svg index 3330af1..6ab40e7 100644 --- a/resources-icons-48/group_type.svg +++ b/resources-icons-48/group_type.svg @@ -1 +1,8 @@ - + + + + + + + + diff --git a/resources-icons-53/group_type.svg b/resources-icons-53/group_type.svg index 15f81e0..42a64e1 100644 --- a/resources-icons-53/group_type.svg +++ b/resources-icons-53/group_type.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/resources-ind/strings/strings.xml b/resources-ind/strings/strings.xml index c6d8061..0a8591d 100644 --- a/resources-ind/strings/strings.xml +++ b/resources-ind/strings/strings.xml @@ -25,21 +25,23 @@ Mengetuk Menu Tentu? - Tidak ada koneksi Telepon + Tidak ada koneksi Telepon Tidak ada koneksi internet Tidak Ada Respon, periksa koneksi Internet - Kesalahan Pengambilan Menu - Tidak ada kunci API di pengaturan aplikasi - Tidak ada URL API di pengaturan aplikasi - Tidak ada URL konfigurasi dalam pengaturan aplikasi + Tidak ada kunci API di pengaturan aplikasi + Tidak ada URL API di pengaturan aplikasi + Tidak ada URL konfigurasi di pengaturan aplikasi Panggilan API terlalu cepat. Harap memperlambat permintaan Anda. URL tidak ditemukan. Potensi kesalahan URL API dalam pengaturan. URL tidak ditemukan. Potensi kesalahan URL Konfigurasi dalam pengaturan. + Tidak ada JSON yang dikembalikan dari permintaan HTTP. Permintaan HTTP mengembalikan kode kesalahan = URL API tidak boleh memiliki garis miring '/' - Mengambil Konfigurasi Menu.. - Geser ke Kanan untuk Keluar\nKetuk untuk Tetap - Tekan Kembali untuk Keluar\nMasuk untuk Tetap + Tersedia + Memeriksa... + Tidak tersedia + Tidak dikonfigurasi + Menu Kunci API untuk HomeAssistant. Token Akses Berumur Panjang. @@ -49,4 +51,5 @@ Setelah waktu ini (dalam detik), dialog konfirmasi untuk suatu tindakan secara otomatis ditutup dan tindakan tersebut dibatalkan. Setel ke 0 untuk menonaktifkan batas waktu. Mewakili tipe dengan ikon (mati) atau dengan label (aktif). Penyelarasan Menu Kiri (mati) atau Kanan (hidup). + (Khusus widget) Secara otomatis memulai aplikasi dari widget tanpa menunggu ketukan. \ No newline at end of file diff --git a/resources-ita/strings/strings.xml b/resources-ita/strings/strings.xml index e782445..1c02546 100644 --- a/resources-ita/strings/strings.xml +++ b/resources-ita/strings/strings.xml @@ -25,21 +25,23 @@ Rubinetto Menù Sicuro? - Nessuna connessione telefonica + Nessuna connessione telefonica Nessuna connessione internet Nessuna risposta, controlla la connessione Internet - Errore di recupero del menu - Nessuna chiave API nelle impostazioni dell'applicazione - Nessun URL API nelle impostazioni dell'applicazione - Nessun URL di configurazione nelle impostazioni dell'applicazione + Nessuna chiave API nelle impostazioni dell'applicazione + Nessun URL API nelle impostazioni dell'applicazione + Nessun URL di configurazione nelle impostazioni dell'applicazione Chiamate API troppo rapide. Per favore rallenta le tue richieste. URL non trovato. Potenziale errore URL API nelle impostazioni. URL non trovato. Potenziale errore dell'URL di configurazione nelle impostazioni. + Nessun JSON restituito dalla richiesta HTTP. La richiesta HTTP ha restituito il codice di errore = L'URL dell'API non deve avere una barra finale "/" - Recupero configurazione menu.. - Scorri verso destra per uscire\nTocca per restare - Premi Indietro per uscire\nInvio per restare + Disponibile + Controllo... + Non disponibile + Non configurato + Menù Chiave API per HomeAssistant. Token di accesso di lunga durata. @@ -49,4 +51,5 @@ Trascorso questo tempo (in secondi), una finestra di dialogo di conferma per un'azione viene chiusa automaticamente e l'azione viene annullata. Impostare su 0 per disabilitare il timeout. Rappresentazione dei tipi con icone (disattivata) o con etichette (attivata). Allineamento del menu a sinistra (spento) o a destra (acceso). + (Solo widget) Avvia automaticamente l'applicazione dal widget senza attendere un tocco. \ No newline at end of file diff --git a/resources-jpn/strings/strings.xml b/resources-jpn/strings/strings.xml index 1930e08..da834ac 100644 --- a/resources-jpn/strings/strings.xml +++ b/resources-jpn/strings/strings.xml @@ -25,21 +25,23 @@ タップ メニュー もちろん? - 電話が接続されていません + 電話が接続されていません インターネット接続なし 応答がありません。インターネット接続を確認してください - メニューフェッチエラー - アプリケーション設定に API キーがありません - アプリケーション設定に API URL がありません - アプリケーション設定に構成 URL がありません + アプリケーション設定に API キーがありません + アプリケーション設定に API URL がありません + アプリケーション設定に構成 URL がありません API 呼び出しが速すぎます。リクエストは遅くしてください。 URLが見つかりません。設定における API URL エラーの可能性があります。 URLが見つかりません。設定内の構成 URL エラーの可能性があります。 + HTTP リクエストから JSON が返されませんでした。 HTTP リクエストがエラー コードを返しました = API URL の末尾にスラッシュ「/」を含めることはできません - メニュー構成を取得しています。 - 右にスワイプして終了\nタップしてそのまま残ります - 終了するには戻るキーを押してください\n続行するには Enter キーを押してください + 利用可能 + チェック中... + 利用不可 + 未構成 + メニュー ホームアシスタントの API キー。 有効期間の長いアクセス トークン。 @@ -49,4 +51,5 @@ この時間 (秒単位) が経過すると、アクションの確認ダイアログが自動的に閉じられ、アクションがキャンセルされます。タイムアウトを無効にするには、0 に設定します。 タイプをアイコン (オフ) またはラベル (オン) で表します。 左 (オフ) または右 (オン) メニューの配置。 + (ウィジェットのみ)タップを待たずにウィジェットからアプリを自動起動します。 \ No newline at end of file diff --git a/resources-kor/strings/strings.xml b/resources-kor/strings/strings.xml index cfdc285..e54002c 100644 --- a/resources-kor/strings/strings.xml +++ b/resources-kor/strings/strings.xml @@ -25,21 +25,23 @@ 수도꼭지 메뉴 확신하는? - 전화 연결 없음 + 전화 연결 없음 인터넷에 연결되지 않음 응답이 없습니다. 인터넷 연결을 확인하세요. - 메뉴 가져오기 오류 - 애플리케이션 설정에 API 키가 없습니다. - 애플리케이션 설정에 API URL이 없습니다. - 애플리케이션 설정에 구성 URL이 없습니다. + 애플리케이션 설정에 API 키가 없습니다. + 애플리케이션 설정에 API URL이 없습니다. + 애플리케이션 설정에 구성 URL이 없습니다. API 호출이 너무 빠릅니다. 요청 속도를 늦추시기 바랍니다. URL을 찾을 수 없습니다. 설정에 잠재적인 API URL 오류가 있습니다. URL을 찾을 수 없습니다. 설정에 잠재적인 구성 URL 오류가 있습니다. + HTTP 요청에서 JSON이 반환되지 않았습니다. HTTP 요청이 오류 코드를 반환했습니다 = API URL에는 후행 슬래시 '/'가 없어야 합니다. - 메뉴 구성을 가져오는 중.. - 종료하려면 오른쪽으로 스와이프하세요.\n계속하려면 탭하세요. - 종료하려면 뒤로 버튼을 누르세요.\n계속하려면 입력하세요. + 사용 가능 + 확인 중... + 없는 + 구성되지 않음 + 메뉴 HomeAssistant용 API 키. 장기 액세스 토큰. @@ -49,4 +51,5 @@ 이 시간(초)이 지나면 작업에 대한 확인 대화 상자가 자동으로 닫히고 작업이 취소됩니다. 시간 초과를 비활성화하려면 0으로 설정합니다. 아이콘(끄기) 또는 레이블(켜기)로 유형을 나타냅니다. 왼쪽(끄기) 또는 오른쪽(켜기) 메뉴 정렬. + (위젯만 해당) 탭을 기다리지 않고 위젯에서 애플리케이션을 자동으로 시작합니다. \ No newline at end of file diff --git a/resources-launcher-33-33/drawables.xml b/resources-launcher-33-33/drawables.xml new file mode 100644 index 0000000..b9e68f9 --- /dev/null +++ b/resources-launcher-33-33/drawables.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/resources-launcher-33-33/launcher.png b/resources-launcher-33-33/launcher.png new file mode 100644 index 0000000..8dfd712 Binary files /dev/null and b/resources-launcher-33-33/launcher.png differ diff --git a/resources-lav/strings/strings.xml b/resources-lav/strings/strings.xml index 8e43a6f..4b5088b 100644 --- a/resources-lav/strings/strings.xml +++ b/resources-lav/strings/strings.xml @@ -25,21 +25,23 @@ Krāns Izvēlne Protams? - Nav tālruņa savienojuma + Nav tālruņa savienojuma Nav interneta savienojuma Nav atbildes, pārbaudiet interneta savienojumu - Izvēlnes ielādes kļūda - Lietojumprogrammas iestatījumos nav API atslēgas - Lietojumprogrammas iestatījumos nav API URL - Lietojumprogrammas iestatījumos nav konfigurācijas URL + Lietojumprogrammas iestatījumos nav API atslēgas + Lietojumprogrammas iestatījumos nav API URL + Lietojumprogrammas iestatījumos nav konfigurācijas URL API izsaukumi ir pārāk ātri. Lūdzu, palēniniet pieprasījumu izpildi. URL nav atrasts. Iespējama API URL kļūda iestatījumos. URL nav atrasts. Iespējama konfigurācijas URL kļūda iestatījumos. + No HTTP pieprasījuma netika atgriezts neviens JSON fails. HTTP pieprasījums atgrieza kļūdas kodu = API URL beigās nedrīkst būt slīpsvītra “/” - Notiek izvēlnes konfigurācijas iegūšana... - Lai izietu, velciet pa labi\nPieskarieties, lai paliktu - Noklikšķiniet uz Atpakaļ, lai izietu.\nIevadiet, lai paliktu + Pieejams + Notiek pārbaude... + Nav pieejams + Nav konfigurēts + Izvēlne API atslēga Home Assistant. Ilgmūžīgs piekļuves marķieris. @@ -49,4 +51,5 @@ Pēc šī laika (sekundēs) tiek automātiski aizvērts darbības apstiprinājuma dialoglodziņš un darbība tiek atcelta. Iestatiet uz 0, lai atspējotu taimautu. Apzīmē veidus ar ikonām (izslēgts) vai ar etiķetēm (ieslēgts). Kreisā (izslēgta) vai labā (ieslēgta) izvēlnes līdzināšana. + (tikai logrīkam) Automātiski startējiet lietojumprogrammu no logrīka, negaidot pieskārienu. \ No newline at end of file diff --git a/resources-lit/strings/strings.xml b/resources-lit/strings/strings.xml index 3343734..ab3a3e2 100644 --- a/resources-lit/strings/strings.xml +++ b/resources-lit/strings/strings.xml @@ -25,21 +25,23 @@ Bakstelėkite Meniu Žinoma? - Nėra telefono ryšio + Nėra telefono ryšio Nėra interneto ryšio Neatsako, patikrinkite interneto ryšį - Meniu gavimo klaida - Programos nustatymuose nėra API rakto - Programos nustatymuose nėra API URL - Programos nustatymuose nėra konfigūracijos URL + Programos nustatymuose nėra API rakto + Programos nustatymuose nėra API URL + Programos nustatymuose nėra konfigūracijos URL API skambučiai per greiti. Sulėtinkite prašymų vykdymą. URL nerastas. Galima API URL klaida nustatymuose. URL nerastas. Galima konfigūracijos URL klaida nustatymuose. + Joks JSON negrąžintas iš HTTP užklausos. HTTP užklausa grąžino klaidos kodą = API URL pabaigoje negali būti pasvirojo brūkšnio „/“ - Gaunama meniu konfigūracija... - Norėdami išeiti, braukite į dešinę\nPalieskite, kad pasiliktumėte - Norėdami išeiti, paspauskite „Atgal“.\nĮveskite, kad pasiliktumėte + Yra + Tikrinama... + Nepasiekiamas + Nesukonfigūruotas + Meniu API raktas, skirtas HomeAssistant. Ilgalaikis prieigos raktas. @@ -49,4 +51,5 @@ Praėjus šiam laikui (sekundėmis), veiksmo patvirtinimo dialogo langas automatiškai uždaromas ir veiksmas atšaukiamas. Nustatykite 0, kad išjungtumėte skirtąjį laiką. Tipai su piktogramomis (išjungta) arba etiketėmis (įjungta). Kairysis (išjungtas) arba dešinysis (įjungtas) meniu lygiavimas. + (Tik valdiklis) Automatiškai paleiskite programą iš valdiklio, nelaukdami palietimo. \ No newline at end of file diff --git a/resources-nob/strings/strings.xml b/resources-nob/strings/strings.xml index b18c833..044215f 100644 --- a/resources-nob/strings/strings.xml +++ b/resources-nob/strings/strings.xml @@ -25,21 +25,23 @@ Trykk på Meny Sikker? - Ingen telefonforbindelse + Ingen telefonforbindelse Ingen Internett-tilkobling Ingen svar, sjekk Internett-tilkoblingen - Menyhentingsfeil - Ingen API-nøkkel i applikasjonsinnstillingene - Ingen API-URL i applikasjonsinnstillingene - Ingen konfigurasjons-URL i applikasjonsinnstillingene + Ingen API-nøkkel i applikasjonsinnstillingene + Ingen API-URL i applikasjonsinnstillingene + Ingen konfigurasjons-URL i applikasjonsinnstillingene API-kall for raske. Vennligst senke forespørslene dine. Finner ikke URL. Potensiell API URL-feil i innstillingene. Finner ikke URL. Potensiell konfigurasjons-URL-feil i innstillingene. + Ingen JSON returnert fra HTTP-forespørsel. HTTP-forespørsel returnerte feilkode = API URL må ikke ha en etterfølgende skråstrek '/' - Henter menykonfigurasjon.. - Sveip til høyre for å avslutte\nTrykk for å bli - Trykk Tilbake for å avslutte\nEnter for å bli + Tilgjengelig + Sjekker... + Utilgjengelig + Ukonfigurert + Meny API-nøkkel for HomeAssistant. Langlevd tilgangstoken. @@ -49,4 +51,5 @@ Etter denne tiden (i sekunder), lukkes en bekreftelsesdialog for en handling automatisk og handlingen avbrytes. Sett til 0 for å deaktivere tidsavbruddet. Representerer typer med ikoner (av) eller med etiketter (på). Venstre (av) eller Høyre (på) Menyjustering. + (Kun widget) Start applikasjonen automatisk fra widgeten uten å vente på et trykk. \ No newline at end of file diff --git a/resources-pol/strings/strings.xml b/resources-pol/strings/strings.xml index bc5e094..adf1b27 100644 --- a/resources-pol/strings/strings.xml +++ b/resources-pol/strings/strings.xml @@ -25,21 +25,23 @@ Uzyskiwać Menu Jasne? - Brak połączenia telefonicznego + Brak połączenia telefonicznego Brak połączenia z internetem Brak odpowiedzi, sprawdź połączenie internetowe - Błąd pobierania menu - Brak klucza API w ustawieniach aplikacji - Brak adresu API w ustawieniach aplikacji - Brak adresu URL konfiguracji w ustawieniach aplikacji + Brak klucza API w ustawieniach aplikacji + Brak adresu API w ustawieniach aplikacji + Brak adresu URL konfiguracji w ustawieniach aplikacji Wywołania API są zbyt szybkie. Proszę spowolnić swoje żądania. Nie znaleziono adresu URL. Potencjalny błąd adresu URL interfejsu API w ustawieniach. Nie znaleziono adresu URL. Potencjalny błąd adresu URL konfiguracji w ustawieniach. + Z żądania HTTP nie zwrócono żadnego kodu JSON. Żądanie HTTP zwróciło kod błędu = Adres URL interfejsu API nie może zawierać końcowego ukośnika „/” - Pobieranie konfiguracji menu.. - Przesuń w prawo, aby wyjść\nDotknij, aby pozostać - Naciśnij Wstecz, aby wyjść\nEnter, aby pozostać + Dostępny + Kontrola... + Niedostępne + Nieskonfigurowane + Menu Klucz API dla HomeAssistant. Długowieczny token dostępu. @@ -49,4 +51,5 @@ Po tym czasie (w sekundach) okno dialogowe z potwierdzeniem akcji zamyka się automatycznie, a akcja zostaje anulowana. Ustaw na 0, aby wyłączyć limit czasu. Reprezentowanie typów za pomocą ikon (wyłączone) lub etykiet (włączone). Wyrównanie menu do lewej (wyłączone) lub do prawej (włączone). + (Tylko widget) Automatycznie uruchamiaj aplikację z widgetu, bez czekania na dotknięcie. \ No newline at end of file diff --git a/resources-por/strings/strings.xml b/resources-por/strings/strings.xml index ad7bd0c..620fc13 100644 --- a/resources-por/strings/strings.xml +++ b/resources-por/strings/strings.xml @@ -25,21 +25,23 @@ Tocar Cardápio Claro? - Sem conexão telefônica + Sem conexão telefônica Sem conexão com a Internet Sem resposta, verifique a conexão com a Internet - Erro ao buscar menu - Nenhuma chave de API nas configurações do aplicativo - Nenhum URL de API nas configurações do aplicativo - Nenhum URL de configuração nas configurações do aplicativo + Nenhuma chave de API nas configurações do aplicativo + Nenhum URL de API nas configurações do aplicativo + Nenhum URL de configuração nas configurações do aplicativo Chamadas de API muito rápidas. Por favor, diminua a velocidade de seus pedidos. URL não encontrado. Possível erro de URL da API nas configurações. URL não encontrado. Possível erro de URL de configuração nas configurações. + Nenhum JSON foi retornado da solicitação HTTP. Solicitação HTTP retornou código de erro = O URL da API não deve ter uma barra final '/' - Buscando configuração do menu.. - Deslize para a direita para sair\nToque para permanecer - Volte para sair\nEntre para permanecer + Disponível + Verificando... + Indisponível + Não configurado + Cardápio Chave de API para HomeAssistant. Token de acesso de longa duração. @@ -49,4 +51,5 @@ Após esse tempo (em segundos), uma caixa de diálogo de confirmação de uma ação é automaticamente fechada e a ação é cancelada. Defina como 0 para desativar o tempo limite. Representando tipos com ícones (desligado) ou com rótulos (ligado). Alinhamento do menu à esquerda (desligado) ou à direita (ligado). + (Somente widget) Inicie automaticamente o aplicativo a partir do widget sem esperar por um toque. \ No newline at end of file diff --git a/resources-ron/strings/strings.xml b/resources-ron/strings/strings.xml index b85e612..2327d7f 100644 --- a/resources-ron/strings/strings.xml +++ b/resources-ron/strings/strings.xml @@ -25,21 +25,23 @@ Atingeți Meniul Sigur? - Fără conexiune telefonică + Fără conexiune telefonică Fără conexiune internet Niciun răspuns, verificați conexiunea la internet - Eroare de preluare a meniului - Nicio cheie API în setările aplicației - Nicio adresă URL API în setările aplicației - Nicio adresă URL de configurare în setările aplicației + Nicio cheie API în setările aplicației + Nicio adresă URL API în setările aplicației + Nicio adresă URL de configurare în setările aplicației Apeluri API prea rapide. Vă rugăm să vă încetiniți solicitările. Adresa URL nu a fost găsită. Potențială eroare URL API în setări. Adresa URL nu a fost găsită. Potențială eroare URL de configurare în setări. + Niciun JSON nu a fost returnat de la solicitarea HTTP. Solicitarea HTTP a returnat codul de eroare = Adresa URL API nu trebuie să aibă o bară oblică „/” - Se preiau configurarea meniului... - Glisați spre dreapta pentru a ieși\nAtingeți pentru a rămâne - Apăsați Înapoi pentru a ieși\nIntră pentru a rămâne + Disponibil + Control... + Indisponibil + Neconfigurat + Meniul Cheie API pentru HomeAssistant. Token de acces cu viață lungă. @@ -49,4 +51,5 @@ După acest timp (în secunde), un dialog de confirmare pentru o acțiune este închis automat și acțiunea este anulată. Setați la 0 pentru a dezactiva timeout-ul. Reprezentarea tipurilor cu pictograme (dezactivate) sau cu etichete (activate). Alinierea meniului la stânga (dezactivată) sau la dreapta (activată). + (Numai widget) Porniți automat aplicația din widget fără a aștepta o atingere. \ No newline at end of file diff --git a/resources-slo/strings/strings.xml b/resources-slo/strings/strings.xml index 2e024ac..4e2c3f0 100644 --- a/resources-slo/strings/strings.xml +++ b/resources-slo/strings/strings.xml @@ -25,21 +25,23 @@ Klepnite Ponuka Samozrejme? - Žiadne telefónne spojenie + Žiadne telefónne spojenie Žiadne internetové pripojenie Žiadna odpoveď, skontrolujte internetové pripojenie - Chyba načítania ponuky - V nastaveniach aplikácie nie je žiadny kľúč API - V nastaveniach aplikácie nie je žiadna adresa URL rozhrania API - V nastaveniach aplikácie nie je žiadna konfiguračná URL + V nastaveniach aplikácie nie je žiadny kľúč API + V nastaveniach aplikácie nie je žiadna adresa URL rozhrania API + V nastaveniach aplikácie nie je žiadna konfiguračná URL Volania API sú príliš rýchle. Spomaľte svoje požiadavky. Adresa URL sa nenašla. Potenciálna chyba webovej adresy rozhrania API v nastaveniach. Adresa URL sa nenašla. Potenciálna chyba konfiguračnej adresy URL v nastaveniach. + Z požiadavky HTTP sa nevrátil žiadny JSON. Požiadavka HTTP vrátila kód chyby = Adresa URL rozhrania API nesmie obsahovať koncovú lomku „/“ - Načítava sa konfigurácia ponuky... - Ukončite prejdením prstom doprava\nKlepnutím zostanete - Stlačte Späť na ukončenie\nVstúpte a zostaňte + Dostupné + Prebieha kontrola... + nedostupné + Nekonfigurované + Ponuka Kľúč API pre HomeAssistant. Prístupový token s dlhou životnosťou. @@ -49,4 +51,5 @@ Po tomto čase (v sekundách) sa dialógové okno s potvrdením akcie automaticky zatvorí a akcia sa zruší. Ak chcete časový limit deaktivovať, nastavte na 0. Typy predstavujú ikony (vypnuté) alebo štítky (zapnuté). Zarovnanie ponuky vľavo (vypnuté) alebo vpravo (zapnuté). + (Len miniaplikácia) Automaticky spustite aplikáciu z miniaplikácie bez čakania na klepnutie. \ No newline at end of file diff --git a/resources-slv/strings/strings.xml b/resources-slv/strings/strings.xml index 59f24dc..512dc90 100644 --- a/resources-slv/strings/strings.xml +++ b/resources-slv/strings/strings.xml @@ -25,21 +25,23 @@ Tapnite meni Seveda? - Ni telefonske povezave + Ni telefonske povezave Ni internetne povezave Ni odgovora, preverite internetno povezavo - Napaka pri pridobivanju menija - V nastavitvah aplikacije ni ključa API - V nastavitvah aplikacije ni URL-ja API-ja - V nastavitvah aplikacije ni konfiguracijskega URL-ja + V nastavitvah aplikacije ni ključa API + V nastavitvah aplikacije ni URL-ja API-ja + V nastavitvah aplikacije ni konfiguracijskega URL-ja API klici so prehitri. Prosim, upočasnite svoje zahteve. URL-ja ni bilo mogoče najti. Morebitna napaka URL-ja API-ja v nastavitvah. URL-ja ni bilo mogoče najti. Morebitna napaka URL-ja konfiguracije v nastavitvah. + Zahteva HTTP ni vrnila JSON. Zahteva HTTP je vrnila kodo napake = URL API-ja ne sme imeti končne poševnice '/' - Pridobivanje konfiguracije menija.. - Povlecite v desno za izhod\nDotaknite se, da ostanete - Pritisnite Nazaj za izhod\nVstopite, da ostanete + Na voljo + Preverjanje ... + Ni na voljo + Nekonfigurirano + meni API ključ za HomeAssistant. Dolgoživ dostopni žeton. @@ -49,4 +51,5 @@ Po tem času (v sekundah) se potrditveno pogovorno okno za dejanje samodejno zapre in dejanje je preklicano. Nastavite na 0, da onemogočite časovno omejitev. Predstavljanje tipov z ikonami (izklopljeno) ali z oznakami (vklopljeno). Leva (izklopljena) ali desna (vklopljena) poravnava menija. + (Samo pripomoček) Samodejno zaženite aplikacijo iz pripomočka, ne da bi čakali na dotik. \ No newline at end of file diff --git a/resources-spa/strings/strings.xml b/resources-spa/strings/strings.xml index b1a7bd2..5961adb 100644 --- a/resources-spa/strings/strings.xml +++ b/resources-spa/strings/strings.xml @@ -25,21 +25,23 @@ Grifo Menú ¿Seguro? - Sin conexión telefónica + Sin conexión telefónica Sin conexión a Internet No hay respuesta, verifique la conexión a Internet - Error de recuperación del menú - Sin clave API en la configuración de la aplicación - No hay URL de API en la configuración de la aplicación - No hay URL de configuración en la configuración de la aplicación. + Sin clave API en la configuración de la aplicación + No hay URL de API en la configuración de la aplicación + No hay URL de configuración en la configuración de la aplicación. Llamadas API demasiado rápidas. Por favor, ralentice sus solicitudes. URL no encontrada. Posible error de URL de API en la configuración. URL no encontrada. Posible error de URL de configuración en la configuración. + No se devolvió ningún JSON de la solicitud HTTP. La solicitud HTTP devolvió el código de error = La URL de API no debe tener una barra diagonal '/' - Obteniendo configuración del menú.. - Desliza hacia la derecha para salir\nToca para permanecer - Presione Atrás para salir\nEntrar para permanecer + Disponible + Comprobación... + Indisponible + Desconfigurado + Menú Clave API para HomeAssistant. Token de acceso de larga duración. @@ -49,4 +51,5 @@ Después de este tiempo (en segundos), se cierra automáticamente un cuadro de diálogo de confirmación de una acción y se cancela la acción. Establezca en 0 para desactivar el tiempo de espera. Representando tipos con iconos (apagados) o con etiquetas (encendido). Alineación del menú izquierda (desactivada) o derecha (activada). + (Solo widget) Inicia automáticamente la aplicación desde el widget sin esperar un toque. \ No newline at end of file diff --git a/resources-swe/strings/strings.xml b/resources-swe/strings/strings.xml index ee7550d..f5eb436 100644 --- a/resources-swe/strings/strings.xml +++ b/resources-swe/strings/strings.xml @@ -25,28 +25,31 @@ Knacka Meny Säker? - Ingen telefonanslutning + Ingen telefonanslutning Ingen internetanslutning Inget svar, kontrollera internetanslutningen - Menyhämtningsfel - Ingen API-nyckel i applikationsinställningarna - Ingen API-URL i applikationsinställningarna - Ingen konfigurations-URL i programinställningarna + Ingen API-nyckel i applikationsinställningarna + Ingen API-URL i applikationsinställningarna + Ingen konfigurations-URL i programinställningarna API-anrop för snabba. Vänligen sakta ner dina förfrågningar. Webbadressen hittades inte. Potentiellt API-URL-fel i inställningarna. Webbadressen hittades inte. Potentiellt konfigurations-URL-fel i inställningarna. + Ingen JSON returnerades från HTTP-begäran. HTTP-begäran returnerade felkod = API-URL får inte ha ett snedstreck '/' - Hämtar menykonfiguration.. - Svep åt höger för att avsluta\nKnacka för att stanna - Tryck på Tillbaka för att avsluta\nEnter för att stanna + Tillgängliga + Kontroll... + Inte tillgänglig + Okonfigurerad + Meny API-nyckel för HomeAssistant. Långlivad åtkomsttoken. URL för HomeAssistant API. URL för menykonfiguration (JSON). - Timeout på sekunder. Avsluta programmet efter denna period av inaktivitet för att spara enhetens batteri. + Timeout i sekunder. Avsluta programmet efter denna period av inaktivitet för att spara enhetens batteri. Efter denna tid (i sekunder) stängs en bekräftelsedialog för en åtgärd automatiskt och åtgärden avbryts. Ställ in på 0 för att inaktivera timeout. Representerar typer med ikoner (av) eller med etiketter (på). Vänster (av) eller höger (på) menyjustering. + (Endast widget) Starta programmet automatiskt från widgeten utan att vänta på ett tryck. \ No newline at end of file diff --git a/resources-tha/strings/strings.xml b/resources-tha/strings/strings.xml index 4f10874..7ebd76a 100644 --- a/resources-tha/strings/strings.xml +++ b/resources-tha/strings/strings.xml @@ -25,21 +25,23 @@ แตะ เมนู แน่นอน? - ไม่มีการเชื่อมต่อโทรศัพท์ + ไม่มีการเชื่อมต่อโทรศัพท์ ไม่มีการเชื่อมต่ออินเทอร์เน็ต ไม่มีการตอบสนอง ตรวจสอบการเชื่อมต่ออินเทอร์เน็ต - เมนูดึงข้อมูลผิดพลาด - ไม่มีคีย์ API ในการตั้งค่าแอปพลิเคชัน - ไม่มี URL API ในการตั้งค่าแอปพลิเคชัน - ไม่มี URL การกำหนดค่าในการตั้งค่าแอปพลิเคชัน + ไม่มีคีย์ API ในการตั้งค่าแอปพลิเคชัน + ไม่มี URL API ในการตั้งค่าแอปพลิเคชัน + ไม่มี URL การกำหนดค่าในการตั้งค่าแอปพลิเคชัน การเรียก API เร็วเกินไป กรุณาชะลอคำขอของคุณ ไม่พบ URL ข้อผิดพลาด URL API ที่อาจเกิดขึ้นในการตั้งค่า ไม่พบ URL ข้อผิดพลาด URL การกำหนดค่าที่อาจเกิดขึ้นในการตั้งค่า + ไม่มี JSON ที่ส่งคืนจากคำขอ HTTP คำขอ HTTP ส่งคืนรหัสข้อผิดพลาด = URL ของ API ต้องไม่มีเครื่องหมายทับต่อท้าย '/' - กำลังดึงข้อมูลการกำหนดค่าเมนู.. - ปัดไปทางขวาเพื่อออก\nแตะเพื่ออยู่ต่อ - กดกลับเพื่อออก\nเข้าไปเพื่ออยู่ต่อ + มีอยู่ + กำลังตรวจสอบ... + ไม่พร้อมใช้งาน + ไม่ได้กำหนดค่า + เมนู คีย์ API สำหรับ HomeAssistant โทเค็นการเข้าถึงที่มีอายุการใช้งานยาวนาน @@ -49,4 +51,5 @@ หลังจากเวลานี้ (เป็นวินาที) กล่องโต้ตอบการยืนยันสำหรับการดำเนินการจะปิดโดยอัตโนมัติและการดำเนินการจะถูกยกเลิก ตั้งค่าเป็น 0 เพื่อปิดใช้งานการหมดเวลา เป็นตัวแทนประเภทด้วยไอคอน (ปิด) หรือมีป้ายกำกับ (เปิด) การจัดตำแหน่งเมนูซ้าย (ปิด) หรือขวา (เปิด) + (วิดเจ็ตเท่านั้น) เริ่มแอปพลิเคชันโดยอัตโนมัติจากวิดเจ็ตโดยไม่ต้องรอการแตะ \ No newline at end of file diff --git a/resources-tur/strings/strings.xml b/resources-tur/strings/strings.xml index ceb30dc..379ec54 100644 --- a/resources-tur/strings/strings.xml +++ b/resources-tur/strings/strings.xml @@ -25,28 +25,31 @@ Musluk Menü Elbette? - Telefon bağlantısı yok + Telefon bağlantısı yok İnternet bağlantısı yok Yanıt Yok, İnternet bağlantısını kontrol edin - Menü Alma Hatası - Uygulama ayarlarında API anahtarı yok - Uygulama ayarlarında API URL'si yok - Uygulama ayarlarında yapılandırma URL'si yok + Uygulama ayarlarında API anahtarı yok + Uygulama ayarlarında API URL'si yok + Uygulama ayarlarında yapılandırma URL'si yok API çağrıları çok hızlı. Lütfen isteklerinizi yavaşlatın. URL bulunamadı. Ayarlarda olası API URL hatası. URL bulunamadı. Ayarlarda Olası Yapılandırma URL'si hatası. + HTTP isteğinden JSON döndürülmedi. HTTP isteği hata kodunu döndürdü = API URL'sinin sonunda eğik çizgi '/' olmamalıdır - Menü Yapılandırması alınıyor.. - Çıkmak için Sağa Kaydırın\nKalmak için Dokunun - Çıkış için Geri tuşuna basın\nKalmak için Enter'a basın + Mevcut + Kontrol etme... + Kullanım dışı + Yapılandırılmamış + Menü HomeAssistant için API Anahtarı. Uzun Ömürlü Erişim Jetonu. HomeAssistant API'sinin URL'si. Menü yapılandırmasının URL'si (JSON). Saniye cinsinden zaman aşımı. Cihazın pilinden tasarruf etmek için bu süre boyunca işlem yapılmadığında uygulamadan çıkın. - Bu sürenin sonunda (saniye olarak), bir eyleme ilişkin onay iletişim kutusu otomatik olarak kapatılır ve eylem iptal edilir. Zaman aşımını devre dışı bırakmak için 0'a ayarlayın. + Bu sürenin sonunda (saniye cinsinden), bir eyleme ilişkin onay iletişim kutusu otomatik olarak kapatılır ve eylem iptal edilir. Zaman aşımını devre dışı bırakmak için 0'a ayarlayın. Türleri simgelerle (kapalı) veya etiketlerle (açık) temsil etme. Sol (kapalı) veya Sağ (açık) Menü Hizalaması. + (Yalnızca Widget) Dokunmayı beklemeden uygulamayı widget'tan otomatik olarak başlatın. \ No newline at end of file diff --git a/resources-ukr/strings/strings.xml b/resources-ukr/strings/strings.xml index be12986..fe951b2 100644 --- a/resources-ukr/strings/strings.xml +++ b/resources-ukr/strings/strings.xml @@ -25,21 +25,23 @@ Торкніться Меню Зрозуміло? - Немає телефонного зв'язку + Немає телефонного зв'язку Немає підключення до Інтернету Немає відповіді, перевірте підключення до Інтернету - Помилка вибірки меню - У налаштуваннях програми немає ключа API - У налаштуваннях програми немає URL-адреси API - У налаштуваннях програми немає URL-адреси конфігурації + У налаштуваннях програми немає ключа API + У налаштуваннях програми немає URL-адреси API + У налаштуваннях програми немає URL-адреси конфігурації Надто швидкі виклики API. Будь ласка, сповільніть свої запити. URL не знайдено. Потенційна помилка URL-адреси API в налаштуваннях. URL не знайдено. Потенційна помилка URL-адреси конфігурації в налаштуваннях. + Запит HTTP не повертає JSON. Запит HTTP повернув код помилки = URL-адреса API не повинна містити косу риску '/' - Отримання конфігурації меню.. - Проведіть праворуч, щоб вийти\nТоркніться, щоб залишитися - Натисніть «Назад», щоб вийти\nВведіть, щоб залишитися + в наявності + Перевірка... + Недоступний + Неналаштований + Меню Ключ API для HomeAssistant. Довговічний маркер доступу. @@ -49,4 +51,5 @@ Після закінчення цього часу (у секундах) діалогове вікно підтвердження дії автоматично закривається, а дія скасовується. Встановіть 0, щоб вимкнути тайм-аут. Представлення типів піктограмами (вимкнено) або мітками (увімкнено). Ліворуч (вимкнено) або праворуч (увімкнено) вирівнювання меню. + (Лише віджет) Автоматично запускайте програму з віджета, не чекаючи дотику. \ No newline at end of file diff --git a/resources-vie/strings/strings.xml b/resources-vie/strings/strings.xml index ae3a0ae..ca576fa 100644 --- a/resources-vie/strings/strings.xml +++ b/resources-vie/strings/strings.xml @@ -25,21 +25,23 @@ Vỗ nhẹ Thực đơn Chắc chắn? - Không có kết nối điện thoại + Không có kết nối điện thoại Không có kết nối Internet Không có phản hồi, kiểm tra kết nối Internet - Lỗi tìm nạp menu - Không có khóa API trong cài đặt ứng dụng - Không có URL API trong cài đặt ứng dụng - Không có URL cấu hình trong cài đặt ứng dụng + Không có khóa API trong cài đặt ứng dụng + Không có URL API trong cài đặt ứng dụng + Không có URL cấu hình trong cài đặt ứng dụng Cuộc gọi API quá nhanh. Hãy làm chậm yêu cầu của bạn. Không tìm thấy URL. Lỗi URL API tiềm ẩn trong cài đặt. Không tìm thấy URL. Lỗi URL cấu hình tiềm ẩn trong cài đặt. + Không có JSON nào được trả về từ yêu cầu HTTP. Yêu cầu HTTP trả về mã lỗi = URL API không được có dấu gạch chéo ở cuối '/' - Đang tìm nạp cấu hình menu.. - Vuốt sang phải để thoát\nNhấn để ở lại - Nhấn Quay lại để thoát\nNhập để ở lại + Có sẵn + Đang kiểm tra... + Không có sẵn + Chưa được định cấu hình + Thực đơn Khóa API cho HomeAssistant. Mã thông báo truy cập tồn tại lâu dài. @@ -47,6 +49,7 @@ URL cho cấu hình menu (JSON). Thời gian chờ tính bằng giây. Thoát khỏi ứng dụng sau khoảng thời gian không hoạt động này để tiết kiệm pin cho thiết bị. Sau thời gian này (tính bằng giây), hộp thoại xác nhận cho một hành động sẽ tự động đóng và hành động đó sẽ bị hủy. Đặt thành 0 để tắt thời gian chờ. - Thể hiện các loại bằng biểu tượng (tắt) hoặc bằng nhãn (bật). + Biểu diễn các loại bằng biểu tượng (tắt) hoặc bằng nhãn (bật). Căn chỉnh menu Trái (tắt) hoặc Phải (bật). + (Chỉ tiện ích) Tự động khởi động ứng dụng từ tiện ích mà không cần chờ nhấn. \ No newline at end of file diff --git a/resources-zhs/strings/strings.xml b/resources-zhs/strings/strings.xml index 9a200a9..72928d5 100644 --- a/resources-zhs/strings/strings.xml +++ b/resources-zhs/strings/strings.xml @@ -25,21 +25,23 @@ 轻敲 菜单 当然? - 没有电话连接 + 没有电话连接 没有网络连接 无响应,请检查互联网连接 - 菜单获取错误 - 应用程序设置中没有 API 密钥 - 应用程序设置中没有 API URL - 应用程序设置中没有配置 URL + 应用程序设置中没有 API 密钥 + 应用程序设置中没有 API URL + 应用程序设置中没有配置 URL API 调用速度太快。请放慢您的请求。 找不到网址。设置中可能存在 API URL 错误。 找不到网址。设置中可能存在配置 URL 错误。 + HTTP 请求未返回 JSON。 HTTP请求返回错误码= API URL 不得有尾部斜杠“/” - 正在获取菜单配置.. - 向右滑动即可退出\n点击即可停留 - 按回车键退出\n按回车键继续 + 可用的 + 检查... + 不可用 + 未配置 + 菜单 HomeAssistant 的 API 密钥。 长期访问令牌。 @@ -49,4 +51,5 @@ 在此时间(以秒为单位)之后,操作的确认对话框将自动关闭并取消该操作。设置为 0 以禁用超时。 用图标(关闭)或标签(打开)表示类型。 左(关)或右(开)菜单对齐。 + (仅限小部件)从小部件自动启动应用程序,无需等待点击。 \ No newline at end of file diff --git a/resources-zht/strings/strings.xml b/resources-zht/strings/strings.xml index 8703056..3196f55 100644 --- a/resources-zht/strings/strings.xml +++ b/resources-zht/strings/strings.xml @@ -25,21 +25,23 @@ 輕敲 選單 當然? - 沒有電話連接 + 沒有電話連接 沒有網路連線 無響應,請檢查互聯網連接 - 選單取得錯誤 - 應用程式設定中沒有 API 金鑰 - 應用程式設定中沒有 API URL - 應用程式設定中沒有配置 URL + 應用程式設定中沒有 API 金鑰 + 應用程式設定中沒有 API URL + 應用程式設定中沒有配置 URL API 呼叫速度太快。請放慢您的請求。 找不到網址。設定中可能存在 API URL 錯誤。 找不到網址。設定中可能存在配置 URL 錯誤。 + HTTP 請求未傳回 JSON。 HTTP請求回傳錯誤碼= API URL 不得有尾部斜線“/” - 正在取得選單配置.. - 向右滑動即可退出\n點選即可停留 - 按回車鍵退出\n按回車鍵繼續 + 可用的 + 檢查... + 不可用 + 未配置 + 選單 HomeAssistant 的 API 金鑰。 長期訪問令牌。 @@ -49,4 +51,5 @@ 在此時間(以秒為單位)之後,操作的確認對話方塊將自動關閉並取消該操作。設定為 0 以停用逾時。 用圖示(關閉)或標籤(開啟)表示類型。 左(關)或右(開)選單對齊。 + (僅限小部件)從小部件自動啟動應用程序,無需等待點擊。 \ No newline at end of file diff --git a/resources-zsm/strings/strings.xml b/resources-zsm/strings/strings.xml index b4a79b8..d5d5c20 100644 --- a/resources-zsm/strings/strings.xml +++ b/resources-zsm/strings/strings.xml @@ -25,21 +25,23 @@ Ketik Menu pasti? - Tiada sambungan Telefon + Tiada sambungan Telefon Tiada sambungan internet Tiada Respons, semak sambungan Internet - Ralat Pengambilan Menu - Tiada kunci API dalam tetapan aplikasi - Tiada URL API dalam tetapan aplikasi - Tiada URL konfigurasi dalam tetapan aplikasi + Tiada kunci API dalam tetapan aplikasi + Tiada URL API dalam tetapan aplikasi + Tiada URL konfigurasi dalam tetapan aplikasi Panggilan API terlalu pantas. Sila perlahankan permintaan anda. URL tidak ditemui. Ralat URL API yang berpotensi dalam tetapan. URL tidak ditemui. Ralat URL Konfigurasi Potensi dalam tetapan. + Tiada JSON dikembalikan daripada permintaan HTTP. Permintaan HTTP mengembalikan kod ralat = URL API tidak boleh mempunyai garis miring '/' - Mengambil Konfigurasi Menu.. - Leret ke Kanan untuk Keluar\nKetik untuk Kekal - Tekan Kembali untuk Keluar\nMasuk untuk Kekal + Tersedia + Menyemak... + Tidak ada + Tidak dikonfigurasikan + Menu Kunci API untuk HomeAssistant. Token Akses Berumur Panjang. @@ -49,4 +51,5 @@ Selepas masa ini (dalam beberapa saat), dialog pengesahan untuk tindakan ditutup secara automatik dan tindakan itu dibatalkan. Tetapkan kepada 0 untuk melumpuhkan tamat masa. Mewakili jenis dengan ikon (dimatikan) atau dengan label (dihidupkan). Penjajaran Menu Kiri (mati) atau Kanan (hidup). + (Widget sahaja) Mulakan aplikasi secara automatik daripada widget tanpa menunggu satu ketikan. \ No newline at end of file diff --git a/resources/settings/properties.xml b/resources/settings/properties.xml index fbc6a81..03fd708 100644 --- a/resources/settings/properties.xml +++ b/resources/settings/properties.xml @@ -36,8 +36,22 @@ --> 3 + + + + + diff --git a/resources/settings/settings.xml b/resources/settings/settings.xml index b972088..22aab55 100644 --- a/resources/settings/settings.xml +++ b/resources/settings/settings.xml @@ -81,4 +81,13 @@ /> + + + + diff --git a/resources/strings/strings.xml b/resources/strings/strings.xml index 4d64b1a..b93f732 100644 --- a/resources/strings/strings.xml +++ b/resources/strings/strings.xml @@ -19,21 +19,23 @@ Tap Menu Sure? - No Phone connection + No Phone connection No Internet connection No Response, check Internet connection - Menu Fetch Error - No API key in the application settings - No API URL in the application settings - No configuration URL in the application settings + No API key in the application settings + No API URL in the application settings + No configuration URL in the application settings API calls too rapid. Please slow down your requests. URL not found. Potential API URL error in settings. URL not found. Potential Configuration URL error in settings. + No JSON returned from HTTP request. HTTP request returned error code = API URL must not have a trailing slash '/' - Fetching Menu Config.. - Swipe to Right to Exit\nTap to Stay - Hit Back to Exit\nEnter to Stay + Available + Checking... + Unavailable + Unconfigured + Menu API Key for HomeAssistant. @@ -44,4 +46,5 @@ After this time (in seconds), a confirmation dialog for an action is automatically closed and the action is cancelled. Set to 0 to disable the timeout. Representing types with icons (off) or with labels (on). Left (off) or Right (on) Menu Alignment. + (Widget only) Automatically start the application from the widget without waiting for a tap. diff --git a/source/Alert.mc b/source/Alert.mc index 70b60f9..121d31e 100644 --- a/source/Alert.mc +++ b/source/Alert.mc @@ -27,40 +27,39 @@ using Toybox.Graphics; using Toybox.WatchUi; using Toybox.Timer; -const bRadius = 10; - class Alert extends WatchUi.View { - private var mTimer; - private var mTimeout; - private var mText; - private var mFont; - private var mFgcolor; - private var mBgcolor; + private static const bRadius = 10; + private var mTimer as Timer.Timer; + private var mTimeout as Lang.Number; + private var mText as Lang.String; + private var mFont as Graphics.FontType; + private var mFgcolor as Graphics.ColorType; + private var mBgcolor as Graphics.ColorType; function initialize(params as Lang.Dictionary) { View.initialize(); - mText = params.get(:text); + mText = params.get(:text) as Lang.String; if (mText == null) { mText = "Alert"; } - mFont = params.get(:font); + mFont = params.get(:font) as Graphics.FontType; if (mFont == null) { mFont = Graphics.FONT_MEDIUM; } - mFgcolor = params.get(:fgcolor); + mFgcolor = params.get(:fgcolor) as Graphics.ColorType; if (mFgcolor == null) { mFgcolor = Graphics.COLOR_BLACK; } - mBgcolor = params.get(:bgcolor); + mBgcolor = params.get(:bgcolor) as Graphics.ColorType; if (mBgcolor == null) { mBgcolor = Graphics.COLOR_WHITE; } - mTimeout = params.get(:timeout); + mTimeout = params.get(:timeout) as Lang.Number; if (mTimeout == null) { mTimeout = 2000; } @@ -84,7 +83,7 @@ class Alert extends WatchUi.View { var bX = (dc.getWidth() - bWidth) / 2; var bY = (dc.getHeight() - bHeight) / 2; - if(dc has :setAntiAlias) { + if (dc has :setAntiAlias) { dc.setAntiAlias(true); } @@ -113,30 +112,30 @@ class Alert extends WatchUi.View { // Remove the alert from view, usually on user input, but that is defined by the calling function. // - function dismiss() { + function dismiss() as Void { WatchUi.popView(SLIDE_IMMEDIATE); } - function pushView(transition) { + function pushView(transition) as Void { WatchUi.pushView(self, new AlertDelegate(self), transition); } } class AlertDelegate extends WatchUi.InputDelegate { - hidden var mView; + private var mView; function initialize(view) { InputDelegate.initialize(); mView = view; } - function onKey(evt) { + function onKey(evt) as Lang.Boolean { mView.dismiss(); getApp().getQuitTimer().reset(); return true; } - function onTap(evt) { + function onTap(evt) as Lang.Boolean { mView.dismiss(); getApp().getQuitTimer().reset(); return true; diff --git a/source/ErrorView.mc b/source/ErrorView.mc index 0c5c5c8..729851d 100644 --- a/source/ErrorView.mc +++ b/source/ErrorView.mc @@ -33,6 +33,7 @@ using Toybox.Graphics; using Toybox.Lang; using Toybox.WatchUi; using Toybox.Communications; +using Toybox.Timer; class ErrorView extends ScalableView { private var mText as Lang.String = ""; @@ -53,14 +54,12 @@ class ErrorView extends ScalableView { mDelegate = new ErrorDelegate(self); // Convert the settings from % of screen size to pixels mErrorIconMargin = pixelsForScreen(cSettings.get(:errorIconMargin) as Lang.Float); + mErrorIcon = Application.loadResource(Rez.Drawables.ErrorIcon) as Graphics.BitmapResource; } // Load your resources here function onLayout(dc as Graphics.Dc) as Void { - mErrorIcon = Application.loadResource(Rez.Drawables.ErrorIcon) as Graphics.BitmapResource; - var w = dc.getWidth(); - var h = dc.getHeight(); mTextArea = new WatchUi.TextArea({ :text => mText, @@ -68,22 +67,21 @@ class ErrorView extends ScalableView { :font => Graphics.FONT_XTINY, :justification => Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER, :locX => 0, - :locY => 83, + :locY => pixelsForScreen(20.0), :width => w, - :height => h - 166 + :height => pixelsForScreen(60.0) }); } // Update the view function onUpdate(dc as Graphics.Dc) as Void { var w = dc.getWidth(); - var hw = w/2; if(dc has :setAntiAlias) { dc.setAntiAlias(true); } dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLUE); dc.clear(); - dc.drawBitmap(hw - mErrorIcon.getWidth()/2, mErrorIconMargin, mErrorIcon); + dc.drawBitmap(w/2 - mErrorIcon.getWidth()/2, mErrorIconMargin, mErrorIcon); mTextArea.draw(dc); } @@ -97,19 +95,35 @@ class ErrorView extends ScalableView { } if (!mShown) { instance.setText(text); + mShown = true; } return [instance, instance.getDelegate()]; } // Create or reuse an existing ErrorView, and pass on the text. static function show(text as Lang.String) as Void { - create(text); // Ignore returned values if (!mShown) { + create(text); // Ignore returned values WatchUi.pushView(instance, instance.getDelegate(), WatchUi.SLIDE_UP); + // This must be last to avoid a race condition with unShow(), where the + // ErrorView can't be dismissed. mShown = true; } } + static function unShow() as Void { + if (mShown) { + WatchUi.popView(WatchUi.SLIDE_DOWN); + // The call to 'updateNextMenuItem()' must be on another thread so that the view is popped above. + 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.scApiResume, false); + // This must be last to avoid a race condition with show(), where the + // ErrorView can't be dismissed. + mShown = false; + } + } + // Internal show now we're not a static method like 'show()'. function setText(text as Lang.String) as Void { mText = text; @@ -119,13 +133,6 @@ class ErrorView extends ScalableView { } } - static function unShow() as Void { - if (mShown) { - mShown = false; - WatchUi.popView(WatchUi.SLIDE_DOWN); - } - } - } class ErrorDelegate extends WatchUi.BehaviorDelegate { @@ -134,7 +141,7 @@ class ErrorDelegate extends WatchUi.BehaviorDelegate { WatchUi.BehaviorDelegate.initialize(); } - function onBack() { + function onBack() as Lang.Boolean { getApp().getQuitTimer().reset(); ErrorView.unShow(); return true; diff --git a/source/Globals.mc b/source/Globals.mc index 510019c..6089b16 100644 --- a/source/Globals.mc +++ b/source/Globals.mc @@ -26,4 +26,10 @@ class Globals { 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 + // Communications.NETWORK_RESPONSE_OUT_OF_MEMORY response code. + static const scApiBackoff = 1000; // ms + // Needs to be long enough to enable a "double ESC" to quit the application from + // an ErrorView. + static const scApiResume = 200; // ms } diff --git a/source/HomeAssistantApp.mc b/source/HomeAssistantApp.mc index 1e667f0..13169d1 100644 --- a/source/HomeAssistantApp.mc +++ b/source/HomeAssistantApp.mc @@ -22,33 +22,45 @@ using Toybox.Application; using Toybox.Lang; using Toybox.WatchUi; using Toybox.Application.Properties; +using Toybox.Timer; class HomeAssistantApp extends Application.AppBase { - private var mHaMenu as HomeAssistantView or Null; - private var mQuitTimer as QuitTimer or Null; private var strNoApiKey as Lang.String or Null; private var strNoApiUrl as Lang.String or Null; private var strNoConfigUrl as Lang.String or Null; private var strNoPhone as Lang.String or Null; private var strNoInternet as Lang.String or Null; private var strNoResponse as Lang.String or Null; - private var strNoMenu as Lang.String or Null; private var strApiFlood as Lang.String or Null; private var strConfigUrlNotFound as Lang.String or Null; + private var strNoJson as Lang.String or Null; private var strUnhandledHttpErr as Lang.String or Null; private var strTrailingSlashErr as Lang.String or Null; - private var mItemsToUpdate; // Array initialised by onReturnFetchMenuConfig() - private var mNextItemToUpdate = 0; // Index into the above array + private var strAvailable = WatchUi.loadResource($.Rez.Strings.Available); + private var strUnavailable = WatchUi.loadResource($.Rez.Strings.Unavailable); + private var strUnconfigured = WatchUi.loadResource($.Rez.Strings.Unconfigured); + + private var mApiKey as Lang.String or Null; // The compiler can't tell these are updated by + private var mApiUrl as Lang.String or Null; // initialize(), hence the "or Null". + private var mConfigUrl as Lang.String or Null; // + private var mApiStatus as Lang.String = WatchUi.loadResource($.Rez.Strings.Checking); + private var mMenuStatus as Lang.String = WatchUi.loadResource($.Rez.Strings.Checking); + 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; // Array initialised by onReturnFetchMenuConfig() + private var mNextItemToUpdate = 0; // Index into the above array + private var mIsGlance as Lang.Boolean = false; function initialize() { AppBase.initialize(); - + onSettingsChanged(); // ATTENTION when adding stuff into this block: // Because of the >>GlanceView<<, it should contain only // code, which is used as well for the glance: // - https://developer.garmin.com/connect-iq/core-topics/glances/ // - // Also dealing with resources "Rez" needs attention, too. See + // Also dealing with resources "Rez" needs attention, too. See // "Resource Scopes": // - https://developer.garmin.com/connect-iq/core-topics/resources/ // @@ -58,12 +70,13 @@ class HomeAssistantApp extends Application.AppBase { // onStart() is called on application start up function onStart(state as Lang.Dictionary?) as Void { + AppBase.onStart(state); // ATTENTION when adding stuff into this block: // Because of the >>GlanceView<<, it should contain only // code, which is used as well for the glance: // - https://developer.garmin.com/connect-iq/core-topics/glances/ // - // Also dealing with resources "Rez" needs attention, too. See + // Also dealing with resources "Rez" needs attention, too. See // "Resource Scopes": // - https://developer.garmin.com/connect-iq/core-topics/resources/ // @@ -73,12 +86,13 @@ class HomeAssistantApp extends Application.AppBase { // onStop() is called when your application is exiting function onStop(state as Lang.Dictionary?) as Void { + AppBase.onStop(state); // ATTENTION when adding stuff into this block: // Because of the >>GlanceView<<, it should contain only // code, which is used as well for the glance: // - https://developer.garmin.com/connect-iq/core-topics/glances/ // - // Also dealing with resources "Rez" needs attention, too. See + // Also dealing with resources "Rez" needs attention, too. See // "Resource Scopes": // - https://developer.garmin.com/connect-iq/core-topics/resources/ // @@ -88,38 +102,35 @@ class HomeAssistantApp extends Application.AppBase { // Return the initial view of your application here function getInitialView() as Lang.Array? { - strNoApiKey = WatchUi.loadResource($.Rez.Strings.NoAPIKey); strNoApiUrl = WatchUi.loadResource($.Rez.Strings.NoApiUrl); strNoConfigUrl = WatchUi.loadResource($.Rez.Strings.NoConfigUrl); strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone); strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet); strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse); - strNoMenu = WatchUi.loadResource($.Rez.Strings.NoMenu); strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood); strConfigUrlNotFound = WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound); + strNoJson = WatchUi.loadResource($.Rez.Strings.NoJson); strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr); strTrailingSlashErr = WatchUi.loadResource($.Rez.Strings.TrailingSlashErr); mQuitTimer = new QuitTimer(); - var api_url = Properties.getValue("api_url") as Lang.String; - - if ((Properties.getValue("api_key") as Lang.String).length() == 0) { + if (mApiKey.length() == 0) { if (Globals.scDebug) { System.println("HomeAssistantApp getInitialView(): No API key in the application settings."); } return ErrorView.create(strNoApiKey + "."); - } else if (api_url.length() == 0) { + } else if (mApiUrl.length() == 0) { if (Globals.scDebug) { System.println("HomeAssistantApp getInitialView(): No API URL in the application settings."); } return ErrorView.create(strNoApiUrl + "."); - } else if (api_url.substring(-1, api_url.length()).equals("/")) { + } else if (mApiUrl.substring(-1, mApiUrl.length()).equals("/")) { if (Globals.scDebug) { System.println("HomeAssistantApp getInitialView(): API URL must not have a trailing slash '/'."); } return ErrorView.create(strTrailingSlashErr + "."); - } else if ((Properties.getValue("config_url") as Lang.String).length() == 0) { + } else if (mConfigUrl.length() == 0) { if (Globals.scDebug) { System.println("HomeAssistantApp getInitialView(): No configuration URL in the application settings."); } @@ -136,79 +147,293 @@ class HomeAssistantApp extends Application.AppBase { return ErrorView.create(strNoInternet + "."); } else { fetchMenuConfig(); - return [new RootView(self), new RootViewDelegate(self)] as Lang.Array; + fetchApiStatus(); + if (WidgetApp.isWidget) { + return [new RootView(self), new RootViewDelegate(self)] as Lang.Array; + } else { + return [new WatchUi.View(), new WatchUi.BehaviorDelegate()] as Lang.Array; + } } } // Callback function after completing the GET request to fetch the configuration menu. // + (: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); } - if (responseCode == Communications.BLE_HOST_TIMEOUT || responseCode == Communications.BLE_CONNECTION_UNAVAILABLE) { - if (Globals.scDebug) { - System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - } - ErrorView.show(strNoPhone + "."); - } else if (responseCode == Communications.BLE_QUEUE_FULL) { - if (Globals.scDebug) { - System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_QUEUE_FULL, API calls too rapid."); - } - // Don't need to worry about multiple ErrorViews here as the fetch does not happen a second time. - ErrorView.show(strApiFlood); - } else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) { - if (Globals.scDebug) { - System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); - } - ErrorView.show(strNoResponse); - } else if (responseCode == 404) { - if (Globals.scDebug) { - System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: 404, page not found. Check Configuration URL setting."); - } - ErrorView.show(strConfigUrlNotFound); - } else if (responseCode == 200) { - mHaMenu = new HomeAssistantView(data, null); - mQuitTimer.begin(); - pushHomeAssistantMenuView(); - mItemsToUpdate = mHaMenu.getItemsToUpdate(); - // Start the continuous update process that continues for as long as the application is running. - // The chain of functions from 'updateNextMenuItem()' calls 'updateNextMenuItem()' on completion. - if (mItemsToUpdate.size() > 0) { - updateNextMenuItem(); - } - } else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) { - if (Globals.scDebug) { - System.println("HomeAssistantApp onReturnFetchMenuConfig(): Network request timeout."); - } - ErrorView.show(strNoMenu + ". " + strNoInternet + "?"); + + mMenuStatus = strUnavailable; + 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."); + } + if (!mIsGlance) { + ErrorView.show(strNoPhone + "."); + } + break; + + case Communications.BLE_QUEUE_FULL: + if (Globals.scDebug) { + System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + } + if (!mIsGlance) { + ErrorView.show(strApiFlood); + } + break; + + case Communications.NETWORK_REQUEST_TIMED_OUT: + if (Globals.scDebug) { + System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); + } + if (!mIsGlance) { + ErrorView.show(strNoResponse); + } + 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."); + } + if (!mIsGlance) { + ErrorView.show(strNoJson); + } + break; + + case 404: + if (Globals.scDebug) { + System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: 404, page not found. Check Configuration URL setting."); + } + if (!mIsGlance) { + ErrorView.show(strConfigUrlNotFound); + } + break; + + case 200: + mMenuStatus = strAvailable; + if (!mIsGlance) { + mHaMenu = new HomeAssistantView(data, null); + mQuitTimer.begin(); + if (Properties.getValue("widget_start_no_tap")) { + // As soon as the menu has been fetched start show the menu of items. + // This behaviour is inconsistent with the standard Garmin User Interface, but has been + // requested by users so has been made the non-default option. + pushHomeAssistantMenuView(); + } + mItemsToUpdate = mHaMenu.getItemsToUpdate(); + // Start the continuous update process that continues for as long as the application is running. + // The chain of functions from 'updateNextMenuItem()' calls 'updateNextMenuItem()' on completion. + if (mItemsToUpdate.size() > 0) { + updateNextMenuItem(); + } + if (!WidgetApp.isWidget) { + WatchUi.switchToView(mHaMenu, new HomeAssistantViewDelegate(false), WatchUi.SLIDE_IMMEDIATE); + } + } + break; + + default: + if (Globals.scDebug) { + System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode); + } + if (!mIsGlance) { + ErrorView.show(strUnhandledHttpErr + responseCode); + } + break; + } + WatchUi.requestUpdate(); + } + + (:glance) + function fetchMenuConfig() as Void { + if (mConfigUrl.equals("")) { + mMenuStatus = strUnconfigured; + WatchUi.requestUpdate(); } else { - if (Globals.scDebug) { - System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode); + var options = { + :method => Communications.HTTP_REQUEST_METHOD_GET, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }; + if (! System.getDeviceSettings().phoneConnected) { + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call."); + } + if (mIsGlance) { + WatchUi.requestUpdate(); + } else { + ErrorView.show(strNoPhone + "."); + } + mMenuStatus = strUnavailable; + } else if (! System.getDeviceSettings().connectionAvailable) { + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call."); + } + if (mIsGlance) { + WatchUi.requestUpdate(); + } else { + ErrorView.show(strNoInternet + "."); + } + mMenuStatus = strUnavailable; + } else { + Communications.makeWebRequest( + mConfigUrl, + null, + options, + method(:onReturnFetchMenuConfig) + ); } - ErrorView.show(strUnhandledHttpErr + responseCode ); } } - function fetchMenuConfig() as Void { - var options = { - :method => Communications.HTTP_REQUEST_METHOD_GET, - :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON - }; - Communications.makeWebRequest( - Properties.getValue("config_url"), - null, - options, - method(:onReturnFetchMenuConfig) - ); + // Callback function after completing the GET request to fetch the API status. + // + (: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); + } + + mApiStatus = strUnavailable; + 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."); + } + if (!mIsGlance) { + ErrorView.show(strNoPhone + "."); + } + break; + + case Communications.BLE_QUEUE_FULL: + if (Globals.scDebug) { + System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + } + if (!mIsGlance) { + ErrorView.show(strApiFlood); + } + break; + + case Communications.NETWORK_REQUEST_TIMED_OUT: + if (Globals.scDebug) { + System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); + } + if (!mIsGlance) { + ErrorView.show(strNoResponse); + } + 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."); + } + if (!mIsGlance) { + ErrorView.show(strNoJson); + } + break; + + case 404: + if (Globals.scDebug) { + System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: 404, page not found. Check Configuration URL setting."); + } + if (!mIsGlance) { + ErrorView.show(strConfigUrlNotFound); + } + break; + + case 200: + var msg = null; + if (data != null) { + msg = data.get("message"); + } + if (msg.equals("API running.")) { + mApiStatus = strAvailable; + } else { + if (!mIsGlance) { + ErrorView.show("API " + mApiStatus + "."); + } + } + break; + + default: + if (Globals.scDebug) { + System.println("HomeAssistantApp onReturnFetchApiStatus(): Unhandled HTTP response code = " + responseCode); + } + if (!mIsGlance) { + ErrorView.show(strUnhandledHttpErr + responseCode); + } + } + WatchUi.requestUpdate(); } - function homeAssistantMenuIsLoaded() as Lang.Boolean{ - return mHaMenu!=null; + (:glance) + function fetchApiStatus() as Void { + if (mApiUrl.equals("")) { + mApiStatus = strUnconfigured; + WatchUi.requestUpdate(); + } else { + var options = { + :method => Communications.HTTP_REQUEST_METHOD_GET, + :headers => { + "Authorization" => "Bearer " + mApiKey + }, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }; + if (! System.getDeviceSettings().phoneConnected) { + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call."); + } + mApiStatus = strUnavailable; + if (mIsGlance) { + WatchUi.requestUpdate(); + } else { + ErrorView.show(strNoPhone + "."); + } + } else if (! System.getDeviceSettings().connectionAvailable) { + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call."); + } + mApiStatus = strUnavailable; + if (mIsGlance) { + WatchUi.requestUpdate(); + } else { + ErrorView.show(strNoInternet + "."); + } + } else { + Communications.makeWebRequest( + mApiUrl + "/", + null, + options, + method(:onReturnFetchApiStatus) + ); + } + } } - function pushHomeAssistantMenuView() as Void{ + function setApiStatus(s as Lang.String) { + mApiStatus = s; + } + + (:glance) + function getApiStatus() as Lang.String { + return mApiStatus; + } + + (:glance) + function getMenuStatus() as Lang.String { + return mMenuStatus; + } + + function isHomeAssistantMenuLoaded() as Lang.Boolean { + return mHaMenu != null; + } + + function pushHomeAssistantMenuView() as Void { WatchUi.pushView(mHaMenu, new HomeAssistantViewDelegate(true), WatchUi.SLIDE_IMMEDIATE); } @@ -216,17 +441,44 @@ class HomeAssistantApp extends Application.AppBase { // This function is called by a timer every Globals.menuItemUpdateInterval ms. function updateNextMenuItem() as Void { var itu = mItemsToUpdate as Lang.Array; - itu[mNextItemToUpdate].getState(); - mNextItemToUpdate = (mNextItemToUpdate + 1) % itu.size(); + if (itu == null) { + if (Globals.scDebug) { + System.println("HomeAssistantApp updateNextMenuItem(): No menu items to update"); + } + if (!mIsGlance) { + ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound)); + } + } else { + itu[mNextItemToUpdate].getState(); + mNextItemToUpdate = (mNextItemToUpdate + 1) % itu.size(); + } } - function getQuitTimer() as QuitTimer{ + function getQuitTimer() as QuitTimer { return mQuitTimer; } (:glance) - function getGlanceView() { - return [new HomeAssistantGlanceView()]; + function getGlanceView() as Lang.Array or Null { + mIsGlance = true; + updateGlance(); + mTimer = new Timer.Timer(); + mTimer.start(method(:updateGlance), Globals.scApiBackoff, true); + return [new HomeAssistantGlanceView(self)]; + } + + // Required for the Glance update timer. + function updateGlance() as Void { + fetchMenuConfig(); + fetchApiStatus(); + } + + // 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 { + mApiKey = Properties.getValue("api_key"); + mApiUrl = Properties.getValue("api_url"); + mConfigUrl = Properties.getValue("config_url"); } } diff --git a/source/HomeAssistantGlanceView.mc b/source/HomeAssistantGlanceView.mc index 7bc68ee..8e5d9d8 100644 --- a/source/HomeAssistantGlanceView.mc +++ b/source/HomeAssistantGlanceView.mc @@ -24,19 +24,86 @@ using Toybox.Graphics; (:glance) class HomeAssistantGlanceView extends WatchUi.GlanceView { + private static const scLeftMargin = 20; // in pixels + private static const scLeftIndent = 10; // Left Indent "_text:" in pixels + private static const scMidSep = 10; // Middle Separator "text:_text" in pixels + private var mApp as HomeAssistantApp; + private var mTitle as WatchUi.Text or Null; + private var mApiText as WatchUi.Text or Null; + private var mApiStatus as WatchUi.Text or Null; + private var mMenuText as WatchUi.Text or Null; + private var mMenuStatus as WatchUi.Text or Null; - private var mText as Lang.String; - - function initialize() { + function initialize(app as HomeAssistantApp) { GlanceView.initialize(); + mApp = app; + } - mText = WatchUi.loadResource($.Rez.Strings.AppName); + function onLayout(dc as Graphics.Dc) as Void { + var strChecking = WatchUi.loadResource($.Rez.Strings.Checking); + var strGlanceMenu = WatchUi.loadResource($.Rez.Strings.GlanceMenu); + var h = dc.getHeight(); + var tw = dc.getTextWidthInPixels(strGlanceMenu, Graphics.FONT_XTINY); + + mTitle = new WatchUi.Text({ + :text => WatchUi.loadResource($.Rez.Strings.AppName), + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_TINY, + :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => scLeftMargin, + :locY => 1 * h / 6 + }); + + mApiText = new WatchUi.Text({ + :text => "API:", + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_RIGHT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => scLeftMargin + scLeftIndent + tw, + :locY => 3 * h / 6 + }); + mApiStatus = new WatchUi.Text({ + :text => strChecking, + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => scLeftMargin + scLeftIndent + scMidSep + tw, + :locY => 3 * h / 6 + }); + mMenuText = new WatchUi.Text({ + :text => strGlanceMenu + ":", + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_RIGHT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => scLeftMargin + scLeftIndent + tw, + :locY => 5 * h / 6 + }); + mMenuStatus = new WatchUi.Text({ + :text => strChecking, + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => scLeftMargin + scLeftIndent + scMidSep + tw, + :locY => 5 * h / 6 + }); } function onUpdate(dc) as Void { GlanceView.onUpdate(dc); - - dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK); - dc.drawText(0, dc.getHeight() / 2, Graphics.FONT_TINY, mText, Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER); + if(dc has :setAntiAlias) { + dc.setAntiAlias(true); + } + dc.setColor( + Graphics.COLOR_WHITE, + Graphics.COLOR_BLUE + ); + dc.clear(); + mTitle.draw(dc); + mApiText.draw(dc); + mApiStatus.setText(mApp.getApiStatus()); + mApiStatus.draw(dc); + mMenuText.draw(dc); + mMenuStatus.setText(mApp.getMenuStatus()); + mMenuStatus.draw(dc); } } diff --git a/source/HomeAssistantService.mc b/source/HomeAssistantService.mc index 14fd621..d5a84bd 100644 --- a/source/HomeAssistantService.mc +++ b/source/HomeAssistantService.mc @@ -24,22 +24,18 @@ using Toybox.Graphics; using Toybox.Application.Properties; class HomeAssistantService { - private var mApiKey as Lang.String; - private var strNoPhone as Lang.String; - private var strNoInternet as Lang.String; - private var strNoResponse as Lang.String; - private var strApiFlood as Lang.String; - private var strApiUrlNotFound as Lang.String; - private var strUnhandledHttpErr as Lang.String; + private var strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone); + private var strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet); + private var strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse); + private var strNoJson = WatchUi.loadResource($.Rez.Strings.NoJson); + private var strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood); + private var strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound); + private var strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr); + + private var mApiKey as Lang.String; function initialize() { - strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone); - strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet); - strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse); - strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood); - strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound); - strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr); - mApiKey = Properties.getValue("api_key"); + mApiKey = Properties.getValue("api_key"); } // Callback function after completing the POST request to call a service. @@ -50,54 +46,79 @@ class HomeAssistantService { System.println("HomeAssistantService onReturnCall() Response Code: " + responseCode); System.println("HomeAssistantService onReturnCall() Response Data: " + data); } - if (responseCode == Communications.BLE_HOST_TIMEOUT || responseCode == 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(strNoPhone + "."); - } else if (responseCode == Communications.BLE_QUEUE_FULL) { - if (Globals.scDebug) { - System.println("HomeAssistantService onReturnCall() Response Code: BLE_QUEUE_FULL, API calls too rapid."); - } - // Don't need to worry about multiple ErrorViews here as the call is not on a repeat timer. - ErrorView.show(strApiFlood); - } else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) { - if (Globals.scDebug) { - System.println("HomeAssistantService onReturnCall() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); - } - ErrorView.show(strNoResponse); - } else if (responseCode == 404) { - if (Globals.scDebug) { - System.println("HomeAssistantService onReturnCall() Response Code: 404, page not found. Check API URL setting."); - } - ErrorView.show(strApiUrlNotFound); - } else if (responseCode == 200) { - if (Globals.scDebug) { - System.println("HomeAssistantService onReturnCall(): Service executed."); - } - var d = data as Lang.Array; - var toast = "Executed"; - for(var i = 0; i < d.size(); i++) { - if ((d[i].get("entity_id") as Lang.String).equals(identifier)) { - toast = (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String; + + 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."); } - } - if (WatchUi has :showToast) { - WatchUi.showToast(toast, null); - } else { - new Alert({ - :timeout => Globals.scAlertTimeout, - :font => Graphics.FONT_MEDIUM, - :text => toast, - :fgcolor => Graphics.COLOR_WHITE, - :bgcolor => Graphics.COLOR_BLACK - }).pushView(WatchUi.SLIDE_IMMEDIATE); - } - } else { - if (Globals.scDebug) { - System.println("HomeAssistantService onReturnCall(): Unhandled HTTP response code = " + responseCode); - } - ErrorView.show(strUnhandledHttpErr + responseCode ); + ErrorView.show(strNoPhone + "."); + break; + + case Communications.BLE_QUEUE_FULL: + if (Globals.scDebug) { + System.println("HomeAssistantService onReturnCall() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + } + ErrorView.show(strApiFlood); + 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(strNoResponse); + 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?"); + } + // 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(strNoJson); + break; + + case 404: + if (Globals.scDebug) { + System.println("HomeAssistantService onReturnCall() Response Code: 404, page not found. Check API URL setting."); + } + ErrorView.show(strApiUrlNotFound); + break; + + case 200: + if (Globals.scDebug) { + System.println("HomeAssistantService onReturnCall(): Service executed."); + } + var d = data as Lang.Array; + var toast = "Executed"; + for(var i = 0; i < d.size(); i++) { + if ((d[i].get("entity_id") as Lang.String).equals(identifier)) { + toast = (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String; + } + } + if (WatchUi has :showToast) { + WatchUi.showToast(toast, null); + } else { + new Alert({ + :timeout => Globals.scAlertTimeout, + :font => Graphics.FONT_MEDIUM, + :text => toast, + :fgcolor => Graphics.COLOR_WHITE, + :bgcolor => Graphics.COLOR_BLACK + }).pushView(WatchUi.SLIDE_IMMEDIATE); + } + break; + + default: + if (Globals.scDebug) { + System.println("HomeAssistantService onReturnCall(): Unhandled HTTP response code = " + responseCode); + } + ErrorView.show(strUnhandledHttpErr + responseCode); } } diff --git a/source/HomeAssistantToggleMenuItem.mc b/source/HomeAssistantToggleMenuItem.mc index ad648e4..c27024f 100644 --- a/source/HomeAssistantToggleMenuItem.mc +++ b/source/HomeAssistantToggleMenuItem.mc @@ -25,13 +25,17 @@ using Toybox.Application.Properties; using Toybox.Timer; class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { - private var mApiKey as Lang.String; - private var strNoPhone as Lang.String; - private var strNoInternet as Lang.String; - private var strNoResponse as Lang.String; - private var strApiFlood as Lang.String; - private var strApiUrlNotFound as Lang.String; - private var strUnhandledHttpErr as Lang.String; + private var strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone); + private var strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet); + private var strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse); + private var strNoJson = WatchUi.loadResource($.Rez.Strings.NoJson); + private var strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood); + private var strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound); + private var strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr); + private var strUnavailable = WatchUi.loadResource($.Rez.Strings.Unavailable); + private var strAvailable = WatchUi.loadResource($.Rez.Strings.Available); + + private var mApiKey as Lang.String; function initialize( label as Lang.String or Lang.Symbol, @@ -46,13 +50,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { :icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol } or Null ) { - strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone); - strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet); - strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse); - strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood); - strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound); - strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr); - mApiKey = Properties.getValue("api_key"); + mApiKey = Properties.getValue("api_key"); WatchUi.ToggleMenuItem.initialize(label, subLabel, identifier, enabled, options); } @@ -69,14 +67,16 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { } // Callback function after completing the GET request to fetch the status. + // Terminate updating the toggle menu items via the chain of calls for a permanent network + // 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); } - // Provide the ability to terminate updating chain of calls for a permanent network error. - var keepUpdating = true; + + var status = strUnavailable; switch (responseCode) { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: @@ -85,45 +85,66 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { } ErrorView.show(strNoPhone + "."); break; + case Communications.BLE_QUEUE_FULL: if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); } ErrorView.show(strApiFlood); 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(strNoResponse); 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(strNoJson); + 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?"); + } + 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); + break; + case 404: var msg = null; if (data != null) { msg = data.get("message"); } if (msg != null) { - // Should be an HTTP 405 according to curl queries + // Should be an HTTP 404 according to curl queries if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404. " + mIdentifier + " " + msg); } - ErrorView.show("HTTP 405, " + mIdentifier + ". " + data.get("message")); + ErrorView.show("HTTP 404, " + mIdentifier + ". " + data.get("message")); } else { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting."); } ErrorView.show(strApiUrlNotFound); } - keepUpdating = false; break; + case 405: if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 405. " + mIdentifier + " " + data.get("message")); } ErrorView.show("HTTP 405, " + mIdentifier + ". " + data.get("message")); - keepUpdating = false; + break; + case 200: + status = strAvailable; var state = data.get("state") as Lang.String; if (Globals.scDebug) { System.println((data.get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state); @@ -133,17 +154,17 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { } setUiToggle(state); ErrorView.unShow(); + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + getApp().updateNextMenuItem(); break; + default: if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode); } ErrorView.show(strUnhandledHttpErr + responseCode); } - if (keepUpdating) { - // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. - getApp().updateNextMenuItem(); - } + getApp().setApiStatus(status); } function getState() as Void { @@ -154,17 +175,18 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { }, :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON }; - var keepUpdating = true; if (! System.getDeviceSettings().phoneConnected) { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call."); } ErrorView.show(strNoPhone + "."); + getApp().setApiStatus(strUnavailable); } else if (! System.getDeviceSettings().connectionAvailable) { if (Globals.scDebug) { System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call."); } ErrorView.show(strNoInternet + "."); + getApp().setApiStatus(strUnavailable); } else { var url = Properties.getValue("api_url") + "/states/" + mIdentifier; if (Globals.scDebug) { @@ -176,19 +198,6 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { options, method(:onReturnGetState) ); - // The update is called by onReturnGetState() instead - keepUpdating = false; - } - // On temporary failure, keep the updating going. - if (keepUpdating) { - // Need to avoid an infinite loop where the pushed ErrorView does not appear before getState() is called again - // and the call stack overflows. So continue the call chain from somewhere asynchronous. - 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), 500, false); - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem getState(): Updated failed " + mIdentifier); - } } } @@ -199,44 +208,67 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: " + responseCode); System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Data: " + data); } - if (responseCode == Communications.BLE_HOST_TIMEOUT || responseCode == 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(strNoPhone + "."); - } else if (responseCode == Communications.BLE_QUEUE_FULL) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); - } - ErrorView.show(strApiFlood); - } else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); - } - ErrorView.show(strNoResponse); - } else if (responseCode == 404) { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: 404, page not found. Check API URL setting."); - } - ErrorView.show(strApiUrlNotFound); - } else if (responseCode == 200) { - var state; - var d = data as Lang.Array; - for(var i = 0; i < d.size(); i++) { - if ((d[i].get("entity_id") as Lang.String).equals(mIdentifier)) { - 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); - } - setUiToggle(state); + + var status = strUnavailable; + 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."); } - } - } else { - if (Globals.scDebug) { - System.println("HomeAssistantToggleMenuItem onReturnSetState(): Unhandled HTTP response code = " + responseCode); - } - ErrorView.show(strUnhandledHttpErr + responseCode ); + ErrorView.show(strNoPhone + "."); + break; + + case Communications.BLE_QUEUE_FULL: + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + } + ErrorView.show(strApiFlood); + 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(strNoResponse); + 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(strNoJson); + break; + + case 404: + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: 404, page not found. Check API URL setting."); + } + ErrorView.show(strApiUrlNotFound); + break; + + case 200: + var state; + var d = data as Lang.Array; + for(var i = 0; i < d.size(); i++) { + if ((d[i].get("entity_id") as Lang.String).equals(mIdentifier)) { + 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); + } + setUiToggle(state); + } + } + status = strAvailable; + break; + + default: + if (Globals.scDebug) { + System.println("HomeAssistantToggleMenuItem onReturnSetState(): Unhandled HTTP response code = " + responseCode); + } + ErrorView.show(strUnhandledHttpErr + responseCode); } + getApp().setApiStatus(status); } function setState(s as Lang.Boolean) as Void { diff --git a/source/HomeAssistantView.mc b/source/HomeAssistantView.mc index 9fb8e3a..747adcb 100644 --- a/source/HomeAssistantView.mc +++ b/source/HomeAssistantView.mc @@ -103,20 +103,21 @@ class HomeAssistantView extends WatchUi.Menu2 { // Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/ // class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate { - - private var mIsRootMenuView = false; + private var mIsRootMenuView as Lang.Boolean = false; + private var mTimer as QuitTimer; function initialize(isRootMenuView as Lang.Boolean) { Menu2InputDelegate.initialize(); mIsRootMenuView = isRootMenuView; + mTimer = getApp().getQuitTimer(); } function onBack() { - getApp().getQuitTimer().reset(); + mTimer.reset(); - if (mIsRootMenuView){ + if (mIsRootMenuView) { // If its started from glance or as an activity, directly exit the widget/app - // (on widgets without glance, this exit() won`t do anything, + // (on widgets without glance, this exit() won't do anything, // so the base view will be shown instead, through the popView below this "if body") System.exit(); } @@ -126,16 +127,16 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate { // Only for CheckboxMenu function onDone() { - getApp().getQuitTimer().reset(); + mTimer.reset(); } // Only for CustomMenu function onFooter() { - getApp().getQuitTimer().reset(); + mTimer.reset(); } function onSelect(item as WatchUi.MenuItem) as Void { - getApp().getQuitTimer().reset(); + mTimer.reset(); if (item instanceof HomeAssistantToggleMenuItem) { var haToggleItem = item as HomeAssistantToggleMenuItem; if (Globals.scDebug) { @@ -175,7 +176,7 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate { // Only for CustomMenu function onTitle() { - getApp().getQuitTimer().reset(); + mTimer.reset(); } } diff --git a/source/RootView.mc b/source/RootView.mc index ff20a42..1d412d5 100644 --- a/source/RootView.mc +++ b/source/RootView.mc @@ -25,76 +25,96 @@ using Toybox.WatchUi; class RootView extends ScalableView { // ATTENTION when the app is running as a "widget" (that means, it runs on devices - // without glance view support), the input events in this view are limited, as + // without glance view support), the input events in this view are limited, as // described under "Base View and the Widget Carousel" on: - // + // // https://developer.garmin.com/connect-iq/connect-iq-basics/app-types/ - // + // // Also the view type of the base view is limited too (for example "WatchUi.Menu2" // is not possible)). - // - // Also System.exit() is not working/do nothing when running as a widget: A widget will be + // + // Also System.exit() is not working/do nothing when running as a widget: A widget will be // terminated automatically by OS after some time or can be quit manually, when on the base // view a swipe to left / "back button" press is done. - private var mApp as HomeAssistantApp; - private var strFetchingMenuConfig as Lang.String; - private var strExit as Lang.String; - private var mTextAreaExit as WatchUi.TextArea or Null; - private var mTextAreaFetching as WatchUi.TextArea or Null; + private static const scMidSep = 10; // Middle Separator "text:_text" in pixels + private var mApp as HomeAssistantApp; + private var mTitle as WatchUi.Text or Null; + private var mApiText as WatchUi.Text or Null; + private var mApiStatus as WatchUi.Text or Null; + private var mMenuText as WatchUi.Text or Null; + private var mMenuStatus as WatchUi.Text or Null; function initialize(app as HomeAssistantApp) { ScalableView.initialize(); - mApp=app; - - strFetchingMenuConfig = WatchUi.loadResource($.Rez.Strings.FetchingMenuConfig); - - if (System.getDeviceSettings().isTouchScreen){ - strExit = WatchUi.loadResource($.Rez.Strings.ExitViewTouch); - } else { - strExit = WatchUi.loadResource($.Rez.Strings.ExitViewButtons); - } + mApp = app; } function onLayout(dc as Graphics.Dc) as Void { - var w = dc.getWidth(); - var h = dc.getHeight(); + var strChecking = WatchUi.loadResource($.Rez.Strings.Checking); + var w = dc.getWidth(); - mTextAreaExit = new WatchUi.TextArea({ - :text => strExit, + mTitle = new WatchUi.Text({ + :text => WatchUi.loadResource($.Rez.Strings.AppName), :color => Graphics.COLOR_WHITE, - :font => Graphics.FONT_XTINY, + :font => Graphics.FONT_TINY, :justification => Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER, - :locX => 0, - :locY => 83, - :width => w, - :height => h - 166 + :locX => w/2, + :locY => pixelsForScreen(30.0) }); - mTextAreaFetching = new WatchUi.TextArea({ - :text => strFetchingMenuConfig, + mApiText = new WatchUi.Text({ + :text => "API:", :color => Graphics.COLOR_WHITE, :font => Graphics.FONT_XTINY, - :justification => Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER, - :locX => 0, - :locY => 83, - :width => w, - :height => h - 166 + :justification => Graphics.TEXT_JUSTIFY_RIGHT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => w/2 - scMidSep/2, + :locY => pixelsForScreen(50.0) + }); + mApiStatus = new WatchUi.Text({ + :text => strChecking, + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => w/2 + scMidSep/2, + :locY => pixelsForScreen(50.0) + }); + mMenuText = new WatchUi.Text({ + :text => WatchUi.loadResource($.Rez.Strings.GlanceMenu) + ":", + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_RIGHT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => w/2 - scMidSep/2, + :locY => pixelsForScreen(70.0) + }); + mMenuStatus = new WatchUi.Text({ + :text => strChecking, + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => w/2 + scMidSep/2, + :locY => pixelsForScreen(70.0) }); } function onUpdate(dc as Graphics.Dc) as Void { - if(dc has :setAntiAlias) { + if (dc has :setAntiAlias) { dc.setAntiAlias(true); } dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK); dc.clear(); - - if(mApp.homeAssistantMenuIsLoaded()) { - mTextAreaExit.draw(dc); - } else { - mTextAreaFetching.draw(dc); - } + // Initialise this locally, otherwise the venu1 device runs out of memory when stored at class level. + var launcherIcon = Application.loadResource(Rez.Drawables.LauncherIcon); + var w = dc.getWidth(); + var h = dc.getHeight(); + dc.drawBitmap(w/2 - launcherIcon.getWidth()/2, h/8 - launcherIcon.getHeight()/2, launcherIcon); + mTitle.draw(dc); + mApiText.draw(dc); + mApiStatus.setText(mApp.getApiStatus()); + mApiStatus.draw(dc); + mMenuText.draw(dc); + mMenuStatus.setText(mApp.getMenuStatus()); + mMenuStatus.draw(dc); } } @@ -102,9 +122,9 @@ class RootViewDelegate extends WatchUi.BehaviorDelegate { var mApp as HomeAssistantApp; - function initialize(app as HomeAssistantApp ) { + function initialize(app as HomeAssistantApp) { BehaviorDelegate.initialize(); - mApp=app; + mApp = app; } function onTap(evt as WatchUi.ClickEvent) as Lang.Boolean { @@ -115,12 +135,12 @@ class RootViewDelegate extends WatchUi.BehaviorDelegate { return backToMainMenu(); } - function onMenu() as Lang.Boolean{ + function onMenu() as Lang.Boolean { return backToMainMenu(); } - private function backToMainMenu() as Lang.Boolean{ - if(mApp.homeAssistantMenuIsLoaded()){ + private function backToMainMenu() as Lang.Boolean { + if (mApp.isHomeAssistantMenuLoaded()) { mApp.pushHomeAssistantMenuView(); return true; } diff --git a/source/ScalableView.mc b/source/ScalableView.mc index febf84d..5d1a4bf 100644 --- a/source/ScalableView.mc +++ b/source/ScalableView.mc @@ -27,6 +27,7 @@ class ScalableView extends WatchUi.View { function initialize() { View.initialize(); + mScreenWidth = System.getDeviceSettings().screenWidth; } // Convert a fraction expressed as a percentage (%) to a number of pixels for the @@ -40,9 +41,6 @@ class ScalableView extends WatchUi.View { // height > width. // function pixelsForScreen(pc as Lang.Float) as Lang.Number { - if (mScreenWidth == null) { - mScreenWidth = System.getDeviceSettings().screenWidth; - } return Math.round(pc * mScreenWidth) / 100; } }