diff --git a/HISTORY.md b/HISTORY.md index f433cb5..c0e798d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -24,8 +24,8 @@ | 2.9 | Added an option to enable confirmation vibration so it can be turned off by request of a user. Removed a redundant setting for the alternative Widget version that was not removed previously, and fixed a bug with dereferencing Null. | | 2.10 | Added a user requested feature to slow down the rate of API calls in order to reduce battery wear for a situation where the application is kept open permanently on the device for convenience. Added 4 new devices. | | 2.11 | Bug fix release for menu caching being turned off and language corrections (Czech & Slovenian). | -| 2.12 | Re-enabled Edge 540 and Edge 840 devices which we are unable to support due to simulator issues, but the Edge 840 device has been confirmed as working by a @Petucky. | +| 2.12 | Re-enabled Edge 540 and Edge 840 devices which we are unable to support due to simulator issues, but the Edge 840 device has been confirmed as working by a [Petucky](https://github.com/Petucky). | | 2.13 | Moved the template status queries to Webhooks in order to fix the situation where an account is a non-privileged user. Added telemetry update on activity completion to make automations more timely at the end of an activity. When using a polling delay, there is no longer a startup delay for status updates and an action will trigger an immediate round of updates. | | 2.14 | Cautionary bug fix for the background service code where refactorisation spoilt some API level guard clauses. | | 2.15 | Better support for templates by isolating erroneous returns and marking the menu item. | -| 2.16 | Bug fix for lack of phone connection when starting the application. | +| 2.16 | Bug fix for lack of phone connection when starting the application. Includes new activity reporting features from [KPWhiver](https://github.com/KPWhiver) covering steps, heart rate, floors climbed and descended, and respiration rate. | diff --git a/source/BackgroundServiceDelegate.mc b/source/BackgroundServiceDelegate.mc index 91a1ef7..0f54139 100644 --- a/source/BackgroundServiceDelegate.mc +++ b/source/BackgroundServiceDelegate.mc @@ -124,18 +124,56 @@ class BackgroundServiceDelegate extends System.ServiceDelegate { method(:onReturnBatteryUpdate) ); } + var activityInfo = ActivityMonitor.getInfo(); + var heartRate = Activity.getActivityInfo().currentHeartRate; var data = [ { "state" => System.getSystemStats().battery, "type" => "sensor", - "unique_id" => "battery_level" + "unique_id" => "battery_level", + "icon" => "mdi:battery" }, { "state" => System.getSystemStats().charging, "type" => "binary_sensor", - "unique_id" => "battery_is_charging" + "unique_id" => "battery_is_charging", + "icon" => System.getSystemStats().charging ? "mdi:battery-plus" : "mdi:battery-minus" + }, + { + "state" => activityInfo.steps == null ? "unknown" : activityInfo.steps, + "type" => "sensor", + "unique_id" => "steps_today", + "icon" => "mdi:walk" + }, + { + "state" => heartRate == null ? "unknown" : heartRate, + "type" => "sensor", + "unique_id" => "heart_rate", + "icon" => "mdi:heart-pulse" + }, + { + "state" => activityInfo.floorsClimbed == null ? "unknown" : activityInfo.floorsClimbed, + "type" => "sensor", + "unique_id" => "floors_climbed_today", + "icon" => "mdi:stairs-up" + }, + { + "state" => activityInfo.floorsDescended == null ? "unknown" : activityInfo.floorsDescended, + "type" => "sensor", + "unique_id" => "floors_descended_today", + "icon" => "mdi:stairs-down" } ]; + + if (ActivityMonitor.Info has :respirationRate) { + data.add({ + "state" => activityInfo.respirationRate == null ? "unknown" : activityInfo.respirationRate, + "type" => "sensor", + "unique_id" => "respiration_rate", + "icon" => "mdi:lungs" + }); + } + if (activity != null) { data.add({ "state" => activity, diff --git a/source/Settings.mc b/source/Settings.mc index c7f0e90..c5c5f61 100644 --- a/source/Settings.mc +++ b/source/Settings.mc @@ -78,18 +78,7 @@ class Settings { } else { // System.println("Settings update(): Doing just sensor creation."); // We already have a Webhook ID, so just enable or disable the sensor in Home Assistant. - // Its a multiple step process, hence starting at step 0. - mWebhookManager.registerWebhookSensor({ - "device_class" => "battery", - "name" => "Battery Level", - "state" => System.getSystemStats().battery, - "type" => "sensor", - "unique_id" => "battery_level", - "unit_of_measurement" => "%", - "state_class" => "measurement", - "entity_category" => "diagnostic", - "disabled" => !Settings.isSensorsLevelEnabled() - }, 0); + mWebhookManager.registerWebhookSensors(); } if (mIsSensorsLevelEnabled) { // Create the timed activity diff --git a/source/WebhookManager.mc b/source/WebhookManager.mc index 80017fb..c938803 100644 --- a/source/WebhookManager.mc +++ b/source/WebhookManager.mc @@ -69,17 +69,7 @@ class WebhookManager { if (id != null) { Settings.setWebhookId(id); // System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering first sensor: Battery Level"); - registerWebhookSensor({ - "device_class" => "battery", - "name" => "Battery Level", - "state" => System.getSystemStats().battery, - "type" => "sensor", - "unique_id" => "battery_level", - "unit_of_measurement" => "%", - "state_class" => "measurement", - "entity_category" => "diagnostic", - "disabled" => !Settings.isSensorsLevelEnabled() - }, 0); + registerWebhookSensors(); } else { // System.println("WebhookManager onReturnRequestWebhookId(): No webhook id in response data."); Settings.unsetIsSensorsLevelEnabled(); @@ -125,7 +115,7 @@ class WebhookManager { ); } - function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String, step as Lang.Number) as Void { + function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String, sensors as Lang.Array) as Void { switch (responseCode) { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: @@ -172,63 +162,10 @@ class WebhookManager { case 201: var d = data as Lang.Dictionary; if ((d.get("success") as Lang.Boolean or Null) != false) { - // System.println("WebhookManager onReturnRegisterWebhookSensor(): Success"); - switch (step) { - case 0: - // System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering next sensor: Battery is Charging"); - registerWebhookSensor({ - "device_class" => "battery_charging", - "name" => "Battery is Charging", - "state" => System.getSystemStats().charging, - "type" => "binary_sensor", - "unique_id" => "battery_is_charging", - "entity_category" => "diagnostic", - "disabled" => !Settings.isSensorsLevelEnabled() - }, 1); - break; - case 1: - // System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering next sensor: Activity"); - if (Activity has :getProfileInfo) { - var activity = Activity.getProfileInfo().sport; - if ((Activity.getActivityInfo() != null) and - ((Activity.getActivityInfo().elapsedTime == null) or - (Activity.getActivityInfo().elapsedTime == 0))) { - // Indicate no activity with -1, not part of Garmin's activity codes. - // https://developer.garmin.com/connect-iq/api-docs/Toybox/Activity.html#Sport-module - activity = -1; - } - registerWebhookSensor({ - "name" => "Activity", - "state" => activity, - "type" => "sensor", - "unique_id" => "activity", - "disabled" => !Settings.isSensorsLevelEnabled() - }, 2); - break; - } - case 2: - // System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering next sensor: Sub-Activity"); - if (Activity has :getProfileInfo) { - var sub_activity = Activity.getProfileInfo().subSport; - if ((Activity.getActivityInfo() != null) and - ((Activity.getActivityInfo().elapsedTime == null) or - (Activity.getActivityInfo().elapsedTime == 0))) { - // Indicate no activity with -1, not part of Garmin's activity codes. - // https://developer.garmin.com/connect-iq/api-docs/Toybox/Activity.html#Sport-module - sub_activity = -1; - } - registerWebhookSensor({ - "name" => "Sub-activity", - "state" => sub_activity, - "type" => "sensor", - "unique_id" => "sub_activity", - "disabled" => !Settings.isSensorsLevelEnabled() - }, 3); - break; - } - case 3: - getApp().startUpdates(); - default: + if (sensors.size() == 0) { + getApp().startUpdates(); + } else { + registerWebhookSensor(sensors); } } else { // System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure"); @@ -246,7 +183,7 @@ class WebhookManager { } } - function registerWebhookSensor(sensor as Lang.Object, step as Lang.Number) { + function registerWebhookSensor(sensors as Lang.Array) { var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(); // System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString()); // System.println("WebhookManager registerWebhookSensor(): URL=" + url); @@ -255,7 +192,7 @@ class WebhookManager { url, { "type" => "register_sensor", - "data" => sensor + "data" => sensors[0] }, { :method => Communications.HTTP_REQUEST_METHOD_POST, @@ -263,10 +200,120 @@ class WebhookManager { "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON }, :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON, - :context => step + :context => sensors.slice(1, null) }, method(:onReturnRegisterWebhookSensor) ); } + function registerWebhookSensors() { + var activityInfo = ActivityMonitor.getInfo(); + var heartRate = Activity.getActivityInfo().currentHeartRate; + + var sensors = [ + { + "device_class" => "battery", + "name" => "Battery Level", + "state" => System.getSystemStats().battery, + "type" => "sensor", + "unique_id" => "battery_level", + "icon" => "mdi:battery", + "unit_of_measurement" => "%", + "state_class" => "measurement", + "entity_category" => "diagnostic", + "disabled" => !Settings.isSensorsLevelEnabled() + }, + { + "device_class" => "battery_charging", + "name" => "Battery is Charging", + "state" => System.getSystemStats().charging, + "type" => "binary_sensor", + "unique_id" => "battery_is_charging", + "icon" => System.getSystemStats().charging ? "mdi:battery-plus" : "mdi:battery-minus", + "entity_category" => "diagnostic", + "disabled" => !Settings.isSensorsLevelEnabled() + }, + { + "name" => "Steps today", + "state" => activityInfo.steps == null ? "unknown" : activityInfo.steps, + "type" => "sensor", + "unique_id" => "steps_today", + "icon" => "mdi:walk", + "state_class" => "total", + "disabled" => !Settings.isSensorsLevelEnabled() + }, + { + "name" => "Heart rate", + "state" => heartRate == null ? "unknown" : heartRate, + "type" => "sensor", + "unique_id" => "heart_rate", + "icon" => "mdi:heart-pulse", + "unit_of_measurement" => "bpm", + "state_class" => "measurement", + "disabled" => !Settings.isSensorsLevelEnabled() + }, + { + "name" => "Floors climbed today", + "state" => activityInfo.floorsClimbed == null ? "unknown" : activityInfo.floorsClimbed, + "type" => "sensor", + "unique_id" => "floors_climbed_today", + "icon" => "mdi:stairs-up", + "state_class" => "total", + "disabled" => !Settings.isSensorsLevelEnabled() + }, + { + "name" => "Floors descended today", + "state" => activityInfo.floorsDescended == null ? "unknown" : activityInfo.floorsDescended, + "type" => "sensor", + "unique_id" => "floors_descended_today", + "icon" => "mdi:stairs-down", + "state_class" => "total", + "disabled" => !Settings.isSensorsLevelEnabled() + } + ]; + + + if (ActivityMonitor.Info has :respirationRate) { + sensors.add({ + "name" => "Respiration rate", + "state" => activityInfo.respirationRate == null ? "unknown" : activityInfo.respirationRate, + "type" => "sensor", + "unique_id" => "respiration_rate", + "icon" => "mdi:lungs", + "unit_of_measurement" => "bpm", + "state_class" => "measurement", + "disabled" => !Settings.isSensorsLevelEnabled() + }); + } + + if (Activity has :getProfileInfo) { + var activity = Activity.getProfileInfo().sport; + var sub_activity = Activity.getProfileInfo().subSport; + if ((Activity.getActivityInfo() != null) and + ((Activity.getActivityInfo().elapsedTime == null) or + (Activity.getActivityInfo().elapsedTime == 0))) { + // Indicate no activity with -1, not part of Garmin's activity codes. + // https://developer.garmin.com/connect-iq/api-docs/Toybox/Activity.html#Sport-module + activity = -1; + sub_activity = -1; + } + sensors.add({ + "name" => "Activity", + "state" => activity, + "type" => "sensor", + "unique_id" => "activity", + "disabled" => !Settings.isSensorsLevelEnabled() + }); + sensors.add({ + "name" => "Sub-activity", + "state" => sub_activity, + "type" => "sensor", + "unique_id" => "sub_activity", + "disabled" => !Settings.isSensorsLevelEnabled() + }); + } + + registerWebhookSensor(sensors); + } + }