Skip to content

Commit

Permalink
Initial solution
Browse files Browse the repository at this point in the history
  • Loading branch information
philipabbey committed Jan 8, 2024
1 parent 24ebc72 commit 0a2d257
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 47 deletions.
6 changes: 6 additions & 0 deletions resources/settings/properties.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
<!-- 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>

<!-- Decide if the menu configuration should be cached. -->
<property id="cache_config" type="boolean">false</property>

<!-- Clear the menu configuration on next application start, and refetch, then set this back to false -->
<property id="clear_cache" type="boolean">false</property>

<!--
Application timeout in seconds, except 0 for no timeout (default). After this amount of elapsed time
with no activity, exit the application.
Expand Down
14 changes: 14 additions & 0 deletions resources/settings/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@
<settingConfig type="alphaNumeric" />
</setting>

<setting
propertyKey="@Properties.cache_config"
title="@Strings.SettingsCacheConfig"
>
<settingConfig type="boolean" />
</setting>

<setting
propertyKey="@Properties.clear_cache"
title="@Strings.SettingsClearCache"
>
<settingConfig type="boolean" />
</setting>

<setting
propertyKey="@Properties.app_timeout"
title="@Strings.SettingsAppTimeout"
Expand Down
3 changes: 3 additions & 0 deletions resources/strings/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<string id="Checking" scope="glance">Checking...</string>
<string id="Unavailable" scope="glance">Unavailable</string>
<string id="Unconfigured" scope="glance">Unconfigured</string>
<string id="Cached" scope="glance">Cached</string>
<string id="GlanceMenu" scope="glance">Menu</string>

