Use webhooks for battery

This commit is contained in:
Joseph Abbey
2024-01-10 23:08:08 +00:00
parent 4e6f8bcefc
commit 42e0a1b543
4 changed files with 166 additions and 16 deletions

View File

@ -13,31 +13,44 @@
--> -->
<properties> <properties>
<property id="api_key" type="string"></property> <property id="api_key" type="string"></property>
<!--
The webhook ID is the last part of the webhook URL.
It is secret and should not be shared.
It will not be set in settings but will be
generated by the application and stored in
persistent storage.
-->
<property id="webhook_id" type="string"></property>
<!-- <!--
Internal URL "https://homeassistant.local/api" Internal URL "https://homeassistant.local/api"
External URL "https://<dynamic DNS>/api" External URL "https://<dynamic DNS>/api"
--> -->
<property id="api_url" type="string"></property> <property id="api_url" type="string"></property>
<!-- Best be a public URL in order to work away from your home LAN and have a trusted HTTPS certificate --> <!-- Best be a public URL in order to work away from your home LAN and have a trusted HTTPS
certificate -->
<property id="config_url" type="string"></property> <property id="config_url" type="string"></property>
<!-- Decide if the menu configuration should be cached. --> <!-- Decide if the menu configuration should be cached. -->
<property id="cache_config" type="boolean">false</property> <property id="cache_config" type="boolean">false</property>
<!-- Clear the menu configuration on next application start, and refetch, then set this back to false --> <!-- Clear the menu configuration on next application start, and refetch, then set this back to
false -->
<property id="clear_cache" type="boolean">false</property> <property id="clear_cache" type="boolean">false</property>
<!-- <!--
Application timeout in seconds, except 0 for no timeout (default). After this amount of elapsed time Application timeout in seconds, except 0 for no timeout (default). After this amount of elapsed
time
with no activity, exit the application. with no activity, exit the application.
--> -->
<property id="app_timeout" type="number">0</property> <property id="app_timeout" type="number">0</property>
<!-- <!--
After this time (in seconds), a confirmation dialog for an action is automatically closed and the action After this time (in seconds), a confirmation dialog for an action is automatically closed and the
action
is cancelled. Set to 0 to disable the timeout. The default value is 3 seconds. is cancelled. Set to 0 to disable the timeout. The default value is 3 seconds.
--> -->
<property id="confirm_timeout" type="number">3</property> <property id="confirm_timeout" type="number">3</property>
@ -71,4 +84,4 @@
--> -->
<property id="battery_level_refresh_rate" type="number">15</property> <property id="battery_level_refresh_rate" type="number">15</property>
</properties> </properties>

View File

@ -107,4 +107,10 @@
<settingConfig type="numeric" min="5" /> <settingConfig type="numeric" min="5" />
</setting> </setting>
</settings> <setting
propertyKey="@Properties.webhook_id"
title="Webhook ID"
>
<settingConfig type="alphaNumeric" />
</setting>
</settings>

View File

