diff --git a/HISTORY.md b/HISTORY.md index 6ca3ed1..86bdb58 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -34,3 +34,4 @@ | 2.19 | A template to evaluate is now optionally allowed on both `group` and `toggle` menu items. The template to evaluate is non-optional on a `template` menu item. All updates are performed in a single HTTP GET request for efficiency. Bug fix for negative heading values. Vibration now (optionally) confirms toggle menu items being tapped. | | 2.20 | Simplified the code base now that templates have been requested in all menu items. This means the `template` menu item became a superset of `tap`. Therefore the `tap` code has been has been upgraded to include `template` and the latter deprecated. JSON menu definitions continue to support `template` items by instantiating a `tap` menu item, but the schema marks them as deprecated and users should migrate their menu definitions now. Use the [web editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) for assistance with changes. | | 2.21 | Added 7 new devices (`edge1050`, `enduro3`, `fenix843mm`, `fenix847mm`, `fenix8solar47mm`, `fenix8solar51mm`, `fenixe`) and upgraded the SDK to 7.3.0. Fix for a bug on Edge devices introduced by v2.16 activity reporting improvements. | +| 2.21 | Major feature release adding an optional PIN to menu items. This significant new feature has been provided by [moesterheld](https://github.com/moesterheld). Please do not rely on this application for security. Use at your own risk! | diff --git a/config.schema.json b/config.schema.json index 1e70bba..d536b94 100644 --- a/config.schema.json +++ b/config.schema.json @@ -169,7 +169,7 @@ }, "type": { "title": "Menu item type", - "description": "One of 'tap', 'template', 'toggle' or 'group'." + "description": "One of 'tap', 'toggle' or 'group'." }, "items": { "type": "array", diff --git a/resources/strings/strings.xml b/resources/strings/strings.xml index f558a53..9ff1d3c 100644 --- a/resources/strings/strings.xml +++ b/resources/strings/strings.xml @@ -13,38 +13,37 @@ --> - HomeAssistant - Sure? - Confirmed - No Phone connection - No Internet connection - No Response, check Internet connection - 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 '/' - Failed to register Webhook - Failed to render template + HomeAssistant Available - Checking... - Unavailable - Unconfigured Cached + Checking... + URL not found. Potential Configuration URL error in settings. + Sure? + Empty + Confirmed Menu Memory - Empty - Template Error - Potential Error + No API key in the application settings. + No API URL in the application settings. + No configuration URL in the application settings. + No Internet connection. + No JSON returned from HTTP request. + No Phone connection. + No Response, check Internet connection PIN input locked for + Potential Error seconds + Template Error + API URL must not have a trailing slash '/'. + Unavailable + Unconfigured + HTTP request returned error code = + Failed to register Webhook Wrong PIN - + Select... API Key for HomeAssistant. Long-Lived Access Token. diff --git a/source/Globals.mc b/source/Globals.mc index 3c40a7d..fbd64f8 100644 --- a/source/Globals.mc +++ b/source/Globals.mc @@ -22,20 +22,19 @@ using Toybox.Lang; (:glance) class Globals { - static const scAlertTimeout = 2000; // ms - static const scTapTimeout = 1000; // ms + 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 + 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 + static const scApiResume = 200; // ms // Warn the user after fetching the menu if their watch is low on memory before the device crashes. - static const scLowMem = 0.90; // percent as a fraction. + static const scLowMem = 0.90; // percent as a fraction. - // constants for PIN confirmation dialog - static const scPinMaxFailures = 5; // maximum number of failed pin confirmation attemps allwed in ... - static const scPinMaxFailureMinutes = 2; // ... this number of minutes before pin confirmation is locked for ... + // Constants for PIN confirmation dialog + static const scPinMaxFailures = 5; // Maximum number of failed PIN confirmation attemps allwed in ... + static const scPinMaxFailureMinutes = 2; // ... this number of minutes before PIN confirmation is locked for ... static const scPinLockTimeMinutes = 10; // ... this number of minutes - } diff --git a/source/HomeAssistantApp.mc b/source/HomeAssistantApp.mc index e86531d..ab437a3 100644 --- a/source/HomeAssistantApp.mc +++ b/source/HomeAssistantApp.mc @@ -99,25 +99,25 @@ class HomeAssistantApp extends Application.AppBase { if (Settings.getApiKey().length() == 0) { // System.println("HomeAssistantApp getInitialView(): No API key in the application Settings."); - return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoAPIKey) as Lang.String + "."); + return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoAPIKey) as Lang.String); } else if (Settings.getApiUrl().length() == 0) { // System.println("HomeAssistantApp getInitialView(): No API URL in the application Settings."); - return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoApiUrl) as Lang.String + "."); + return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoApiUrl) as Lang.String); } else if (Settings.getApiUrl().substring(-1, Settings.getApiUrl().length()).equals("/")) { // System.println("HomeAssistantApp getInitialView(): API URL must not have a trailing slash '/'."); - return ErrorView.create(WatchUi.loadResource($.Rez.Strings.TrailingSlashErr) as Lang.String + "."); + return ErrorView.create(WatchUi.loadResource($.Rez.Strings.TrailingSlashErr) as Lang.String); } else if (Settings.getConfigUrl().length() == 0) { // System.println("HomeAssistantApp getInitialView(): No configuration URL in the application settings."); - return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoConfigUrl) as Lang.String + "."); + return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoConfigUrl) as Lang.String); } else if (Settings.getPin() == null) { // System.println("HomeAssistantApp getInitialView(): Invalid PIN in application settings."); - return ErrorView.create(WatchUi.loadResource($.Rez.Strings.SettingsPinError) as Lang.String + "."); + return ErrorView.create(WatchUi.loadResource($.Rez.Strings.SettingsPinError) as Lang.String); } else if (! System.getDeviceSettings().phoneConnected) { // System.println("HomeAssistantApp getInitialView(): No Phone connection, skipping API call."); - return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } else if (! System.getDeviceSettings().connectionAvailable) { // System.println("HomeAssistantApp getInitialView(): No Internet connection, skipping API call."); - return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); + return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String); } else { var isCached = fetchMenuConfig(); fetchApiStatus(); @@ -142,7 +142,7 @@ class HomeAssistantApp extends Application.AppBase { case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); if (!mIsGlance) { - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } break; @@ -226,7 +226,7 @@ class HomeAssistantApp extends Application.AppBase { if (mIsGlance) { WatchUi.requestUpdate(); } else { - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; } else if (! System.getDeviceSettings().connectionAvailable) { @@ -234,7 +234,7 @@ class HomeAssistantApp extends Application.AppBase { if (mIsGlance) { WatchUi.requestUpdate(); } else { - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String); } mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; } else { @@ -285,7 +285,7 @@ class HomeAssistantApp extends Application.AppBase { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("HomeAssistantApp onReturnUpdateMenuItems() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); break; case Communications.BLE_QUEUE_FULL: @@ -353,11 +353,11 @@ class HomeAssistantApp extends Application.AppBase { function updateMenuItems() as Void { if (! System.getDeviceSettings().phoneConnected) { // System.println("HomeAssistantApp updateMenuItems(): No Phone connection, skipping API call."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); } else if (! System.getDeviceSettings().connectionAvailable) { // System.println("HomeAssistantApp updateMenuItems(): No Internet connection, skipping API call."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String); setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); } else { if (mItemsToUpdate == null or mTemplates == null) { @@ -412,7 +412,7 @@ class HomeAssistantApp extends Application.AppBase { case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); if (!mIsGlance) { - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } break; @@ -475,7 +475,7 @@ class HomeAssistantApp extends Application.AppBase { if (mIsGlance) { WatchUi.requestUpdate(); } else { - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } } else if (! System.getDeviceSettings().connectionAvailable) { // System.println("HomeAssistantApp fetchApiStatus(): No Internet connection, skipping API call."); @@ -483,7 +483,7 @@ class HomeAssistantApp extends Application.AppBase { if (mIsGlance) { WatchUi.requestUpdate(); } else { - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String); } } else { Communications.makeWebRequest( diff --git a/source/HomeAssistantPinConfirmation.mc b/source/HomeAssistantPinConfirmation.mc index 830d365..ab51a98 100644 --- a/source/HomeAssistantPinConfirmation.mc +++ b/source/HomeAssistantPinConfirmation.mc @@ -36,30 +36,30 @@ class PinDigit extends WatchUi.Selectable { x += marginX + HomeAssistantPinConfirmationView.MARGIN_X; var y = (digit == 0) ? stepY * 4 : (digit <= 3) ? stepY : (digit <=6) ? stepY * 2 : stepY * 3; // layout '0' in bottom row (5), others top to bottom in 3 rows (2-4) (row 1 is reserved for masked pin) y += marginY; - var width = stepX - (marginX * 2); + var width = stepX - (marginX * 2); var height = stepY - (marginY * 2); var button = new PinDigitButton({ - :width=>width, - :height=>height, - :label=>digit + :width => width, + :height => height, + :label => digit }); var buttonTouched = new PinDigitButton({ - :width=>width, - :height=>height, - :label=>digit, - :touched=>true + :width => width, + :height => height, + :label => digit, + :touched => true }); // initialize selectable Selectable.initialize({ - :stateDefault=>button, - :stateHighlighted=>buttonTouched, - :locX =>x, - :locY=>y, - :width=>width, - :height=>height + :stateDefault => button, + :stateHighlighted => buttonTouched, + :locX => x, + :locY => y, + :width => width, + :height => height }); mDigit = digit; @@ -71,12 +71,12 @@ class PinDigit extends WatchUi.Selectable { } class PinDigitButton extends WatchUi.Drawable { - private var mText as Number; + private var mText as Number; private var mTouched as Boolean = false; function initialize(options) { Drawable.initialize(options); - mText = options.get(:label); + mText = options.get(:label); mTouched = options.get(:touched); } diff --git a/source/HomeAssistantService.mc b/source/HomeAssistantService.mc index b64a941..ba8e0d7 100644 --- a/source/HomeAssistantService.mc +++ b/source/HomeAssistantService.mc @@ -51,7 +51,7 @@ class HomeAssistantService { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("HomeAssistantService onReturnCall() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); break; case Communications.BLE_QUEUE_FULL: @@ -113,10 +113,10 @@ class HomeAssistantService { ) as Void { if (! System.getDeviceSettings().phoneConnected) { // System.println("HomeAssistantService call(): No Phone connection, skipping API call."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } else if (! System.getDeviceSettings().connectionAvailable) { // System.println("HomeAssistantService call(): No Internet connection, skipping API call."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String); } else { // Can't use null for substring() parameters due to API version level. var url = Settings.getApiUrl() + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, service.length()); diff --git a/source/HomeAssistantToggleMenuItem.mc b/source/HomeAssistantToggleMenuItem.mc index b24ad62..3548d5a 100644 --- a/source/HomeAssistantToggleMenuItem.mc +++ b/source/HomeAssistantToggleMenuItem.mc @@ -124,7 +124,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); break; case Communications.BLE_QUEUE_FULL: @@ -175,11 +175,11 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { setEnabled(!isEnabled()); if (! System.getDeviceSettings().phoneConnected) { // System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } else if (! System.getDeviceSettings().connectionAvailable) { // System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call."); // Toggle the UI back - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String); } else { // Updated SDK and got a new error // ERROR: venu: Cannot find symbol ':substring' on type 'PolyType'. @@ -242,5 +242,4 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { setState(b); } - } diff --git a/source/HomeAssistantView.mc b/source/HomeAssistantView.mc index 76e0edf..9d94e9f 100644 --- a/source/HomeAssistantView.mc +++ b/source/HomeAssistantView.mc @@ -30,8 +30,7 @@ class HomeAssistantView extends WatchUi.Menu2 { definition as Lang.Dictionary, options as { :focus as Lang.Number, - :icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol, - :theme as WatchUi.MenuTheme or Null + :icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol } or Null ) { if (options == null) { @@ -56,7 +55,7 @@ class HomeAssistantView extends WatchUi.Menu2 { if (tap_action != null) { service = tap_action.get("service"); confirm = tap_action.get("confirm"); // Optional - pin = tap_action.get("pin"); // Optional + pin = tap_action.get("pin"); // Optional data = tap_action.get("data"); // Optional if (confirm == null) { confirm = false; diff --git a/source/WebhookManager.mc b/source/WebhookManager.mc index 0d45ef0..8c3b31c 100644 --- a/source/WebhookManager.mc +++ b/source/WebhookManager.mc @@ -34,7 +34,7 @@ class WebhookManager { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("WebhookManager onReturnRequestWebhookId() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); break; case Communications.BLE_QUEUE_FULL: @@ -73,7 +73,7 @@ class WebhookManager { } else { // System.println("WebhookManager onReturnRequestWebhookId(): No webhook id in response data."); Settings.unsetIsSensorsLevelEnabled(); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "."); } break; @@ -121,7 +121,7 @@ class WebhookManager { case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); Settings.unsetWebhookId(); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); break; case Communications.BLE_QUEUE_FULL: @@ -173,7 +173,7 @@ class WebhookManager { // System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure, no 'success'."); Settings.unsetWebhookId(); Settings.unsetIsSensorsLevelEnabled(); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "."); } } else { // !! Speculative code for an application crash !!