GarminHomeAssistant/source/WebhookManager.mc
Philip Abbey 69ddb0f197 Amended "no activity" condition
Back to using elapsed time.
2024-01-29 19:06:58 +00:00

264 lines
15 KiB
MonkeyC

//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// 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, 10 January 2024
//
//
// Description:
//
// Home Assistant Webhook creation.
//
// Reference:
// * https://developers.home-assistant.io/docs/api/native-app-integration
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.Communications;
using Toybox.System;
// Can use push view so must never be run in a glance context
class WebhookManager {
function onReturnRequestWebhookId(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:
// 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 + ".");
break;
case Communications.BLE_QUEUE_FULL:
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
break;
case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY:
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
// Ignore and see if we can carry on
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
break;
case 404:
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: 404, page not found. Check API URL setting.");
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String);
break;
case 200:
case 201:
var id = data.get("webhook_id") as Lang.String or Null;
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" => false
}, 0);
} else {
// System.println("WebhookManager onReturnRequestWebhookId(): No webhook id in response data.");
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String);
}
break;
default:
// System.println("WebhookManager onReturnRequestWebhookId(): Unhandled HTTP response code = " + responseCode);
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
}
function requestWebhookId() {
// System.println("WebhookManager requestWebhookId(): Requesting webhook id");
var deviceSettings = System.getDeviceSettings();
Communications.makeWebRequest(
Settings.getApiUrl() + "/mobile_app/registrations",
{
"device_id" => deviceSettings.uniqueIdentifier,
"app_id" => "garmin_home_assistant",
"app_name" => WatchUi.loadResource($.Rez.Strings.AppName) as Lang.String,
"app_version" => "",
"device_name" => "Garmin Device",
"manufacturer" => "Garmin",
// An unhelpful part number that can be translated to a familiar model name.
"model" => deviceSettings.partNumber,
"os_name" => "",
"os_version" => Lang.format("$1$.$2$", deviceSettings.firmwareVersion),
"supports_encryption" => false,
"app_data" => {}
},
{
:method => Communications.HTTP_REQUEST_METHOD_POST,
:headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON,
"Authorization" => "Bearer " + Settings.getApiKey()
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
},
method(:onReturnRequestWebhookId)
);
}
function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String, step as Lang.Number) as Void {
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
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 + ".");
break;
case Communications.BLE_QUEUE_FULL:
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
Settings.unsetWebhookId();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
break;
case Communications.NETWORK_REQUEST_TIMED_OUT:
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
Settings.unsetWebhookId();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
break;
case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY:
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
Settings.unsetWebhookId();
// Ignore and see if we can carry on
break;
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
// System.println("WebhookManager onReturnRegisterWebhookSensor() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
Settings.unsetWebhookId();
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
break;
case 404:
// System.println("WebhookManager onReturnRequestWebhookId() Response Code: 404, page not found. Check API URL setting.");
Settings.unsetWebhookId();
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String);
break;
case 200:
case 201:
if ((data.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" => false
}, 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" => false
}, 2);
break;
}
case 2:
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering next sensor: 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" => false
}, 3);
break;
}
default:
}
} else {
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure");
Settings.unsetWebhookId();
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String);
}
break;
default:
// System.println("WebhookManager onReturnRequestWebhookId(): Unhandled HTTP response code = " + responseCode);
Settings.unsetWebhookId();
Settings.unsetIsBatteryLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
}
}
function registerWebhookSensor(sensor as Lang.Object, step as Lang.Number) {
// System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
Communications.makeWebRequest(
Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(),
{
"type" => "register_sensor",
"data" => sensor
},
{
:method => Communications.HTTP_REQUEST_METHOD_POST,
:headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
:context => step
},
method(:onReturnRegisterWebhookSensor)
);
}
}