Fix to prevent out of memory error in a glance

This commit also includes a minor code tidy for constants, to make their format consistent.
This commit is contained in:
Philip Abbey
2026-01-25 16:39:59 +00:00
parent 34d13865e9
commit a3ffabae27
3 changed files with 37 additions and 25 deletions

View File

@@ -27,6 +27,8 @@ using Toybox.Timer;
// //
(:glance, :background) (:glance, :background)
class HomeAssistantApp extends Application.AppBase { class HomeAssistantApp extends Application.AppBase {
static const scStorageKeyMenu as Lang.String = "menu";
private var mHasToast as Lang.Boolean = false; private var mHasToast as Lang.Boolean = false;
private var mApiStatus as Lang.String?; private var mApiStatus as Lang.String?;
private var mMenuStatus as Lang.String?; private var mMenuStatus as Lang.String?;
@@ -207,7 +209,15 @@ class HomeAssistantApp extends Application.AppBase {
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
} else { } else {
if (Settings.getCacheConfig()) { if (Settings.getCacheConfig()) {
Storage.setValue("menu", data as Lang.Dictionary); // var stats = System.getSystemStats(); // stats.* values in bytes
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Memory: total=" + stats.totalMemory + ", used=" + stats.usedMemory + ", free=" + stats.freeMemory);
if (mIsApp) {
// This call can crash a glance with "Error: Out Of Memory Error"
// https://forums.garmin.com/developer/connect-iq/i/bug-reports/storage-setvalue-should-handle-memory-limits-gracefully
// "Keys and values are limited to 8 KB each, and a total of 128 KB of storage is available."
// "Storage.setValue() fails with an uncatchable out-of-memory error."
Storage.setValue(scStorageKeyMenu, data as Lang.Dictionary);
}
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String; mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String;
} else { } else {
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String; mMenuStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
@@ -246,7 +256,7 @@ class HomeAssistantApp extends Application.AppBase {
if (Settings.getClearCache() || !Settings.getCacheConfig()) { if (Settings.getClearCache() || !Settings.getCacheConfig()) {
return false; return false;
} }
return (Storage.getValue("menu") as Lang.Dictionary) != null; return (Storage.getValue(scStorageKeyMenu) as Lang.Dictionary) != null;
} }
//! Fetch the menu configuration over HTTPS, which might be locally cached. //! Fetch the menu configuration over HTTPS, which might be locally cached.
@@ -260,10 +270,10 @@ class HomeAssistantApp extends Application.AppBase {
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String; mMenuStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
WatchUi.requestUpdate(); WatchUi.requestUpdate();
} else { } else {
var menu = Storage.getValue("menu") as Lang.Dictionary; var menu = Storage.getValue(scStorageKeyMenu) as Lang.Dictionary;
if (menu != null and (Settings.getClearCache() || !Settings.getCacheConfig())) { if (menu != null and (Settings.getClearCache() || !Settings.getCacheConfig())) {
// System.println("HomeAssistantApp fetchMenuConfig(): Clearing cached menu on user request."); // System.println("HomeAssistantApp fetchMenuConfig(): Clearing cached menu on user request.");
Storage.deleteValue("menu"); Storage.deleteValue(scStorageKeyMenu);
menu = null; menu = null;
Settings.unsetClearCache(); Settings.unsetClearCache();
} }
@@ -524,10 +534,10 @@ class HomeAssistantApp extends Application.AppBase {
case 200: case 200:
if (data != null) { if (data != null) {
// 'menu' will be null if caching has just been enabled, but not yet cached locally. // 'menu' will be null if caching has just been enabled, but not yet cached locally.
var menu = Storage.getValue("menu") as Lang.Dictionary; var menu = Storage.getValue(scStorageKeyMenu) as Lang.Dictionary;
if (menu == null || !structuralEquals(data, menu)) { if (menu == null || !structuralEquals(data, menu)) {
// System.println("HomeAssistantApp onReturnCheckMenuConfig() New menu found."); // System.println("HomeAssistantApp onReturnCheckMenuConfig() New menu found.");
Storage.setValue("menu", data as Lang.Dictionary); Storage.setValue(scStorageKeyMenu, data as Lang.Dictionary);
if (menu != null) { if (menu != null) {
// Notify the the user we have just got a newer menu file // Notify the the user we have just got a newer menu file
var toast = WatchUi.loadResource($.Rez.Strings.MenuUpdated) as Lang.String; var toast = WatchUi.loadResource($.Rez.Strings.MenuUpdated) as Lang.String;

View File

@@ -34,7 +34,7 @@ class PinDigit extends WatchUi.Selectable {
// //
function initialize(digit as Lang.Number, stepX as Lang.Number, stepY as Lang.Number) { function initialize(digit as Lang.Number, stepX as Lang.Number, stepY as Lang.Number) {
var marginX = stepX * 0.05; // 5% margin on all sides var marginX = stepX * 0.05; // 5% margin on all sides
var marginY = stepY * 0.05; var marginY = stepY * 0.05;
var x = (digit == 0) ? stepX : stepX * ((digit+2) % 3); // layout '0' in 2nd col, others ltr in 3 columns var x = (digit == 0) ? stepX : stepX * ((digit+2) % 3); // layout '0' in 2nd col, others ltr in 3 columns
x += marginX + HomeAssistantPinConfirmationView.MARGIN_X; 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) 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)
@@ -216,7 +216,7 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
mFailures = new PinFailures(); mFailures = new PinFailures();
if (mFailures.isLocked()) { if (mFailures.isLocked()) {
var msg = WatchUi.loadResource($.Rez.Strings.PinInputLocked) + " " + var msg = WatchUi.loadResource($.Rez.Strings.PinInputLocked) + " " +
mFailures.getLockedUntilSeconds() + " " + mFailures.getLockedUntilSeconds() + " " +
WatchUi.loadResource($.Rez.Strings.Seconds); WatchUi.loadResource($.Rez.Strings.Seconds);
WatchUi.showToast(msg, {}); WatchUi.showToast(msg, {});
} }
@@ -298,7 +298,7 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
if (mTimer != null) { if (mTimer != null) {
mTimer.stop(); mTimer.stop();
} }
WatchUi.popView(WatchUi.SLIDE_RIGHT); WatchUi.popView(WatchUi.SLIDE_RIGHT);
} }
@@ -310,7 +310,7 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
if (Attention has :vibrate && Settings.getVibrate()) { if (Attention has :vibrate && Settings.getVibrate()) {
Attention.vibrate([ Attention.vibrate([
new Attention.VibeProfile(100, 100), new Attention.VibeProfile(100, 100),
new Attention.VibeProfile( 0, 200), new Attention.VibeProfile( 0, 200),
new Attention.VibeProfile( 75, 100), new Attention.VibeProfile( 75, 100),
new Attention.VibeProfile( 0, 200), new Attention.VibeProfile( 0, 200),
new Attention.VibeProfile( 50, 100), new Attention.VibeProfile( 50, 100),
@@ -337,10 +337,10 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
//! Manage PIN entry failures to try and prevent brute force exhaustion by inserting delays in retries. //! Manage PIN entry failures to try and prevent brute force exhaustion by inserting delays in retries.
// //
class PinFailures { class PinFailures {
const STORAGE_KEY_FAILURES as Lang.String = "pin_failures"; static const scStorageKeyFailures as Lang.String = "pin_failures";
const STORAGE_KEY_LOCKED as Lang.String = "pin_locked"; static const scStorageKeyLocked as Lang.String = "pin_locked";
private var mFailures as Lang.Array<Lang.Number>; private var mFailures as Lang.Array<Lang.Number>;
private var mLockedUntil as Lang.Number?; private var mLockedUntil as Lang.Number?;
@@ -348,9 +348,9 @@ class PinFailures {
// //
function initialize() { function initialize() {
// System.println("PinFailures initialize() Initializing PIN failures from storage"); // System.println("PinFailures initialize() Initializing PIN failures from storage");
var failures = Application.Storage.getValue(PinFailures.STORAGE_KEY_FAILURES); var failures = Application.Storage.getValue(scStorageKeyFailures);
mFailures = (failures == null) ? [] : failures; mFailures = (failures == null) ? [] : failures;
mLockedUntil = Application.Storage.getValue(PinFailures.STORAGE_KEY_LOCKED); mLockedUntil = Application.Storage.getValue(scStorageKeyLocked);
} }
//! Record a PIN entry failure. If too many have occurred lock the application. //! Record a PIN entry failure. If too many have occurred lock the application.
@@ -368,11 +368,11 @@ class PinFailures {
} else { } else {
mFailures = []; mFailures = [];
mLockedUntil = Time.now().add(new Time.Duration(Globals.scPinLockTimeMinutes * Time.Gregorian.SECONDS_PER_MINUTE)).value(); mLockedUntil = Time.now().add(new Time.Duration(Globals.scPinLockTimeMinutes * Time.Gregorian.SECONDS_PER_MINUTE)).value();
Application.Storage.setValue(STORAGE_KEY_LOCKED, mLockedUntil); Application.Storage.setValue(scStorageKeyLocked, mLockedUntil);
// System.println("PinFailures addFailure() Locked until " + mLockedUntil); // System.println("PinFailures addFailure() Locked until " + mLockedUntil);
} }
} }
Application.Storage.setValue(STORAGE_KEY_FAILURES, mFailures); Application.Storage.setValue(scStorageKeyFailures, mFailures);
} }
//! Clear the record of previous PIN entry failures, e.g. because the correct PIN has now been entered //! Clear the record of previous PIN entry failures, e.g. because the correct PIN has now been entered
@@ -382,8 +382,8 @@ class PinFailures {
// System.println("PinFailures reset() Resetting failures"); // System.println("PinFailures reset() Resetting failures");
mFailures = []; mFailures = [];
mLockedUntil = null; mLockedUntil = null;
Application.Storage.deleteValue(STORAGE_KEY_FAILURES); Application.Storage.deleteValue(scStorageKeyFailures);
Application.Storage.deleteValue(STORAGE_KEY_LOCKED); Application.Storage.deleteValue(scStorageKeyLocked);
} }
//! Retrieve the remaining time the application must be locked out for. //! Retrieve the remaining time the application must be locked out for.
@@ -399,12 +399,12 @@ class PinFailures {
// //
function isLocked() as Lang.Boolean { function isLocked() as Lang.Boolean {
if (mLockedUntil == null) { if (mLockedUntil == null) {
return false; return false;
} }
var isLocked = new Time.Moment(Time.now().value()).lessThan(new Time.Moment(mLockedUntil)); var isLocked = new Time.Moment(Time.now().value()).lessThan(new Time.Moment(mLockedUntil));
if (!isLocked) { if (!isLocked) {
mLockedUntil = null; mLockedUntil = null;
Application.Storage.deleteValue(STORAGE_KEY_LOCKED); Application.Storage.deleteValue(scStorageKeyLocked);
} }
return isLocked; return isLocked;
} }

View File

@@ -29,6 +29,8 @@ using Toybox.Time;
// //
(:glance, :background) (:glance, :background)
class Settings { class Settings {
static const scStorageKeySensorsEn as Lang.String = "sensors_enabled";
private static var mApiKey as Lang.String? = ""; private static var mApiKey as Lang.String? = "";
private static var mWebhookId as Lang.String? = ""; private static var mWebhookId as Lang.String? = "";
private static var mApiUrl as Lang.String? = ""; private static var mApiUrl as Lang.String? = "";
@@ -115,9 +117,9 @@ class Settings {
} else { } else {
// System.println("Settings update(): Doing just sensor creation."); // System.println("Settings update(): Doing just sensor creation.");
// We already have a Webhook ID, so just enable or disable the sensor in Home Assistant. // We already have a Webhook ID, so just enable or disable the sensor in Home Assistant.
// Storage.getValue("sensors_enabled") returns true, false, or null // Storage.getValue(scStorageKeySensorsEn) returns true, false, or null
if (mIsSensorsEnabled != Storage.getValue("sensors_enabled")) { if (mIsSensorsEnabled != Storage.getValue(scStorageKeySensorsEn)) {
Storage.setValue("sensors_enabled", mIsSensorsEnabled); Storage.setValue(scStorageKeySensorsEn, mIsSensorsEnabled);
mWebhookManager.registerWebhookSensors(); mWebhookManager.registerWebhookSensors();
} }
} }