@ -51,17 +51,26 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
} else { } else {
// Don't use Settings.* here as the object lasts < 30 secs and is recreated each time the background service is run // Don't use Settings.* here as the object lasts < 30 secs and is recreated each time the background service is run
Communications.makeWebRequest( Communications.makeWebRequest(
(Properties.getValue("api_url") as Lang.String) + "/events/garmin.battery_level", (Properties.getValue("api_url") as Lang.String) + "/webhook/" + (Properties.getValue("webhook_id") as Lang.String),
{ {
"level" => System.getSystemStats().battery, "type" => "update_sensor_states",
"is_charging" => System.getSystemStats().charging, "data" => [
"device_id" => System.getDeviceSettings().uniqueIdentifier {
"state" => System.getSystemStats().battery,
"type" => "sensor",
"unique_id" => "battery_level"
},
{
"state" => System.getSystemStats().charging,
"type" => "binary_sensor",
"unique_id" => "battery_is_charging"
}
]
}, },
{ {
:method => Communications.HTTP_REQUEST_METHOD_POST, :method => Communications.HTTP_REQUEST_METHOD_POST,
:headers => { :headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON, "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
"Authorization" => "Bearer " + (Properties.getValue("api_key") as Lang.String)
}, },
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
}, },

View File

@ -25,6 +25,110 @@ using Toybox.System;
// Battery Level Reporting // Battery Level Reporting
using Toybox.Background; using Toybox.Background;
using Toybox.Time; using Toybox.Time;
using Toybox.Communications;
class WebhookManager {
function onReturnRequestWebhookId(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
// TODO: Handle errors
if (responseCode == 201) {
var id = data.get("webhook_id") as Lang.String or Null;
if (id != null) {
Settings.setWebhookId(id);
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
});
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
});
}
} else {
if (Globals.scDebug) {
System.println("Settings onReturnRequestWebhookId(): Error: " + responseCode);
}
}
}
function requestWebhookId() {
if (Globals.scDebug) {
System.println("Settings requestWebhookId(): Requesting webhook id");
}
Communications.makeWebRequest(
Settings.getApiUrl() + "/mobile_app/registrations",
{
"device_id" => System.getDeviceSettings().uniqueIdentifier,
"app_id" => "garmin_home_assistant",
"app_name" => "GarminHomeAssistant",
"app_version" => "",
"device_name" => "Garmin Watch",
"manufacturer" => "Garmin",
"model" => "",
"os_name" => "",
"os_version" => Lang.format("$1$.$2$", System.getDeviceSettings().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) as Void {
// TODO: Handle errors
if (responseCode == 201) {
if ((data.get("success") as Lang.Boolean or Null) == true) {
if (Globals.scDebug) {
System.println("Settings onReturnRegisterWebhookSensor(): Success");
}
}
} else {
if (Globals.scDebug) {
System.println("Settings onReturnRegisterWebhookSensor(): Error: " + responseCode);
}
}
}
function registerWebhookSensor(sensor as Lang.Object) {
if (Globals.scDebug) {
System.println("Settings 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
},
method(:onReturnRegisterWebhookSensor)
);
}
}
(:glance, :background) (:glance, :background)
class Settings { class Settings {
@ -32,6 +136,7 @@ class Settings {
public static const MENU_STYLE_TEXT = 1; public static const MENU_STYLE_TEXT = 1;
private static var mApiKey as Lang.String = ""; private static var mApiKey as Lang.String = "";
private static var mWebhookId as Lang.String = "";
private static var mApiUrl as Lang.String = ""; private static var mApiUrl as Lang.String = "";
private static var mConfigUrl as Lang.String = ""; private static var mConfigUrl as Lang.String = "";
private static var mCacheConfig as Lang.Boolean = false; private static var mCacheConfig as Lang.Boolean = false;
@ -45,10 +150,14 @@ class Settings {
private static var mBatteryRefreshRate as Lang.Number = 15; // minutes private static var mBatteryRefreshRate as Lang.Number = 15; // minutes
private static var mIsApp as Lang.Boolean = false; private static var mIsApp as Lang.Boolean = false;
// Must keep the object so it doesn't get garbage collected.
private static var mWebhookManager as WebhookManager or Null;
// Called on application start and then whenever the settings are changed. // Called on application start and then whenever the settings are changed.
static function update() { static function update() {
mIsApp = getApp().getIsApp(); mIsApp = getApp().getIsApp();
mApiKey = Properties.getValue("api_key"); mApiKey = Properties.getValue("api_key");
mWebhookId = Properties.getValue("webhook_id");
mApiUrl = Properties.getValue("api_url"); mApiUrl = Properties.getValue("api_url");
mConfigUrl = Properties.getValue("config_url"); mConfigUrl = Properties.getValue("config_url");
mCacheConfig = Properties.getValue("cache_config"); mCacheConfig = Properties.getValue("cache_config");
@ -64,7 +173,12 @@ class Settings {
// Manage this inside the application or widget only (not a glance or background service process) // Manage this inside the application or widget only (not a glance or background service process)
if (mIsApp) { if (mIsApp) {
if (mIsBatteryLevelEnabled) { if (mIsBatteryLevelEnabled) {
if ((System has :ServiceDelegate) and if (getWebhookId().equals("")) {
mWebhookManager = new WebhookManager();
mWebhookManager.requestWebhookId();
}
if (!getWebhookId().equals("") and
(System has :ServiceDelegate) and
((Background.getTemporalEventRegisteredTime() == null) or ((Background.getTemporalEventRegisteredTime() == null) or
(Background.getTemporalEventRegisteredTime() != (mBatteryRefreshRate * 60)))) { (Background.getTemporalEventRegisteredTime() != (mBatteryRefreshRate * 60)))) {
Background.registerForTemporalEvent(new Time.Duration(mBatteryRefreshRate * 60)); // Convert to seconds Background.registerForTemporalEvent(new Time.Duration(mBatteryRefreshRate * 60)); // Convert to seconds
@ -90,6 +204,15 @@ class Settings {
return mApiKey; return mApiKey;
} }
static function getWebhookId() as Lang.String {
return mWebhookId;
}
static function setWebhookId(webhookId as Lang.String) {
mWebhookId = webhookId;
Properties.setValue("webhook_id", mWebhookId);
}
static function getApiUrl() as Lang.String { static function getApiUrl() as Lang.String {
return mApiUrl; return mApiUrl;
} }
@ -130,5 +253,4 @@ class Settings {
static function getIsWidgetStartNoTap() as Lang.Boolean { static function getIsWidgetStartNoTap() as Lang.Boolean {
return mIsWidgetStartNoTap; return mIsWidgetStartNoTap;
} }
} }