An assortment of cosmetic changes

JSON schema fix to remove 'template' as a valid option, punctuation in strings, whitespace, and a new application history entry.
This commit is contained in:
Philip Abbey
2024-11-18 19:39:13 +00:00
parent fb68ce2fba
commit 11ecf88ee2
10 changed files with 75 additions and 78 deletions

View File

@ -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! |

View File

@ -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",

View File

@ -13,38 +13,37 @@
-->
<strings>
<string id="AppName" scope="glance">HomeAssistant</string>
<string id="Confirm">Sure?</string>
<string id="Executed" scope="glance">Confirmed</string>
<string id="NoPhone" scope="glance">No Phone connection</string>
<string id="NoInternet">No Internet connection</string>
<string id="NoResponse">No Response, check Internet connection</string>
<string id="NoAPIKey" scope="glance">No API key in the application settings</string>
<string id="NoApiUrl" scope="glance">No API URL in the application settings</string>
<string id="NoConfigUrl" scope="glance">No configuration URL in the application settings</string>
<string id="ApiFlood">API calls too rapid. Please slow down your requests.</string>
<string id="ApiUrlNotFound">URL not found. Potential API URL error in settings.</string>
<string id="ConfigUrlNotFound">URL not found. Potential Configuration URL error in settings.</string>
<string id="NoJson">No JSON returned from HTTP request.</string>
<string id="UnhandledHttpErr">HTTP request returned error code = </string>
<string id="TrailingSlashErr">API URL must not have a trailing slash '/'</string>
<string id="WebhookFailed">Failed to register Webhook</string>
<string id="TemplateError">Failed to render template</string>
<string id="AppName" scope="glance">HomeAssistant</string>
<string id="Available" scope="glance">Available</string>
<string id="Checking" scope="glance">Checking...</string>
<string id="Unavailable" scope="glance">Unavailable</string>
<string id="Unconfigured" scope="glance">Unconfigured</string>
<string id="Cached" scope="glance">Cached</string>
<string id="Checking" scope="glance">Checking...</string>
<string id="ConfigUrlNotFound">URL not found. Potential Configuration URL error in settings.</string>
<string id="Confirm">Sure?</string>
<string id="Empty">Empty</string>
<string id="Executed" scope="glance">Confirmed</string>
<string id="GlanceMenu" scope="glance">Menu</string>
<string id="Memory" scope="glance">Memory</string>
<string id="Empty">Empty</string>
<string id="TemplateError">Template Error</string>
<string id="PotentialError">Potential Error</string>
<string id="NoAPIKey" scope="glance">No API key in the application settings.</string>
<string id="NoApiUrl" scope="glance">No API URL in the application settings.</string>
<string id="NoConfigUrl" scope="glance">No configuration URL in the application settings.</string>
<string id="NoInternet">No Internet connection.</string>
<string id="NoJson">No JSON returned from HTTP request.</string>
<string id="NoPhone" scope="glance">No Phone connection.</string>
<string id="NoResponse">No Response, check Internet connection</string>
<string id="PinInputLocked">PIN input locked for</string>
<string id="PotentialError">Potential Error</string>
<string id="Seconds">seconds</string>
<string id="TemplateError">Template Error</string>
<string id="TrailingSlashErr">API URL must not have a trailing slash '/'.</string>
<string id="Unavailable" scope="glance">Unavailable</string>
<string id="Unconfigured" scope="glance">Unconfigured</string>
<string id="UnhandledHttpErr">HTTP request returned error code = </string>
<string id="WebhookFailed">Failed to register Webhook</string>
<string id="WrongPin">Wrong PIN</string>
<!-- For the settings GUI -->
<!-- For the settings GUI, strings should be in the order they are used. -->
<string id="SettingsSelect">Select...</string>
<string id="SettingsApiKey">API Key for HomeAssistant.</string>
<string id="SettingsApiKeyPrompt">Long-Lived Access Token.</string>

View File

@ -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
}

View File

@ -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(

View File

@ -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);
}

View File

@ -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());

View File

@ -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<Null or $.Toybox.Lang.Object>'.
@ -242,5 +242,4 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
setState(b);
}
}

View File

@ -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;

View File

@ -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 !!