<!-- For the settings GUI -->
Expand All @@ -43,6 +44,8 @@
<string id="SettingsApiKeyPrompt">Long-Lived Access Token.</string>
<string id="SettingsApiUrl">URL for HomeAssistant API.</string>
<string id="SettingsConfigUrl">URL for menu configuration (JSON).</string>
<string id="SettingsCacheConfig">Should the application cache the menu configuration?</string>
<string id="SettingsClearCache">Should the application clear the existing cache next time it is started?</string>
<string id="SettingsAppTimeout">Timeout in seconds. Exit the application after this period of inactivity to save the device battery.</string>
<string id="SettingsConfirmTimeout">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.</string>
<string id="SettingsMenuItemStyle">Menu item style.</string>
Expand Down
116 changes: 72 additions & 44 deletions source/HomeAssistantApp.mc
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,16 @@ class HomeAssistantApp extends Application.AppBase {
}
return ErrorView.create(RezStrings.getNoInternet() + ".");
} else {
fetchMenuConfig();
var isCached = fetchMenuConfig();
fetchApiStatus();
if (WidgetApp.isWidget) {
return [new RootView(self), new RootViewDelegate(self)] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else {
return [new WatchUi.View(), new WatchUi.BehaviorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
if (isCached) {
return [mHaMenu, new HomeAssistantViewDelegate(true)] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else {
return [new WatchUi.View(), new WatchUi.BehaviorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
}
}
}
}
Expand Down Expand Up @@ -193,21 +197,11 @@ class HomeAssistantApp extends Application.AppBase {

case 200:
mMenuStatus = RezStrings.getAvailable();
if (Settings.getCacheConfig()) {
Storage.setValue("menu", data as Lang.Dictionary);
}
if (!mIsGlance) {
mHaMenu = new HomeAssistantView(data, null);
mQuitTimer.begin();
if (Settings.getIsWidgetStartNoTap()) {
// As soon as the menu has been fetched start show the menu of items.
// This behaviour is inconsistent with the standard Garmin User Interface, but has been
// requested by users so has been made the non-default option.
pushHomeAssistantMenuView();
}
mItemsToUpdate = mHaMenu.getItemsToUpdate();
// Start the continuous update process that continues for as long as the application is running.
// The chain of functions from 'updateNextMenuItem()' calls 'updateNextMenuItem()' on completion.
if (mItemsToUpdate.size() > 0) {
updateNextMenuItem();
}
buildMenu(data);
if (!WidgetApp.isWidget) {
WatchUi.switchToView(mHaMenu, new HomeAssistantViewDelegate(false), WatchUi.SLIDE_IMMEDIATE);
}
Expand All @@ -226,44 +220,78 @@ class HomeAssistantApp extends Application.AppBase {
WatchUi.requestUpdate();
}

// 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 Void {
function fetchMenuConfig() as Lang.Boolean {
if (Settings.getConfigUrl().equals("")) {
mMenuStatus = RezStrings.getUnconfigured();
WatchUi.requestUpdate();
} else {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
}
if (mIsGlance) {
WatchUi.requestUpdate();
} else {
ErrorView.show(RezStrings.getNoPhone() + ".");
}
mMenuStatus = RezStrings.getUnavailable();
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
if (mIsGlance) {
WatchUi.requestUpdate();
var menu = Storage.getValue("menu") as Lang.Dictionary;
if (menu != null and Settings.getClearCache()) {
Storage.deleteValue("menu");
menu = null;
Settings.unsetClearCache();
}
if (menu == null) {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
}
if (mIsGlance) {
WatchUi.requestUpdate();
} else {
ErrorView.show(RezStrings.getNoPhone() + ".");
}
mMenuStatus = RezStrings.getUnavailable();
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
if (mIsGlance) {
WatchUi.requestUpdate();
} else {
ErrorView.show(RezStrings.getNoInternet() + ".");
}
mMenuStatus = RezStrings.getUnavailable();
} else {
ErrorView.show(RezStrings.getNoInternet() + ".");
Communications.makeWebRequest(
Settings.getConfigUrl(),
null,
{
:method => Communications.HTTP_REQUEST_METHOD_GET,
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
},
method(:onReturnFetchMenuConfig)
);
}
mMenuStatus = RezStrings.getUnavailable();
} else {
Communications.makeWebRequest(
Settings.getConfigUrl(),
null,
{
:method => Communications.HTTP_REQUEST_METHOD_GET,
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
},
method(:onReturnFetchMenuConfig)
);
mMenuStatus = RezStrings.getCached();
if (!mIsGlance) {
buildMenu(menu);
}
return true;
}
}
return false;
}

private function buildMenu(menu as Lang.Dictionary) {
mHaMenu = new HomeAssistantView(menu, null);
mQuitTimer.begin();
if (Settings.getIsWidgetStartNoTap()) {
// As soon as the menu has been fetched start show the menu of items.
// This behaviour is inconsistent with the standard Garmin User Interface, but has been
// requested by users so has been made the non-default option.
pushHomeAssistantMenuView();
}
mItemsToUpdate = mHaMenu.getItemsToUpdate();
// Start the continuous update process that continues for as long as the application is running.
// The chain of functions from 'updateNextMenuItem()' calls 'updateNextMenuItem()' on completion.
if (mItemsToUpdate.size() > 0) {
updateNextMenuItem();
}
}

// Callback function after completing the GET request to fetch the API status.
Expand Down
2 changes: 2 additions & 0 deletions source/HomeAssistantToggleMenuItem.mc
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
var myTimer = new Timer.Timer();
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false);
// Revert status
status = getApp().getApiStatus();
break;

case 404:
Expand Down
4 changes: 2 additions & 2 deletions source/HomeAssistantView.mc
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ using Toybox.WatchUi;

class HomeAssistantView extends WatchUi.Menu2 {
// List of items that need to have their status updated periodically
private var mListToggleItems = [];
private var mListMenuItems = [];
private var mListToggleItems = [];
private var mListMenuItems = [];

function initialize(
definition as Lang.Dictionary,
Expand Down
8 changes: 8 additions & 0 deletions source/RezStrings.mc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class RezStrings {
(:glance)
private static var strUnconfigured as Lang.String or Null;
(:glance)
private static var strCached as Lang.String or Null;
(:glance)
private static var strGlanceMenu as Lang.String or Null;
private static var strLabelToggle as Lang.Dictionary or Null;

Expand All @@ -71,6 +73,7 @@ class RezStrings {
strChecking = WatchUi.loadResource($.Rez.Strings.Checking);
strUnavailable = WatchUi.loadResource($.Rez.Strings.Unavailable);
strUnconfigured = WatchUi.loadResource($.Rez.Strings.Unconfigured);
strCached = WatchUi.loadResource($.Rez.Strings.Cached);
strGlanceMenu = WatchUi.loadResource($.Rez.Strings.GlanceMenu);
}

Expand All @@ -97,6 +100,7 @@ class RezStrings {
strChecking = WatchUi.loadResource($.Rez.Strings.Checking);
strUnavailable = WatchUi.loadResource($.Rez.Strings.Unavailable);
strUnconfigured = WatchUi.loadResource($.Rez.Strings.Unconfigured);
strCached = WatchUi.loadResource($.Rez.Strings.Cached);
strGlanceMenu = WatchUi.loadResource($.Rez.Strings.GlanceMenu);
strLabelToggle = {
:enabled => WatchUi.loadResource($.Rez.Strings.MenuItemOn) as Lang.String,
Expand Down Expand Up @@ -184,6 +188,10 @@ class RezStrings {
return strUnconfigured;
}

static function getCached() as Lang.String {
return strCached;
}

static function getGlanceMenu() as Lang.String {
return strGlanceMenu;
}
Expand Down
19 changes: 18 additions & 1 deletion source/Settings.mc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Settings {
private static var mApiKey as Lang.String = "";
private static var mApiUrl as Lang.String = "";
private static var mConfigUrl as Lang.String = "";
private static var mCacheConfig as Lang.Boolean = false;
private static var mClearCache as Lang.Boolean = false;
private static var mAppTimeout as Lang.Number = 0; // seconds
private static var mConfirmTimeout as Lang.Number = 3; // seconds
private static var mMenuStyle as Lang.Number = MENU_STYLE_ICONS;
Expand All @@ -49,6 +51,8 @@ class Settings {
mApiKey = Properties.getValue("api_key");
mApiUrl = Properties.getValue("api_url");
mConfigUrl = Properties.getValue("config_url");
mCacheConfig = Properties.getValue("cache_config");
mClearCache = Properties.getValue("clear_cache");
mAppTimeout = Properties.getValue("app_timeout");
mConfirmTimeout = Properties.getValue("confirm_timeout");
mMenuStyle = Properties.getValue("menu_theme");
Expand Down Expand Up @@ -99,7 +103,20 @@ class Settings {
static function getConfigUrl() as Lang.String {
return mConfigUrl;
}


static function getCacheConfig() as Lang.Boolean {
return mCacheConfig;
}

static function getClearCache() as Lang.Boolean {
return mClearCache;
}

static function unsetClearCache() {
mClearCache = false;
Properties.setValue("clear_cache", mClearCache);
}

static function getAppTimeout() as Lang.Number {
return mAppTimeout * 1000; // Convert to milliseconds
}
Expand Down

0 comments on commit 0a2d257

Please sign in to comment.