diff --git a/README.md b/README.md index 4ec9ee1..397adcf 100644 --- a/README.md +++ b/README.md @@ -276,8 +276,6 @@ The `id` attribute values are taken from the same names used in [`strings.xml`]( ## Battery Level Reporting -We've had [a report](https://github.com/house-of-abbey/GarminHomeAssistant/issues/39) that this feature does not work without **administrator priviledges**. We've reviewed possible fixes and come up short. We are unable to fix this at present but invite those skilled in the art of Home Assistant to suggest a solution to us! - The application and widget both now include a background service to report your watch's battery level and charging status. This requires some [setup](BatteryReporting.md) via YAML in Home Assistant to display the transmitted value. We offer this [trouble shooting](Troubleshooting.md#watch-battery-level-reporting) guide. ## Version History @@ -296,6 +294,7 @@ The application and widget both now include a background service to report your | 2.1 | Deployment of an idea to provide Home Assistant with access to the watch battery level. Using this requires [significant setup](BatteryReporting.md) on the Home Assistant configuration and hence is detailed separately. Due to this, the default state for this battery option is _off_. Changed the application settings user interface to be more intuitive, and hence amended the way settings are managed in the background. | | 2.2 | Adds a feature to cache the menu configuration and save the time taken for an HTTP request to fetch it. You as the user are responsible for managing the cache by clearing it when you update your configuration. Improvement to widget root display updates. Bug fix for battery level reporting when in the glance carousel. Fixed an uninternationalised string, "Execute". Unfixed issue with battery level updates when the user is not an administrator. | | 2.3 | Fix for battery level updates where previously the function only worked for administrator accounts. The new solution is based on Webhooks and is simpler to implement on Home Assistant. Language support fix where an automatic translation produced an inappropriate word, possibly in more than one language. | +| 2.4 | Sensor status reporting via Home Assistant 'templates'. This provides a generalised way of viewing the status of any entity as long as the result can be rendered as text, e.g. 'uncovered', 'open', '76%', '21 °C'. | ## Known Issues diff --git a/resources/strings/strings.xml b/resources/strings/strings.xml index 34930e1..e696792 100644 --- a/resources/strings/strings.xml +++ b/resources/strings/strings.xml @@ -36,6 +36,7 @@ Unconfigured Cached Menu + Memory Select... diff --git a/source/Alert.mc b/source/Alert.mc index 121d31e..51ca1dc 100644 --- a/source/Alert.mc +++ b/source/Alert.mc @@ -29,12 +29,13 @@ using Toybox.Timer; class Alert extends WatchUi.View { 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; + 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; + private var mAntiAlias as Lang.Boolean = false; function initialize(params as Lang.Dictionary) { View.initialize(); @@ -64,6 +65,10 @@ class Alert extends WatchUi.View { mTimeout = 2000; } + if (Graphics.Dc has :setAntiAlias) { + mAntiAlias = true; + } + mTimer = new Timer.Timer(); } @@ -75,7 +80,7 @@ class Alert extends WatchUi.View { mTimer.stop(); } - function onUpdate(dc) { + function onUpdate(dc as Graphics.Dc) { var tWidth = dc.getTextWidthInPixels(mText, mFont); var tHeight = dc.getFontHeight(mFont); var bWidth = tWidth + 20; @@ -83,7 +88,7 @@ class Alert extends WatchUi.View { var bX = (dc.getWidth() - bWidth) / 2; var bY = (dc.getHeight() - bHeight) / 2; - if (dc has :setAntiAlias) { + if (mAntiAlias) { dc.setAntiAlias(true); } diff --git a/source/ErrorView.mc b/source/ErrorView.mc index 729851d..114a763 100644 --- a/source/ErrorView.mc +++ b/source/ErrorView.mc @@ -44,7 +44,8 @@ class ErrorView extends ScalableView { // Vertical spacing between the top of the face and the error icon private var mErrorIconMargin as Lang.Number; private var mErrorIcon; - private var mTextArea; + private var mTextArea as WatchUi.TextArea or Null; + private var mAntiAlias as Lang.Boolean = false; private static var instance; private static var mShown as Lang.Boolean = false; @@ -55,6 +56,9 @@ class ErrorView extends ScalableView { // 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; + if (Graphics.Dc has :setAntiAlias) { + mAntiAlias = true; + } } // Load your resources here @@ -76,7 +80,7 @@ class ErrorView extends ScalableView { // Update the view function onUpdate(dc as Graphics.Dc) as Void { var w = dc.getWidth(); - if(dc has :setAntiAlias) { + if (mAntiAlias) { dc.setAntiAlias(true); } dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLUE); diff --git a/source/Globals.mc b/source/Globals.mc index 8fbf034..a464a12 100644 --- a/source/Globals.mc +++ b/source/Globals.mc @@ -33,4 +33,6 @@ class Globals { // Needs to be long enough to enable a "double ESC" to quit the application from // an ErrorView. static const scApiResume = 200; // ms + // Warn the user after fetching the menu if their watch is low on memory before the device crashes. + static const scLowMem = 0.95; // percent as a fraction. } diff --git a/source/HomeAssistantGlanceView.mc b/source/HomeAssistantGlanceView.mc index 123228b..9479b8f 100644 --- a/source/HomeAssistantGlanceView.mc +++ b/source/HomeAssistantGlanceView.mc @@ -32,10 +32,14 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView { 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 mAntiAlias as Lang.Boolean = false; function initialize(app as HomeAssistantApp) { GlanceView.initialize(); mApp = app; + if (Graphics.Dc has :setAntiAlias) { + mAntiAlias = true; + } } function onLayout(dc as Graphics.Dc) as Void { @@ -85,9 +89,9 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView { }); } - function onUpdate(dc) as Void { + function onUpdate(dc as Graphics.Dc) as Void { GlanceView.onUpdate(dc); - if(dc has :setAntiAlias) { + if(mAntiAlias) { dc.setAntiAlias(true); } dc.setColor( diff --git a/source/HomeAssistantService.mc b/source/HomeAssistantService.mc index 624ee5c..77b4996 100644 --- a/source/HomeAssistantService.mc +++ b/source/HomeAssistantService.mc @@ -24,6 +24,17 @@ using Toybox.Graphics; using Toybox.Application.Properties; class HomeAssistantService { + private var mHasToast as Lang.Boolean = false; + private var mHasVibrate as Lang.Boolean = false; + + function initialise() { + if (WatchUi has :showToast) { + mHasToast = true; + } + if (Attention has :vibrate) { + mHasVibrate = true; + } + } // Callback function after completing the POST request to call a service. // @@ -88,7 +99,7 @@ class HomeAssistantService { toast = (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String; } } - if (WatchUi has :showToast) { + if (mHasToast) { WatchUi.showToast(toast, null); } else { new Alert({ @@ -143,7 +154,7 @@ class HomeAssistantService { }, method(:onReturnCall) ); - if (Attention has :vibrate) { + if (mHasVibrate) { Attention.vibrate([ new Attention.VibeProfile(50, 100), // On for 100ms new Attention.VibeProfile( 0, 100), // Off for 100ms diff --git a/source/HomeAssistantViewMenuItem.mc b/source/HomeAssistantViewMenuItem.mc index 6b81c23..5a92d55 100644 --- a/source/HomeAssistantViewMenuItem.mc +++ b/source/HomeAssistantViewMenuItem.mc @@ -9,7 +9,7 @@ // 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, 16 November 2023 +// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 // // // Description: diff --git a/source/RezStrings.mc b/source/RezStrings.mc index 3a03eaf..8405f68 100644 --- a/source/RezStrings.mc +++ b/source/RezStrings.mc @@ -59,6 +59,7 @@ class RezStrings { private static var strCached as Lang.String or Null; (:glance) private static var strGlanceMenu as Lang.String or Null; + private static var strMemory as Lang.String or Null; // Can't initialise a constant directly, have to be initialised via a function // for 'WatchUi.loadResource' to be available. @@ -103,6 +104,7 @@ class RezStrings { strUnconfigured = WatchUi.loadResource($.Rez.Strings.Unconfigured); strCached = WatchUi.loadResource($.Rez.Strings.Cached); strGlanceMenu = WatchUi.loadResource($.Rez.Strings.GlanceMenu); + strMemory = WatchUi.loadResource($.Rez.Strings.Memory); } static function getAppName() as Lang.String { @@ -197,4 +199,8 @@ class RezStrings { return strGlanceMenu; } + static function getMemory() as Lang.String { + return strMemory; + } + } diff --git a/source/RootView.mc b/source/RootView.mc index ac6b581..478c4fc 100644 --- a/source/RootView.mc +++ b/source/RootView.mc @@ -21,6 +21,7 @@ using Toybox.Graphics; using Toybox.Lang; using Toybox.WatchUi; +using Toybox.System; class RootView extends ScalableView { @@ -44,10 +45,16 @@ class RootView extends ScalableView { 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 mMemText as WatchUi.Text or Null; + private var mMemStatus as WatchUi.Text or Null; + private var mAntiAlias as Lang.Boolean = false; function initialize(app as HomeAssistantApp) { ScalableView.initialize(); mApp = app; + if (Graphics.Dc has :setAntiAlias) { + mAntiAlias = true; + } } function onLayout(dc as Graphics.Dc) as Void { @@ -84,9 +91,25 @@ class RootView extends ScalableView { :font => Graphics.FONT_XTINY, :justification => Graphics.TEXT_JUSTIFY_RIGHT | Graphics.TEXT_JUSTIFY_VCENTER, :locX => w/2 - scMidSep/2, - :locY => pixelsForScreen(70.0) + :locY => pixelsForScreen(60.0) }); mMenuStatus = new WatchUi.Text({ + :text => RezStrings.getChecking(), + :color => Graphics.COLOR_WHITE, + :font => Graphics.FONT_XTINY, + :justification => Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER, + :locX => w/2 + scMidSep/2, + :locY => pixelsForScreen(60.0) + }); + mMemText = new WatchUi.Text({ + :text => RezStrings.getMemory() + ":", + :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) + }); + mMemStatus = new WatchUi.Text({ :text => RezStrings.getChecking(), :color => Graphics.COLOR_WHITE, :font => Graphics.FONT_XTINY, @@ -97,7 +120,7 @@ class RootView extends ScalableView { } function onUpdate(dc as Graphics.Dc) as Void { - if (dc has :setAntiAlias) { + if (mAntiAlias) { dc.setAntiAlias(true); } dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK); @@ -114,6 +137,16 @@ class RootView extends ScalableView { mMenuText.draw(dc); mMenuStatus.setText(mApp.getMenuStatus()); mMenuStatus.draw(dc); + mMemText.draw(dc); + var stats = System.getSystemStats(); + var memUsed = (100 * stats.usedMemory) / stats.totalMemory; + mMemStatus.setText(memUsed.format("%.1f") + "%"); + if (stats.usedMemory > Globals.scLowMem * stats.totalMemory) { + mMemStatus.setColor(Graphics.COLOR_RED); + } else { + mMemStatus.setColor(Graphics.COLOR_WHITE); + } + mMemStatus.draw(dc); } function onShow() as Void { diff --git a/source/Settings.mc b/source/Settings.mc index d381660..b3f6727 100644 --- a/source/Settings.mc +++ b/source/Settings.mc @@ -41,6 +41,7 @@ class Settings { private static var mIsBatteryLevelEnabled as Lang.Boolean = false; private static var mBatteryRefreshRate as Lang.Number = 15; // minutes private static var mIsApp as Lang.Boolean = false; + private static var mHasService as Lang.Boolean = false; // Must keep the object so it doesn't get garbage collected. private static var mWebhookManager as WebhookManager or Null; @@ -61,6 +62,10 @@ class Settings { mIsBatteryLevelEnabled = Properties.getValue("enable_battery_level"); mBatteryRefreshRate = Properties.getValue("battery_level_refresh_rate"); + if (System has :ServiceDelegate) { + mHasService = true; + } + // Manage this inside the application or widget only (not a glance or background service process) if (mIsApp) { if (mIsBatteryLevelEnabled) { @@ -68,14 +73,14 @@ class Settings { mWebhookManager = new WebhookManager(); mWebhookManager.requestWebhookId(); } else if ( - (System has :ServiceDelegate) and + mHasService and ((Background.getTemporalEventRegisteredTime() == null) or (Background.getTemporalEventRegisteredTime() != (mBatteryRefreshRate * 60)))) { Background.registerForTemporalEvent(new Time.Duration(mBatteryRefreshRate * 60)); // Convert to seconds } } else { // Explicitly disable the background event which persists when the application closes. - if ((System has :ServiceDelegate) and (Background.getTemporalEventRegisteredTime() != null)) { + if (mHasService and (Background.getTemporalEventRegisteredTime() != null)) { Background.deleteTemporalEvent(); } unsetWebhookId(); @@ -149,7 +154,7 @@ class Settings { static function unsetIsBatteryLevelEnabled() { mIsBatteryLevelEnabled = false; Properties.setValue("enable_battery_level", mIsBatteryLevelEnabled); - if ((System has :ServiceDelegate) and (Background.getTemporalEventRegisteredTime() != null)) { + if (mHasService and (Background.getTemporalEventRegisteredTime() != null)) { Background.deleteTemporalEvent(); } }