diff --git a/source/HomeAssistantApp.mc b/source/HomeAssistantApp.mc index e33dbfe..1ba112d 100644 --- a/source/HomeAssistantApp.mc +++ b/source/HomeAssistantApp.mc @@ -25,8 +25,8 @@ using Toybox.Timer; // (:glance, :background) class HomeAssistantApp extends Application.AppBase { - private var mApiStatus as Lang.String?; private var mHasToast as Lang.Boolean = false; + private var mApiStatus as Lang.String?; private var mMenuStatus as Lang.String?; private var mHaMenu as HomeAssistantView?; private var mGlanceTemplate as Lang.String? = null; @@ -36,14 +36,13 @@ class HomeAssistantApp extends Application.AppBase { private var mUpdateTimer as Timer.Timer?; // Array initialised by onReturnFetchMenuConfig() private var mItemsToUpdate as Lang.Array?; - private var mIsGlance as Lang.Boolean = false; private var mIsApp as Lang.Boolean = false; // Or Widget private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates private var mTemplates as Lang.Dictionary = {}; private var mNotifiedNoBle as Lang.Boolean = false; //! Class Constructor - // + // function initialize() { AppBase.initialize(); // ATTENTION when adding stuff into this block: @@ -155,7 +154,6 @@ class HomeAssistantApp extends Application.AppBase { //! @param responseCode Response code. //! @param data Response data. // - (:glance) function onReturnFetchMenuConfig( responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String @@ -168,35 +166,35 @@ class HomeAssistantApp extends Application.AppBase { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } break; case Communications.BLE_QUEUE_FULL: // System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_QUEUE_FULL, API calls too rapid."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String); } break; case Communications.NETWORK_REQUEST_TIMED_OUT: // System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String); } break; case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE: // System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); } break; case 404: // System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: 404, page not found. Check Configuration URL setting."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String); } break; @@ -212,7 +210,7 @@ class HomeAssistantApp extends Application.AppBase { mMenuStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String; } } - if (mIsGlance) { + if (!mIsApp) { glanceTemplate(data); } else { if (data == null) { @@ -226,7 +224,7 @@ class HomeAssistantApp extends Application.AppBase { default: // System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode); } break; @@ -251,7 +249,6 @@ class HomeAssistantApp extends Application.AppBase { //! @return Return true if the menu came from the cache, otherwise false. This is because fetching //! the menu when not in the cache is asynchronous and affects how the views are managed. // - (:glance) function fetchMenuConfig() as Lang.Boolean { // System.println("Menu URL = " + Settings.getConfigUrl()); if (Settings.getConfigUrl().equals("")) { @@ -275,7 +272,7 @@ class HomeAssistantApp extends Application.AppBase { errorRez = $.Rez.Strings.Unavailable; } // System.println("HomeAssistantApp fetchMenuConfig(): No Phone connection, skipping API call."); - if (mIsGlance) { + if (!mIsApp) { WatchUi.requestUpdate(); } else { ErrorView.show(WatchUi.loadResource(errorRez) as Lang.String); @@ -296,7 +293,7 @@ class HomeAssistantApp extends Application.AppBase { } else { mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String; WatchUi.requestUpdate(); - if (mIsGlance) { + if (!mIsApp) { glanceTemplate(menu); } else { buildMenu(menu); @@ -519,7 +516,6 @@ class HomeAssistantApp extends Application.AppBase { //! @param responseCode Response code. //! @param data Response data. // - (:glance) function onReturnFetchApiStatus( responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String @@ -532,35 +528,35 @@ class HomeAssistantApp extends Application.AppBase { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } break; case Communications.BLE_QUEUE_FULL: // System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_QUEUE_FULL, API calls too rapid."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String); } break; case Communications.NETWORK_REQUEST_TIMED_OUT: // System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String); } break; case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE: // System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); } break; case 404: // System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: 404, page not found. Check Configuration URL setting."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String); } break; @@ -569,7 +565,7 @@ class HomeAssistantApp extends Application.AppBase { if ((data != null) && data.get("message").equals("API running.")) { mApiStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String; } else { - if (!mIsGlance) { + if (mIsApp) { ErrorView.show("API " + mApiStatus + "."); } } @@ -577,7 +573,7 @@ class HomeAssistantApp extends Application.AppBase { default: // System.println("HomeAssistantApp onReturnFetchApiStatus(): Unhandled HTTP response code = " + responseCode); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode); } } @@ -586,7 +582,6 @@ class HomeAssistantApp extends Application.AppBase { //! Construct the GET request to test the API status, is it accessible? // - (:glance) function fetchApiStatus() as Void { var phoneConnected = System.getDeviceSettings().phoneConnected; var connectionAvailable = System.getDeviceSettings().connectionAvailable; @@ -596,13 +591,13 @@ class HomeAssistantApp extends Application.AppBase { mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String; WatchUi.requestUpdate(); } else { - if (! mIsGlance && Settings.getWifiLteExecutionEnabled() && (! phoneConnected || ! connectionAvailable)) { + if ( mIsApp && Settings.getWifiLteExecutionEnabled() && (! phoneConnected || ! connectionAvailable)) { // System.println("HomeAssistantApp fetchApiStatus(): In-app Wifi mode (No Phone and Internet connection), early return."); return; } else if (! phoneConnected) { // System.println("HomeAssistantApp fetchApiStatus(): No Phone connection, skipping API call."); mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; - if (mIsGlance) { + if (!mIsApp) { WatchUi.requestUpdate(); } else { System.println("we here"); @@ -611,7 +606,7 @@ class HomeAssistantApp extends Application.AppBase { } else if (! connectionAvailable) { // System.println("HomeAssistantApp fetchApiStatus(): No Internet connection, skipping API call."); mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; - if (mIsGlance) { + if (!mIsApp) { WatchUi.requestUpdate(); } else { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String); @@ -639,7 +634,6 @@ class HomeAssistantApp extends Application.AppBase { //! @param responseCode Response code. //! @param data Response data. // - (:glance) function onReturnFetchGlanceContent( responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String @@ -651,35 +645,35 @@ class HomeAssistantApp extends Application.AppBase { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String); } break; case Communications.BLE_QUEUE_FULL: // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: BLE_QUEUE_FULL, API calls too rapid."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String); } break; case Communications.NETWORK_REQUEST_TIMED_OUT: // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String); } break; case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE: // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); } break; case 404: // System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: 404, page not found. Check Configuration URL setting."); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String); } break; @@ -692,7 +686,7 @@ class HomeAssistantApp extends Application.AppBase { default: // System.println("HomeAssistantApp onReturnFetchGlanceContent(): Unhandled HTTP response code = " + responseCode); - if (!mIsGlance) { + if (mIsApp) { ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode); } } @@ -701,7 +695,6 @@ class HomeAssistantApp extends Application.AppBase { //! Construct the GET request to convert the optional glance template to text for display. // - (:glance) function fetchGlanceContent() as Void { if (mGlanceTemplate != null) { // https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates @@ -739,7 +732,6 @@ class HomeAssistantApp extends Application.AppBase { //! //! @return A string describing the API status // - (:glance) function getApiStatus() as Lang.String { return mApiStatus; } @@ -748,7 +740,6 @@ class HomeAssistantApp extends Application.AppBase { //! //! @return A string describing the Menu status // - (:glance) function getMenuStatus() as Lang.String { return mMenuStatus; } @@ -758,7 +749,6 @@ class HomeAssistantApp extends Application.AppBase { //! //! @return A string derived from the glance template (or null) // - (:glance) function getGlanceText() as Lang.String? { return mGlanceText; } @@ -802,7 +792,7 @@ class HomeAssistantApp extends Application.AppBase { //! @return The glance view // function getGlanceView() as [ WatchUi.GlanceView ] or [ WatchUi.GlanceView, WatchUi.GlanceViewDelegate ] or Null { - mIsGlance = true; + mIsApp = false; // A bit unnecessary given the default mApiStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String; mMenuStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String; Settings.update(); @@ -826,7 +816,9 @@ class HomeAssistantApp extends Application.AppBase { mGlanceTimer = null; fetchMenuConfig(); fetchApiStatus(); - fetchGlanceContent(); + if (Settings.getWebhookId() != null && !Settings.getWebhookId().equals("")) { + fetchGlanceContent(); + } } //! Code for when the application settings are updated. @@ -844,6 +836,8 @@ class HomeAssistantApp extends Application.AppBase { } //! Determine is we are a glance or the full application. Glances should be considered to be separate applications. + //! + //! @return We are an application (if not we're a glance) // function getIsApp() as Lang.Boolean { return mIsApp; @@ -859,6 +853,8 @@ class HomeAssistantApp extends Application.AppBase { } //! Global function to return the application object. +//! +//! @return The application object. // (:glance, :background) function getApp() as HomeAssistantApp { diff --git a/source/Settings.mc b/source/Settings.mc index 5b23c26..17fa85d 100644 --- a/source/Settings.mc +++ b/source/Settings.mc @@ -297,7 +297,7 @@ class Settings { //! @return The augmented HTTP header options. // static function augmentHttpHeaders(options as Lang.Dictionary) { - // Use 'm.length() > 0' here in preference to 'm != ""'. The latter makes the app crash on device but not in simulation. + // Use 'm.length() > 0' here in preference to 'm != ""' or '.equals("")'. They make the App crash on device but not in simulation. if (mUserHeaderName != null && mUserHeaderName.length() > 0 && mUserHeaderValue != null && mUserHeaderValue.length() > 0) { options[mUserHeaderName] = mUserHeaderValue; } diff --git a/source/WebhookManager.mc b/source/WebhookManager.mc index 6d43835..5260ce0 100644 --- a/source/WebhookManager.mc +++ b/source/WebhookManager.mc @@ -25,7 +25,9 @@ using Toybox.WatchUi; //! //! Reference: https://developers.home-assistant.io/docs/api/native-app-integration // +(:glance) class WebhookManager { + private var mSensors as Lang.Array = []; //! Callback for requesting a Webhook ID. //! @@ -130,11 +132,7 @@ class WebhookManager { //! @param sensors The remaining sensors to be processed. The list of sensors is iterated through //! until empty. Each POST request creating one sensor on the local Home Assistant. // - function onReturnRegisterWebhookSensor( - responseCode as Lang.Number, - data as Null or Lang.Dictionary or Lang.String, - sensors as Lang.Array - ) as Void { + function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void { switch (responseCode) { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: @@ -176,13 +174,14 @@ class WebhookManager { case 200: case 201: if (data instanceof Lang.Dictionary) { - var d = data as Lang.Dictionary; + var d = data as Lang.Dictionary; var b = d.get("success") as Lang.Boolean?; if (b != null and b != false) { - if (sensors.size() == 0) { + mSensors = mSensors.slice(1, null); + if (mSensors.size() == 0) { getApp().startUpdates(); } else { - registerWebhookSensor(sensors); + registerWebhookSensor(); } } else { // System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure, no 'success'."); @@ -213,11 +212,10 @@ class WebhookManager { } } - //! Local method to send the POST request to register a number of sensors. - //! - //! @param sensors An array of sensors, e.g. As created by `registerWebhookSensors()`. + //! Local method to send the POST request to register a number of sensors. The sensors are taken from the class variable + //! mSensors created by registerWebhookSensors(). // - private function registerWebhookSensor(sensors as Lang.Array) { + private function registerWebhookSensor() { var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(); // System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString()); // System.println("WebhookManager registerWebhookSensor(): URL=" + url); @@ -226,15 +224,14 @@ class WebhookManager { url, { "type" => "register_sensor", - "data" => sensors[0] + "data" => mSensors[0] }, { :method => Communications.HTTP_REQUEST_METHOD_POST, :headers => Settings.augmentHttpHeaders({ "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON }), - :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON, - :context => sensors.slice(1, null) + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON }, method(:onReturnRegisterWebhookSensor) ); @@ -245,7 +242,7 @@ class WebhookManager { function registerWebhookSensors() { var heartRate = Activity.getActivityInfo().currentHeartRate; - var sensors = [ + mSensors = [ { "device_class" => "battery", "name" => "Battery Level", @@ -283,7 +280,7 @@ class WebhookManager { if (Toybox has :ActivityMonitor) { // System.println("WebhookManager registerWebhookSensors(): has ActivityMonitor class"); var activityInfo = ActivityMonitor.getInfo(); - sensors.add({ + mSensors.add({ "name" => "Steps today", "state" => activityInfo.steps == null ? "unknown" : activityInfo.steps, "type" => "sensor", @@ -294,7 +291,7 @@ class WebhookManager { }); if (ActivityMonitor.Info has :floorsClimbed) { - sensors.add({ + mSensors.add({ "name" => "Floors climbed today", "state" => activityInfo.floorsClimbed == null ? "unknown" : activityInfo.floorsClimbed, "type" => "sensor", @@ -306,7 +303,7 @@ class WebhookManager { } if (ActivityMonitor.Info has :floorsDescended) { - sensors.add({ + mSensors.add({ "name" => "Floors descended today", "state" => activityInfo.floorsDescended == null ? "unknown" : activityInfo.floorsDescended, "type" => "sensor", @@ -318,7 +315,7 @@ class WebhookManager { } if (ActivityMonitor.Info has :respirationRate) { - sensors.add({ + mSensors.add({ "name" => "Respiration rate", "state" => activityInfo.respirationRate == null ? "unknown" : activityInfo.respirationRate, "type" => "sensor", @@ -345,14 +342,14 @@ class WebhookManager { activity = -1; sub_activity = -1; } - sensors.add({ + mSensors.add({ "name" => "Activity", "state" => activity, "type" => "sensor", "unique_id" => "activity", "disabled" => !Settings.isSensorsLevelEnabled() }); - sensors.add({ + mSensors.add({ "name" => "Sub-activity", "state" => sub_activity, "type" => "sensor", @@ -361,7 +358,7 @@ class WebhookManager { }); } - registerWebhookSensor(sensors); + registerWebhookSensor(); } }