mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-09-15 13:31:31 +00:00
Compare commits
9 Commits
ad7d278072
...
v3.1
Author | SHA1 | Date | |
---|---|---|---|
|
46740fac76 | ||
|
649f0f250d | ||
|
5a0bd98ddb | ||
|
ce81c6af0a | ||
|
3d7b588d2c | ||
|
166b5f4ec3 | ||
|
57128bf7a4 | ||
|
64bebded0a | ||
|
b9db9af3bf |
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -4,6 +4,8 @@
|
|||||||
"Venu"
|
"Venu"
|
||||||
],
|
],
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"resources-*": true
|
"resources-*": true,
|
||||||
|
"bin": true,
|
||||||
|
"export": true
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -46,4 +46,4 @@
|
|||||||
| 2.31 | Adding [two new options](./examples/Actions.md#exit-on-tap) to the menu items: 1) The ability to disable a menu item, e.g. temporarily for seasonal changes, 2) The option to exit after a menu item has been select. |
|
| 2.31 | Adding [two new options](./examples/Actions.md#exit-on-tap) to the menu items: 1) The ability to disable a menu item, e.g. temporarily for seasonal changes, 2) The option to exit after a menu item has been select. |
|
||||||
| 2.32 | Bug fix for a breaking change extracting options caused by the need to rearrange function parameters for an [annoying compiler error](https://github.com/house-of-abbey/GarminHomeAssistant/issues/253). |
|
| 2.32 | Bug fix for a breaking change extracting options caused by the need to rearrange function parameters for an [annoying compiler error](https://github.com/house-of-abbey/GarminHomeAssistant/issues/253). |
|
||||||
| 3.0 | First version with the ability to use [Wi-Fi or LTE](Wi-Fi.md) instead of Bluetooth but with limited functionality, thanks to [@vincentezw](https://github.com/vincentezw). |
|
| 3.0 | First version with the ability to use [Wi-Fi or LTE](Wi-Fi.md) instead of Bluetooth but with limited functionality, thanks to [@vincentezw](https://github.com/vincentezw). |
|
||||||
| 3.1 | Added the ability for users to provide custom HTTP headers for their Home Assistant server. Improved German language translations. Removed all groups in settings as the SDK is buggy. |
|
| 3.1 | Added the ability for users to provide custom HTTP headers for their Home Assistant server. Improved German language translations. Removed all groups in settings as the SDK is buggy. Fixed a bug with templates in glances causing application crash on startup. |
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# User Specified Custom HTTP Headers
|
# User Specified Custom HTTP Headers
|
||||||
|
|
||||||
Principally for those who use Home Assistant add-on [Cloudflared](https://github.com/brenner-tobias/addon-cloudflared) in order to provide additional security via Cloudflare's Web Application Firewall (WAF). But the solution is generic enough for other use cases.
|
Principally for those who use Home Assistant add-on [Cloudflared](https://github.com/brenner-tobias/addon-cloudflared) in order to provide additional security via Cloudflare's Web Application Firewall (WAF). But Garmin does not support certificates in requests. And the solution is generic enough for other use cases.
|
||||||
|
|
||||||
Please let us know if this solution is found to be useful for other situations.
|
Please let us know if this solution is found to be useful for other situations.
|
||||||
|
|
||||||
@@ -14,6 +14,12 @@ The settings contain two options for users to specify both the HTTP header name
|
|||||||
|
|
||||||
If you don't know why you need these, leave them empty and ignore.
|
If you don't know why you need these, leave them empty and ignore.
|
||||||
|
|
||||||
|
### Cloudflare WAF rule example
|
||||||
|
|
||||||
|
`(any(http.request.headers["your-header-name"][*] eq "your-header-key"))`
|
||||||
|
|
||||||
|
Make the key strong enough!
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
**None!**
|
**None!**
|
||||||
@@ -23,3 +29,9 @@ The authors of the Garmin Home Assistant application do not use, and hence do no
|
|||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
With thanks to Lars Pöpperl ([@tispokes](https://github.com/tispokes)) for contributing to this solution.
|
With thanks to Lars Pöpperl ([@tispokes](https://github.com/tispokes)) for contributing to this solution.
|
||||||
|
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
* [Using Cloudflare ZeroTrust and mTLS to securely access Home Assistant via the internet](https://kcore.org/2024/06/28/using-cloudflare-zerotrust-and-mtls-with-home-assistant-via-the-internet/)
|
||||||
|
* [Home Assistant Add-on: Cloudflared](https://github.com/brenner-tobias/addon-cloudflared)
|
@@ -17,6 +17,8 @@ The application is designed around a simple scrollable menu where menu items hav
|
|||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> The Garmin SDK allows HTTP requests only to a limited number of domains specified in their app. Therefore, for your Garmin to communicate with your Home Assistant instance, your Home Assistant instance must be accessible via HTTPS (with a public certificate!) or through a local DNS server that overrides one of the whitelisted domains to communicate using HTTP.
|
> The Garmin SDK allows HTTP requests only to a limited number of domains specified in their app. Therefore, for your Garmin to communicate with your Home Assistant instance, your Home Assistant instance must be accessible via HTTPS (with a public certificate!) or through a local DNS server that overrides one of the whitelisted domains to communicate using HTTP.
|
||||||
>
|
>
|
||||||
|
>New with version 3.1, you can use [Cloudflared](https://github.com/brenner-tobias/addon-cloudflared) plug-in in combination with a [custom HTTP header](HTTP_Headers.md) and do not need a public certificate for HTTPS.
|
||||||
|
>
|
||||||
> To make your Home Assistant instance accessible via HTTPS, you will need a public certificate. You can get one for free from [Let's Encrypt](https://letsencrypt.org/) or you can pay for [Home Assistant cloud](https://www.nabucasa.com/). (You can install a local [Nginx proxy server](https://my.home-assistant.io/redirect/supervisor_addon/?addon=a0d7b954_nginxproxymanager) to manage Let's Encrypt certificates.)
|
> To make your Home Assistant instance accessible via HTTPS, you will need a public certificate. You can get one for free from [Let's Encrypt](https://letsencrypt.org/) or you can pay for [Home Assistant cloud](https://www.nabucasa.com/). (You can install a local [Nginx proxy server](https://my.home-assistant.io/redirect/supervisor_addon/?addon=a0d7b954_nginxproxymanager) to manage Let's Encrypt certificates.)
|
||||||
>
|
>
|
||||||
> If you use a local DNS server (like [Pi-Hole](https://pi-hole.net/)), you can create a local DNS record for the domain `garmincdn.com` (which is allowed for HTTP in the Garmin SDK) and map it to your Home Assistant instance's IP. "_[About Communication Between Garmin SDK and a Raspberry Pi](https://www.instructables.com/About-Communication-Between-Garmin-SDK-and-a-Raspb/)_" provides additional workarounds for HTTP request restrictions in the Garmin SDK.
|
> If you use a local DNS server (like [Pi-Hole](https://pi-hole.net/)), you can create a local DNS record for the domain `garmincdn.com` (which is allowed for HTTP in the Garmin SDK) and map it to your Home Assistant instance's IP. "_[About Communication Between Garmin SDK and a Raspberry Pi](https://www.instructables.com/About-Communication-Between-Garmin-SDK-and-a-Raspb/)_" provides additional workarounds for HTTP request restrictions in the Garmin SDK.
|
||||||
@@ -230,7 +232,7 @@ Make sure you can browse to the URL of your JSON file in a standard web browser
|
|||||||
|
|
||||||
## API Key Creation
|
## API Key Creation
|
||||||
|
|
||||||
Having created your JSON definition for your dashboard, you need to create an API key for your personal account on Home Assistant. You will need a [Long-Lived Access Token](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token). This is not obvious to find and is bound to your own Home Assistant account. Follow the menu sequence: `HA -> user profile -> Long-lived access tokens`. Make sure you save the generated token before dismissing it.
|
Having created your JSON definition for your dashboard, you need to create an API key for your personal account on Home Assistant. You will need a [Long-Lived Access Token](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token). This is not obvious to find and is bound to your own Home Assistant account. Follow the menu sequence: `HA -> User Profile -> "Security" tab -> Long-lived access tokens`. Make sure you save the generated token before dismissing it.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 634 KiB After Width: | Height: | Size: 557 KiB |
@@ -25,8 +25,8 @@ using Toybox.Timer;
|
|||||||
//
|
//
|
||||||
(:glance, :background)
|
(:glance, :background)
|
||||||
class HomeAssistantApp extends Application.AppBase {
|
class HomeAssistantApp extends Application.AppBase {
|
||||||
private var mApiStatus as Lang.String?;
|
|
||||||
private var mHasToast as Lang.Boolean = false;
|
private var mHasToast as Lang.Boolean = false;
|
||||||
|
private var mApiStatus as Lang.String?;
|
||||||
private var mMenuStatus as Lang.String?;
|
private var mMenuStatus as Lang.String?;
|
||||||
private var mHaMenu as HomeAssistantView?;
|
private var mHaMenu as HomeAssistantView?;
|
||||||
private var mGlanceTemplate as Lang.String? = null;
|
private var mGlanceTemplate as Lang.String? = null;
|
||||||
@@ -36,7 +36,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
private var mUpdateTimer as Timer.Timer?;
|
private var mUpdateTimer as Timer.Timer?;
|
||||||
// Array initialised by onReturnFetchMenuConfig()
|
// Array initialised by onReturnFetchMenuConfig()
|
||||||
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem>?;
|
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem>?;
|
||||||
private var mIsGlance as Lang.Boolean = false;
|
|
||||||
private var mIsApp as Lang.Boolean = false; // Or Widget
|
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 mUpdating as Lang.Boolean = false; // Don't start a second chain of updates
|
||||||
private var mTemplates as Lang.Dictionary = {};
|
private var mTemplates as Lang.Dictionary = {};
|
||||||
@@ -155,7 +154,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
//! @param responseCode Response code.
|
//! @param responseCode Response code.
|
||||||
//! @param data Response data.
|
//! @param data Response data.
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function onReturnFetchMenuConfig(
|
function onReturnFetchMenuConfig(
|
||||||
responseCode as Lang.Number,
|
responseCode as Lang.Number,
|
||||||
data as Null or Lang.Dictionary or Lang.String
|
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_HOST_TIMEOUT:
|
||||||
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
||||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.BLE_QUEUE_FULL:
|
case Communications.BLE_QUEUE_FULL:
|
||||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.NETWORK_REQUEST_TIMED_OUT:
|
case Communications.NETWORK_REQUEST_TIMED_OUT:
|
||||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
|
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
|
||||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 404:
|
case 404:
|
||||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: 404, page not found. Check Configuration URL setting.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -212,7 +210,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
|
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mIsGlance) {
|
if (!mIsApp) {
|
||||||
glanceTemplate(data);
|
glanceTemplate(data);
|
||||||
} else {
|
} else {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
@@ -226,7 +224,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode);
|
// System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode);
|
||||||
if (!mIsGlance) {
|
if (mIsApp) {
|
||||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
|
||||||
}
|
}
|
||||||
break;
|
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
|
//! @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.
|
//! the menu when not in the cache is asynchronous and affects how the views are managed.
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function fetchMenuConfig() as Lang.Boolean {
|
function fetchMenuConfig() as Lang.Boolean {
|
||||||
// System.println("Menu URL = " + Settings.getConfigUrl());
|
// System.println("Menu URL = " + Settings.getConfigUrl());
|
||||||
if (Settings.getConfigUrl().equals("")) {
|
if (Settings.getConfigUrl().equals("")) {
|
||||||
@@ -275,7 +272,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
errorRez = $.Rez.Strings.Unavailable;
|
errorRez = $.Rez.Strings.Unavailable;
|
||||||
}
|
}
|
||||||
// System.println("HomeAssistantApp fetchMenuConfig(): No Phone connection, skipping API call.");
|
// System.println("HomeAssistantApp fetchMenuConfig(): No Phone connection, skipping API call.");
|
||||||
if (mIsGlance) {
|
if (!mIsApp) {
|
||||||
WatchUi.requestUpdate();
|
WatchUi.requestUpdate();
|
||||||
} else {
|
} else {
|
||||||
ErrorView.show(WatchUi.loadResource(errorRez) as Lang.String);
|
ErrorView.show(WatchUi.loadResource(errorRez) as Lang.String);
|
||||||
@@ -296,7 +293,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
} else {
|
} else {
|
||||||
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String;
|
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Cached) as Lang.String;
|
||||||
WatchUi.requestUpdate();
|
WatchUi.requestUpdate();
|
||||||
if (mIsGlance) {
|
if (!mIsApp) {
|
||||||
glanceTemplate(menu);
|
glanceTemplate(menu);
|
||||||
} else {
|
} else {
|
||||||
buildMenu(menu);
|
buildMenu(menu);
|
||||||
@@ -430,85 +427,87 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
//! Construct the GET request to update all menu items.
|
//! Construct the GET request to update all menu items.
|
||||||
//
|
//
|
||||||
function updateMenuItems() as Void {
|
function updateMenuItems() as Void {
|
||||||
var phoneConnected = System.getDeviceSettings().phoneConnected;
|
if (mUpdating) {
|
||||||
var connectionAvailable = System.getDeviceSettings().connectionAvailable;
|
var phoneConnected = System.getDeviceSettings().phoneConnected;
|
||||||
|
var connectionAvailable = System.getDeviceSettings().connectionAvailable;
|
||||||
|
|
||||||
// In Wi-Fi/LTE execution mode, we should not show an error page but use a toast instead.
|
// In Wi-Fi/LTE execution mode, we should not show an error page but use a toast instead.
|
||||||
if (Settings.getWifiLteExecutionEnabled() && (! phoneConnected || ! connectionAvailable)) {
|
if (Settings.getWifiLteExecutionEnabled() && (! phoneConnected || ! connectionAvailable)) {
|
||||||
// Notify only once per disconnection cycle
|
// Notify only once per disconnection cycle
|
||||||
if (!mNotifiedNoBle) {
|
if (!mNotifiedNoBle) {
|
||||||
var toast = WatchUi.loadResource($.Rez.Strings.NoPhone);
|
var toast = WatchUi.loadResource($.Rez.Strings.NoPhone);
|
||||||
if (!connectionAvailable) {
|
if (!connectionAvailable) {
|
||||||
toast = WatchUi.loadResource($.Rez.Strings.NoInternet);
|
toast = WatchUi.loadResource($.Rez.Strings.NoInternet);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mHasToast) {
|
||||||
|
WatchUi.showToast(toast, null);
|
||||||
|
} else {
|
||||||
|
new Alert({
|
||||||
|
:timeout => Globals.scAlertTimeoutMs,
|
||||||
|
:font => Graphics.FONT_MEDIUM,
|
||||||
|
:text => toast,
|
||||||
|
:fgcolor => Graphics.COLOR_WHITE,
|
||||||
|
:bgcolor => Graphics.COLOR_BLACK
|
||||||
|
}).pushView(WatchUi.SLIDE_IMMEDIATE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mHasToast) {
|
mNotifiedNoBle = true;
|
||||||
WatchUi.showToast(toast, null);
|
setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
|
||||||
} else {
|
mUpdateTimer.start(method(:startUpdates), Globals.wifiPollResumeDelayMs, false);
|
||||||
new Alert({
|
|
||||||
:timeout => Globals.scAlertTimeoutMs,
|
mUpdating = false;
|
||||||
:font => Graphics.FONT_MEDIUM,
|
return;
|
||||||
:text => toast,
|
|
||||||
:fgcolor => Graphics.COLOR_WHITE,
|
|
||||||
:bgcolor => Graphics.COLOR_BLACK
|
|
||||||
}).pushView(WatchUi.SLIDE_IMMEDIATE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mNotifiedNoBle = true;
|
if (! phoneConnected) {
|
||||||
setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
|
// System.println("HomeAssistantApp updateMenuItems(): No Phone connection, skipping API call.");
|
||||||
mUpdateTimer.start(method(:startUpdates), Globals.wifiPollResumeDelayMs, false);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String);
|
||||||
|
setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
|
||||||
|
} else if (! connectionAvailable) {
|
||||||
|
// System.println("HomeAssistantApp updateMenuItems(): No Internet connection, skipping API call.");
|
||||||
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
|
||||||
|
setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
|
||||||
|
} else {
|
||||||
|
mNotifiedNoBle = false;
|
||||||
|
|
||||||
mUpdating = false;
|
if (mItemsToUpdate == null or mTemplates == null) {
|
||||||
return;
|
mItemsToUpdate = mHaMenu.getItemsToUpdate();
|
||||||
}
|
mTemplates = {};
|
||||||
|
for (var i = 0; i < mItemsToUpdate.size(); i++) {
|
||||||
if (! phoneConnected) {
|
var item = mItemsToUpdate[i];
|
||||||
// System.println("HomeAssistantApp updateMenuItems(): No Phone connection, skipping API call.");
|
var template = item.getTemplate();
|
||||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String);
|
if (template != null) {
|
||||||
setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
|
mTemplates.put(i.toString(), {
|
||||||
} else if (! connectionAvailable) {
|
"template" => template
|
||||||
// System.println("HomeAssistantApp updateMenuItems(): No Internet connection, skipping API call.");
|
});
|
||||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
|
}
|
||||||
setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String);
|
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||||
} else {
|
mTemplates.put(i.toString() + "t", {
|
||||||
mNotifiedNoBle = false;
|
"template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate()
|
||||||
|
|
||||||
if (mItemsToUpdate == null or mTemplates == null) {
|
|
||||||
mItemsToUpdate = mHaMenu.getItemsToUpdate();
|
|
||||||
mTemplates = {};
|
|
||||||
for (var i = 0; i < mItemsToUpdate.size(); i++) {
|
|
||||||
var item = mItemsToUpdate[i];
|
|
||||||
var template = item.getTemplate();
|
|
||||||
if (template != null) {
|
|
||||||
mTemplates.put(i.toString(), {
|
|
||||||
"template" => template
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
}
|
||||||
mTemplates.put(i.toString() + "t", {
|
|
||||||
"template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
|
||||||
|
// System.println("HomeAssistantApp updateMenuItems() URL=" + url + ", Template='" + mTemplate + "'");
|
||||||
|
Communications.makeWebRequest(
|
||||||
|
Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(),
|
||||||
|
{
|
||||||
|
"type" => "render_template",
|
||||||
|
"data" => mTemplates
|
||||||
|
},
|
||||||
|
{
|
||||||
|
:method => Communications.HTTP_REQUEST_METHOD_POST,
|
||||||
|
:headers => Settings.augmentHttpHeaders({
|
||||||
|
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
|
||||||
|
}),
|
||||||
|
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
||||||
|
},
|
||||||
|
method(:onReturnUpdateMenuItems)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
|
|
||||||
// System.println("HomeAssistantApp updateMenuItems() URL=" + url + ", Template='" + mTemplate + "'");
|
|
||||||
Communications.makeWebRequest(
|
|
||||||
Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(),
|
|
||||||
{
|
|
||||||
"type" => "render_template",
|
|
||||||
"data" => mTemplates
|
|
||||||
},
|
|
||||||
{
|
|
||||||
:method => Communications.HTTP_REQUEST_METHOD_POST,
|
|
||||||
:headers => Settings.augmentHttpHeaders({
|
|
||||||
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
|
|
||||||
}),
|
|
||||||
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
|
||||||
},
|
|
||||||
method(:onReturnUpdateMenuItems)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,7 +516,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
//! @param responseCode Response code.
|
//! @param responseCode Response code.
|
||||||
//! @param data Response data.
|
//! @param data Response data.
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function onReturnFetchApiStatus(
|
function onReturnFetchApiStatus(
|
||||||
responseCode as Lang.Number,
|
responseCode as Lang.Number,
|
||||||
data as Null or Lang.Dictionary or Lang.String
|
data as Null or Lang.Dictionary or Lang.String
|
||||||
@@ -530,35 +528,35 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
case Communications.BLE_HOST_TIMEOUT:
|
case Communications.BLE_HOST_TIMEOUT:
|
||||||
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
||||||
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.BLE_QUEUE_FULL:
|
case Communications.BLE_QUEUE_FULL:
|
||||||
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.NETWORK_REQUEST_TIMED_OUT:
|
case Communications.NETWORK_REQUEST_TIMED_OUT:
|
||||||
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
|
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
|
||||||
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 404:
|
case 404:
|
||||||
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: 404, page not found. Check Configuration URL setting.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -567,7 +565,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
if ((data != null) && data.get("message").equals("API running.")) {
|
if ((data != null) && data.get("message").equals("API running.")) {
|
||||||
mApiStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
|
mApiStatus = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String;
|
||||||
} else {
|
} else {
|
||||||
if (!mIsGlance) {
|
if (mIsApp) {
|
||||||
ErrorView.show("API " + mApiStatus + ".");
|
ErrorView.show("API " + mApiStatus + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,7 +573,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// System.println("HomeAssistantApp onReturnFetchApiStatus(): Unhandled HTTP response code = " + responseCode);
|
// System.println("HomeAssistantApp onReturnFetchApiStatus(): Unhandled HTTP response code = " + responseCode);
|
||||||
if (!mIsGlance) {
|
if (mIsApp) {
|
||||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -584,7 +582,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
|
|
||||||
//! Construct the GET request to test the API status, is it accessible?
|
//! Construct the GET request to test the API status, is it accessible?
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function fetchApiStatus() as Void {
|
function fetchApiStatus() as Void {
|
||||||
var phoneConnected = System.getDeviceSettings().phoneConnected;
|
var phoneConnected = System.getDeviceSettings().phoneConnected;
|
||||||
var connectionAvailable = System.getDeviceSettings().connectionAvailable;
|
var connectionAvailable = System.getDeviceSettings().connectionAvailable;
|
||||||
@@ -594,13 +591,13 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
|
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String;
|
||||||
WatchUi.requestUpdate();
|
WatchUi.requestUpdate();
|
||||||
} else {
|
} 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.");
|
// System.println("HomeAssistantApp fetchApiStatus(): In-app Wifi mode (No Phone and Internet connection), early return.");
|
||||||
return;
|
return;
|
||||||
} else if (! phoneConnected) {
|
} else if (! phoneConnected) {
|
||||||
// System.println("HomeAssistantApp fetchApiStatus(): No Phone connection, skipping API call.");
|
// System.println("HomeAssistantApp fetchApiStatus(): No Phone connection, skipping API call.");
|
||||||
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
|
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
|
||||||
if (mIsGlance) {
|
if (!mIsApp) {
|
||||||
WatchUi.requestUpdate();
|
WatchUi.requestUpdate();
|
||||||
} else {
|
} else {
|
||||||
System.println("we here");
|
System.println("we here");
|
||||||
@@ -609,7 +606,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
} else if (! connectionAvailable) {
|
} else if (! connectionAvailable) {
|
||||||
// System.println("HomeAssistantApp fetchApiStatus(): No Internet connection, skipping API call.");
|
// System.println("HomeAssistantApp fetchApiStatus(): No Internet connection, skipping API call.");
|
||||||
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
|
mApiStatus = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String;
|
||||||
if (mIsGlance) {
|
if (!mIsApp) {
|
||||||
WatchUi.requestUpdate();
|
WatchUi.requestUpdate();
|
||||||
} else {
|
} else {
|
||||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
|
||||||
@@ -637,7 +634,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
//! @param responseCode Response code.
|
//! @param responseCode Response code.
|
||||||
//! @param data Response data.
|
//! @param data Response data.
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function onReturnFetchGlanceContent(
|
function onReturnFetchGlanceContent(
|
||||||
responseCode as Lang.Number,
|
responseCode as Lang.Number,
|
||||||
data as Null or Lang.Dictionary or Lang.String
|
data as Null or Lang.Dictionary or Lang.String
|
||||||
@@ -649,35 +645,35 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
case Communications.BLE_HOST_TIMEOUT:
|
case Communications.BLE_HOST_TIMEOUT:
|
||||||
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
||||||
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.BLE_QUEUE_FULL:
|
case Communications.BLE_QUEUE_FULL:
|
||||||
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.NETWORK_REQUEST_TIMED_OUT:
|
case Communications.NETWORK_REQUEST_TIMED_OUT:
|
||||||
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
|
case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE:
|
||||||
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 404:
|
case 404:
|
||||||
// System.println("HomeAssistantApp onReturnFetchGlanceContent() Response Code: 404, page not found. Check Configuration URL setting.");
|
// 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);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound) as Lang.String);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -690,7 +686,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// System.println("HomeAssistantApp onReturnFetchGlanceContent(): Unhandled HTTP response code = " + responseCode);
|
// System.println("HomeAssistantApp onReturnFetchGlanceContent(): Unhandled HTTP response code = " + responseCode);
|
||||||
if (!mIsGlance) {
|
if (mIsApp) {
|
||||||
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
|
ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,7 +695,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
|
|
||||||
//! Construct the GET request to convert the optional glance template to text for display.
|
//! Construct the GET request to convert the optional glance template to text for display.
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function fetchGlanceContent() as Void {
|
function fetchGlanceContent() as Void {
|
||||||
if (mGlanceTemplate != null) {
|
if (mGlanceTemplate != null) {
|
||||||
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
|
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
|
||||||
@@ -737,7 +732,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
//!
|
//!
|
||||||
//! @return A string describing the API status
|
//! @return A string describing the API status
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function getApiStatus() as Lang.String {
|
function getApiStatus() as Lang.String {
|
||||||
return mApiStatus;
|
return mApiStatus;
|
||||||
}
|
}
|
||||||
@@ -746,7 +740,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
//!
|
//!
|
||||||
//! @return A string describing the Menu status
|
//! @return A string describing the Menu status
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function getMenuStatus() as Lang.String {
|
function getMenuStatus() as Lang.String {
|
||||||
return mMenuStatus;
|
return mMenuStatus;
|
||||||
}
|
}
|
||||||
@@ -756,7 +749,6 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
//!
|
//!
|
||||||
//! @return A string derived from the glance template (or null)
|
//! @return A string derived from the glance template (or null)
|
||||||
//
|
//
|
||||||
(:glance)
|
|
||||||
function getGlanceText() as Lang.String? {
|
function getGlanceText() as Lang.String? {
|
||||||
return mGlanceText;
|
return mGlanceText;
|
||||||
}
|
}
|
||||||
@@ -800,7 +792,7 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
//! @return The glance view
|
//! @return The glance view
|
||||||
//
|
//
|
||||||
function getGlanceView() as [ WatchUi.GlanceView ] or [ WatchUi.GlanceView, WatchUi.GlanceViewDelegate ] or Null {
|
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;
|
mApiStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
|
||||||
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
|
mMenuStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
|
||||||
Settings.update();
|
Settings.update();
|
||||||
@@ -824,7 +816,9 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
mGlanceTimer = null;
|
mGlanceTimer = null;
|
||||||
fetchMenuConfig();
|
fetchMenuConfig();
|
||||||
fetchApiStatus();
|
fetchApiStatus();
|
||||||
fetchGlanceContent();
|
if (Settings.getWebhookId() != null && !Settings.getWebhookId().equals("")) {
|
||||||
|
fetchGlanceContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Code for when the application settings are updated.
|
//! Code for when the application settings are updated.
|
||||||
@@ -842,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.
|
//! 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 {
|
function getIsApp() as Lang.Boolean {
|
||||||
return mIsApp;
|
return mIsApp;
|
||||||
@@ -857,6 +853,8 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//! Global function to return the application object.
|
//! Global function to return the application object.
|
||||||
|
//!
|
||||||
|
//! @return The application object.
|
||||||
//
|
//
|
||||||
(:glance, :background)
|
(:glance, :background)
|
||||||
function getApp() as HomeAssistantApp {
|
function getApp() as HomeAssistantApp {
|
||||||
|
@@ -297,7 +297,7 @@ class Settings {
|
|||||||
//! @return The augmented HTTP header options.
|
//! @return The augmented HTTP header options.
|
||||||
//
|
//
|
||||||
static function augmentHttpHeaders(options as Lang.Dictionary) {
|
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) {
|
if (mUserHeaderName != null && mUserHeaderName.length() > 0 && mUserHeaderValue != null && mUserHeaderValue.length() > 0) {
|
||||||
options[mUserHeaderName] = mUserHeaderValue;
|
options[mUserHeaderName] = mUserHeaderValue;
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,9 @@ using Toybox.WatchUi;
|
|||||||
//!
|
//!
|
||||||
//! Reference: https://developers.home-assistant.io/docs/api/native-app-integration
|
//! Reference: https://developers.home-assistant.io/docs/api/native-app-integration
|
||||||
//
|
//
|
||||||
|
(:glance)
|
||||||
class WebhookManager {
|
class WebhookManager {
|
||||||
|
private var mSensors as Lang.Array<Lang.Object> = [];
|
||||||
|
|
||||||
//! Callback for requesting a Webhook ID.
|
//! 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
|
//! @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.
|
//! until empty. Each POST request creating one sensor on the local Home Assistant.
|
||||||
//
|
//
|
||||||
function onReturnRegisterWebhookSensor(
|
function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||||
responseCode as Lang.Number,
|
|
||||||
data as Null or Lang.Dictionary or Lang.String,
|
|
||||||
sensors as Lang.Array<Lang.Object>
|
|
||||||
) as Void {
|
|
||||||
switch (responseCode) {
|
switch (responseCode) {
|
||||||
case Communications.BLE_HOST_TIMEOUT:
|
case Communications.BLE_HOST_TIMEOUT:
|
||||||
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
||||||
@@ -176,13 +174,14 @@ class WebhookManager {
|
|||||||
case 200:
|
case 200:
|
||||||
case 201:
|
case 201:
|
||||||
if (data instanceof Lang.Dictionary) {
|
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?;
|
var b = d.get("success") as Lang.Boolean?;
|
||||||
if (b != null and b != false) {
|
if (b != null and b != false) {
|
||||||
if (sensors.size() == 0) {
|
mSensors = mSensors.slice(1, null);
|
||||||
|
if (mSensors.size() == 0) {
|
||||||
getApp().startUpdates();
|
getApp().startUpdates();
|
||||||
} else {
|
} else {
|
||||||
registerWebhookSensor(sensors);
|
registerWebhookSensor();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure, no 'success'.");
|
// 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.
|
//! 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().
|
||||||
//! @param sensors An array of sensors, e.g. As created by `registerWebhookSensors()`.
|
|
||||||
//
|
//
|
||||||
private function registerWebhookSensor(sensors as Lang.Array<Lang.Object>) {
|
private function registerWebhookSensor() {
|
||||||
var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId();
|
var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId();
|
||||||
// System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
|
// System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
|
||||||
// System.println("WebhookManager registerWebhookSensor(): URL=" + url);
|
// System.println("WebhookManager registerWebhookSensor(): URL=" + url);
|
||||||
@@ -226,15 +224,14 @@ class WebhookManager {
|
|||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
"type" => "register_sensor",
|
"type" => "register_sensor",
|
||||||
"data" => sensors[0]
|
"data" => mSensors[0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
:method => Communications.HTTP_REQUEST_METHOD_POST,
|
:method => Communications.HTTP_REQUEST_METHOD_POST,
|
||||||
:headers => Settings.augmentHttpHeaders({
|
:headers => Settings.augmentHttpHeaders({
|
||||||
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
|
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
|
||||||
}),
|
}),
|
||||||
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
|
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
||||||
:context => sensors.slice(1, null)
|
|
||||||
},
|
},
|
||||||
method(:onReturnRegisterWebhookSensor)
|
method(:onReturnRegisterWebhookSensor)
|
||||||
);
|
);
|
||||||
@@ -245,7 +242,7 @@ class WebhookManager {
|
|||||||
function registerWebhookSensors() {
|
function registerWebhookSensors() {
|
||||||
var heartRate = Activity.getActivityInfo().currentHeartRate;
|
var heartRate = Activity.getActivityInfo().currentHeartRate;
|
||||||
|
|
||||||
var sensors = [
|
mSensors = [
|
||||||
{
|
{
|
||||||
"device_class" => "battery",
|
"device_class" => "battery",
|
||||||
"name" => "Battery Level",
|
"name" => "Battery Level",
|
||||||
@@ -283,7 +280,7 @@ class WebhookManager {
|
|||||||
if (Toybox has :ActivityMonitor) {
|
if (Toybox has :ActivityMonitor) {
|
||||||
// System.println("WebhookManager registerWebhookSensors(): has ActivityMonitor class");
|
// System.println("WebhookManager registerWebhookSensors(): has ActivityMonitor class");
|
||||||
var activityInfo = ActivityMonitor.getInfo();
|
var activityInfo = ActivityMonitor.getInfo();
|
||||||
sensors.add({
|
mSensors.add({
|
||||||
"name" => "Steps today",
|
"name" => "Steps today",
|
||||||
"state" => activityInfo.steps == null ? "unknown" : activityInfo.steps,
|
"state" => activityInfo.steps == null ? "unknown" : activityInfo.steps,
|
||||||
"type" => "sensor",
|
"type" => "sensor",
|
||||||
@@ -294,7 +291,7 @@ class WebhookManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (ActivityMonitor.Info has :floorsClimbed) {
|
if (ActivityMonitor.Info has :floorsClimbed) {
|
||||||
sensors.add({
|
mSensors.add({
|
||||||
"name" => "Floors climbed today",
|
"name" => "Floors climbed today",
|
||||||
"state" => activityInfo.floorsClimbed == null ? "unknown" : activityInfo.floorsClimbed,
|
"state" => activityInfo.floorsClimbed == null ? "unknown" : activityInfo.floorsClimbed,
|
||||||
"type" => "sensor",
|
"type" => "sensor",
|
||||||
@@ -306,7 +303,7 @@ class WebhookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ActivityMonitor.Info has :floorsDescended) {
|
if (ActivityMonitor.Info has :floorsDescended) {
|
||||||
sensors.add({
|
mSensors.add({
|
||||||
"name" => "Floors descended today",
|
"name" => "Floors descended today",
|
||||||
"state" => activityInfo.floorsDescended == null ? "unknown" : activityInfo.floorsDescended,
|
"state" => activityInfo.floorsDescended == null ? "unknown" : activityInfo.floorsDescended,
|
||||||
"type" => "sensor",
|
"type" => "sensor",
|
||||||
@@ -318,7 +315,7 @@ class WebhookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ActivityMonitor.Info has :respirationRate) {
|
if (ActivityMonitor.Info has :respirationRate) {
|
||||||
sensors.add({
|
mSensors.add({
|
||||||
"name" => "Respiration rate",
|
"name" => "Respiration rate",
|
||||||
"state" => activityInfo.respirationRate == null ? "unknown" : activityInfo.respirationRate,
|
"state" => activityInfo.respirationRate == null ? "unknown" : activityInfo.respirationRate,
|
||||||
"type" => "sensor",
|
"type" => "sensor",
|
||||||
@@ -345,14 +342,14 @@ class WebhookManager {
|
|||||||
activity = -1;
|
activity = -1;
|
||||||
sub_activity = -1;
|
sub_activity = -1;
|
||||||
}
|
}
|
||||||
sensors.add({
|
mSensors.add({
|
||||||
"name" => "Activity",
|
"name" => "Activity",
|
||||||
"state" => activity,
|
"state" => activity,
|
||||||
"type" => "sensor",
|
"type" => "sensor",
|
||||||
"unique_id" => "activity",
|
"unique_id" => "activity",
|
||||||
"disabled" => !Settings.isSensorsLevelEnabled()
|
"disabled" => !Settings.isSensorsLevelEnabled()
|
||||||
});
|
});
|
||||||
sensors.add({
|
mSensors.add({
|
||||||
"name" => "Sub-activity",
|
"name" => "Sub-activity",
|
||||||
"state" => sub_activity,
|
"state" => sub_activity,
|
||||||
"type" => "sensor",
|
"type" => "sensor",
|
||||||
@@ -361,7 +358,7 @@ class WebhookManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerWebhookSensor(sensors);
|
registerWebhookSensor();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user