Compare commits

...

6 Commits

Author SHA1 Message Date
Philip Abbey
0d73de494e Tidy 2025-07-24 20:56:08 +01:00
Philip Abbey
a686e1a104 Reordered settings 2025-07-24 19:49:44 +01:00
Philip Abbey
8868f2152c Comments & Dictionaries
Reformatted comments to work in VSCode and converted `dict.get(:key)` to `dict[:key]` syntax as its nicer.
2025-07-24 18:54:27 +01:00
Philip Abbey
70f05e8912 Added missing code headers
And included vincentezw in headers for the code he touched.
2025-07-22 22:14:46 +01:00
Philip Abbey
c138fad6ca Update properties.xml
Moved Wi-Fi option up to be more prominent.
2025-07-22 22:05:04 +01:00
Philip Abbey
9641313492 Wifi -> Wi-Fi
Amended in presentational aspects only, not in the actual code. "Wi-Fi" is the proper noun and registered trademark.
2025-07-22 22:03:10 +01:00
21 changed files with 300 additions and 238 deletions

View File

@@ -8,7 +8,7 @@
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 & Someone0nEarth, 31 October 2023
P A Abbey & J D Abbey & Someone0nEarth & vincentezw, 31 October 2023
-->
@@ -38,6 +38,12 @@
-->
<property id="clear_cache" type="boolean">false</property>
<!--
Enables the SyncDelegate and prompt to send a command over Wi-Fi/LTE.
This will only show when not connected to the user's phone.
-->
<property id="wifi_lte_execution" type="boolean">false</property>
<!--
Enable notification via vibrations, typically for confirmation of actions.
-->
@@ -95,10 +101,4 @@
for trouble shooting.
-->
<property id="webhook_id" type="string"></property>
<!--
Enables the SyncDelegate and prompt to send a command over Wifi/LTE.
This will only show when not connected to the user's phone.
-->
<property id="wifi_lte_execution" type="boolean">false</property>
</properties>

View File

@@ -8,7 +8,7 @@
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 & Someone0nEarth, 31 October 2023
P A Abbey & J D Abbey & Someone0nEarth & vincentezw, 31 October 2023
-->
@@ -51,6 +51,15 @@
<settingConfig type="boolean" />
</setting>
<group enableIfTrue="@Properties.cache_config" id="wifiLteExection" title="@Strings.WifiLteExecution" description="@Strings.WifiLteExecutionDescription">
<setting
propertyKey="@Properties.wifi_lte_execution"
title="@Strings.WifiLteExecutionEnable"
>
<settingConfig type="boolean" />
</setting>
</group>
<setting
propertyKey="@Properties.enable_vibration"
title="@Strings.SettingsVibration"
@@ -117,12 +126,4 @@
<settingConfig type="alphaNumeric" readonly="true" />
</setting>
<group enableIfTrue="@Properties.cache_config" id="wifiLteExection" title="@Strings.WifiLteExecution" description="@Strings.WifiLteExecutionDescription">
<setting
propertyKey="@Properties.wifi_lte_execution"
title="@Strings.WifiLteExecutionEnable"
>
<settingConfig type="boolean" />
</setting>
</group>
</settings>

View File

@@ -8,7 +8,7 @@
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 & Someone0nEarth, 31 October 2023
P A Abbey & J D Abbey & Someone0nEarth & vincentezw, 31 October 2023
-->
@@ -44,8 +44,8 @@
<string id="UnhandledHttpErr">HTTP request returned error code = </string>
<string id="WebhookFailed">Failed to register Webhook</string>
<string id="WrongPin">Wrong PIN</string>
<string id="WifiLteNotAvailable">No Wifi or LTE available</string>
<string id="WifiLtePrompt">Execute over Wifi/LTE?</string>
<string id="WifiLteNotAvailable">No Wi-Fi or LTE available</string>
<string id="WifiLtePrompt">Execute over Wi-Fi/LTE?</string>
<string id="WifiLteExecutionTitle">Sending to Home Assistant.</string>
<string id="WifiLteExecutionDataError">No data received.</string>
@@ -70,7 +70,7 @@
<string id="SettingsEnableBatteryLevel">Enable the background service to send the device battery level, location and (if supported) activity data to Home Assistant.</string>
<string id="SettingsBatteryLevelRefreshRate">The refresh rate (in minutes) at which the background service should repeat sending data.</string>
<string id="WebhookId">(Read only) The Webhook ID created by the device for background service updates. You might require this for debugging.</string>
<string id="WifiLteExecution">Wifi/LTE execution mode.</string>
<string id="WifiLteExecutionEnable">Enable executing commands over Wifi/LTE.</string>
<string id="WifiLteExecutionDescription">Allows the app to start without phone connection (when menu is cached), and prompt to execute command over Wifi/LTE.</string>
<string id="WifiLteExecution">Wi-Fi/LTE execution mode.</string>
<string id="WifiLteExecutionEnable">Enable executing commands over Wi-Fi/LTE.</string>
<string id="WifiLteExecutionDescription">Allows the app to start without phone connection (when menu is cached), and prompt to execute command over Wi-Fi/LTE.</string>
</strings>

View File

@@ -35,38 +35,38 @@ class Alert extends WatchUi.View {
//! Class Constructor
//! @param params A dictionary object as follows:<br>
//! &lbrace;<br>
//! &emsp; :timeout as Lang.Number, // Timeout in millseconds<br>
//! &emsp; :font as Graphics.FontType, // Text font size<br>
//! &emsp; :text as Lang.String, // Text to display<br>
//! &emsp; :fgcolor as Graphics.ColorType, // Foreground Colour<br>
//! &emsp; :bgcolor as Graphics.ColorType // Background Colour<br>
//! &rbrace;
//! `{`<br>
//! &emsp; `:timeout as Lang.Number,` // Timeout in millseconds<br>
//! &emsp; `:font as Graphics.FontType,` // Text font size<br>
//! &emsp; `:text as Lang.String,` // Text to display<br>
//! &emsp; `:fgcolor as Graphics.ColorType,` // Foreground Colour<br>
//! &emsp; `:bgcolor as Graphics.ColorType` // Background Colour<br>
//! `}`
//
function initialize(params as Lang.Dictionary) {
View.initialize();
mText = params.get(:text) as Lang.String;
mText = params[:text] as Lang.String;
if (mText == null) {
mText = "Alert";
}
mFont = params.get(:font) as Graphics.FontType;
mFont = params[:font] as Graphics.FontType;
if (mFont == null) {
mFont = Graphics.FONT_MEDIUM;
}
mFgcolor = params.get(:fgcolor) as Graphics.ColorType;
mFgcolor = params[:fgcolor] as Graphics.ColorType;
if (mFgcolor == null) {
mFgcolor = Graphics.COLOR_BLACK;
}
mBgcolor = params.get(:bgcolor) as Graphics.ColorType;
mBgcolor = params[:bgcolor] as Graphics.ColorType;
if (mBgcolor == null) {
mBgcolor = Graphics.COLOR_WHITE;
}
mTimeout = params.get(:timeout) as Lang.Number;
mTimeout = params[:timeout] as Lang.Number;
if (mTimeout == null) {
mTimeout = 2000;
}

View File

@@ -45,10 +45,10 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
//! Called on completion of an activity.
//!
//! @param activity Specified as a Dictionary with two items.<br>
//! &lbrace;<br>
//! &emsp; :sport as Activity.Sport<br>
//! &emsp; :subSport as Activity.SubSport<br>
//! &rbrace;
//! `{`<br>
//! &emsp; `:sport as Activity.Sport`<br>
//! &emsp; `:subSport as Activity.SubSport`<br>
//! `}`
//
function onActivityCompleted(
activity as {
@@ -101,8 +101,8 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
//! @param sub_activity Activity.SubSport
//
private function doUpdate(
activity as Lang.Number or Null,
sub_activity as Lang.Number or Null
activity as Lang.Number?,
sub_activity as Lang.Number?
) {
// System.println("BackgroundServiceDelegate onTemporalEvent(): Making API call.");
var position = Position.getInfo();
@@ -154,7 +154,7 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
(Properties.getValue("api_url") as Lang.String) + "/webhook/" + (Properties.getValue("webhook_id") as Lang.String),
{
"type" => "update_location",
"data" => data,
"data" => data
},
{
:method => Communications.HTTP_REQUEST_METHOD_POST,

View File

@@ -39,7 +39,7 @@ class ErrorView extends ScalableView {
// Vertical spacing between the top of the face and the error icon
private var mErrorIconMargin as Lang.Number;
private var mErrorIcon;
private var mTextArea as WatchUi.TextArea or Null;
private var mTextArea as WatchUi.TextArea?;
private var mAntiAlias as Lang.Boolean = false;
private static var instance;
@@ -169,7 +169,7 @@ class ErrorDelegate extends WatchUi.BehaviorDelegate {
WatchUi.BehaviorDelegate.initialize();
}
//! Process the event to clear the ErrorView.
//! Handle the back button (ESC) to clear the ErrorView.
//
function onBack() as Lang.Boolean {
getApp().getQuitTimer().reset();

View File

@@ -9,7 +9,7 @@
// 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 & Someone0nEarth & moesterheld, 31 October 2023
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld & vincentezw, 31 October 2023
//
//-----------------------------------------------------------------------------------
@@ -25,17 +25,17 @@ using Toybox.Timer;
//
(:glance, :background)
class HomeAssistantApp extends Application.AppBase {
private var mApiStatus as Lang.String or Null;
private var mApiStatus as Lang.String?;
private var mHasToast as Lang.Boolean = false;
private var mMenuStatus as Lang.String or Null;
private var mHaMenu as HomeAssistantView or Null;
private var mGlanceTemplate as Lang.String or Null = null;
private var mGlanceText as Lang.String or Null = null;
private var mQuitTimer as QuitTimer or Null;
private var mGlanceTimer as Timer.Timer or Null;
private var mUpdateTimer as Timer.Timer or Null;
private var mMenuStatus as Lang.String?;
private var mHaMenu as HomeAssistantView?;
private var mGlanceTemplate as Lang.String? = null;
private var mGlanceText as Lang.String? = null;
private var mQuitTimer as QuitTimer?;
private var mGlanceTimer as Timer.Timer?;
private var mUpdateTimer as Timer.Timer?;
// Array initialised by onReturnFetchMenuConfig()
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem> or Null;
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 mUpdating as Lang.Boolean = false; // Don't start a second chain of updates
@@ -132,10 +132,10 @@ class HomeAssistantApp extends Application.AppBase {
// System.println("HomeAssistantApp getInitialView(): No Phone connection, no cached menu, skipping API call.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoPhoneNoCache) as Lang.String);
} else if (! System.getDeviceSettings().phoneConnected and ! Settings.getWifiLteExecutionEnabled()) {
// System.println("HomeAssistantApp getInitialView(): No Phone connection and wifi disabled, skipping API call.");
// System.println("HomeAssistantApp getInitialView(): No Phone connection and Wi-Fi disabled, skipping API call.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String);
} else if (! System.getDeviceSettings().connectionAvailable and ! Settings.getWifiLteExecutionEnabled()) {
// System.println("HomeAssistantApp getInitialView(): No Internet connection and wifi disabled, skipping API call.");
// System.println("HomeAssistantApp getInitialView(): No Internet connection and Wi-Fi disabled, skipping API call.");
return ErrorView.create(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String);
} else {
var isCached = fetchMenuConfig();
@@ -239,15 +239,13 @@ class HomeAssistantApp extends Application.AppBase {
//! Can we use the cached menu?
//!
//! @return Return true if there's a menu in cache, and if the user has enabled the cache and
//! has not requested to have the cache busted.
//! has not requested to have the cache refreshed.
//
function hasCachedMenu() as Lang.Boolean {
if (Settings.getClearCache() || !Settings.getCacheConfig()) {
return false;
}
var menu = Storage.getValue("menu") as Lang.Dictionary;
return menu != null;
return (Storage.getValue("menu") as Lang.Dictionary) != null;
}
//! Fetch the menu configuration over HTTPS, which might be locally cached.
@@ -436,7 +434,7 @@ class HomeAssistantApp extends Application.AppBase {
var phoneConnected = System.getDeviceSettings().phoneConnected;
var connectionAvailable = System.getDeviceSettings().connectionAvailable;
// In Wifi/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)) {
// Notify only once per disconnection cycle
if (!mNotifiedNoBle) {
@@ -757,10 +755,10 @@ class HomeAssistantApp extends Application.AppBase {
//! Return the optional glance text that overrides the default glance content. This
//! is derived from the glance template.
//!
//! @return A string derived from the glance template
//! @return A string derived from the glance template (or null)
//
(:glance)
function getGlanceText() as Lang.String or Null {
function getGlanceText() as Lang.String? {
return mGlanceText;
}

View File

@@ -9,7 +9,7 @@
// 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 & Someone0nEarth, 19 November 2023
// P A Abbey & J D Abbey & Someone0nEarth & vincentezw, 19 November 2023
//
//-----------------------------------------------------------------------------------
@@ -35,8 +35,7 @@ class HomeAssistantConfirmation extends WatchUi.Confirmation {
//! Delegate to respond to the confirmation request.
//
class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
private static var mTimer as Timer.Timer or Null;
private static var mTimer as Timer.Timer?;
private var mConfirmMethod as Method(state as Lang.Boolean) as Void;
private var mState as Lang.Boolean;
private var mToggleMethod as Method(state as Lang.Boolean) as Void or Null;
@@ -44,18 +43,22 @@ class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
//! Class Constructor
//!
//! @param options A dictionary describing the following options:
//! - callback Method to call on confirmation.
//! - confirmationView Confirmation the delegate is active for
//! - state Wanted state of a toggle button.
//! - toggle Optional setEnabled method to untoggle ToggleItem.
//! @param options A dictionary describing the following options:<br>
//! `{`<br>
//! &emsp; `:callback as Method(state as Lang.Boolean) as Void,` // Method to call on confirmation.<br>
//! &emsp; `:confirmationView as WatchUi.Confirmation,` // Confirmation the delegate is active for<br>
//! &emsp; `:state as Lang.Boolean,` // Wanted state of a toggle button.<br>
//! &emsp; `:toggle as Method(state as Lang.Boolean)?` // Optional setEnabled method to untoggle ToggleItem.<br>
//! `}`
//
function initialize(options as {
function initialize(
options as {
:callback as Method(state as Lang.Boolean) as Void,
:confirmationView as WatchUi.Confirmation,
:state as Lang.Boolean,
:toggleMethod as Method(state as Lang.Boolean) or Null,
}) {
:toggleMethod as Method(state as Lang.Boolean)?
}
) {
if (mTimer != null) {
mTimer.stop();
}
@@ -78,7 +81,7 @@ class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
//! Respond to the confirmation event.
//!
//! @param response code
//! @param response response code
//! @return Required to meet the function prototype, but the base class does not indicate a definition.
//
function onResponse(response as WatchUi.Confirm) as Lang.Boolean {
@@ -98,6 +101,7 @@ class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
}
//! Function supplied to a timer in order to limit the time for which the confirmation can be provided.
//
function onTimeout() as Void {
mTimer.stop();
// Undo the toggle, if we have one

View File

@@ -44,12 +44,12 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
private var mTextWidth as Lang.Number = 0;
// Re-usable text items for drawing
private var mApp as HomeAssistantApp;
private var mTitle as WatchUi.Text or Null;
private var mApiText as WatchUi.Text or Null;
private var mApiStatus as WatchUi.Text or Null;
private var mMenuText as WatchUi.Text or Null;
private var mMenuStatus as WatchUi.Text or Null;
private var mGlanceContent as WatchUi.TextArea or Null;
private var mTitle as WatchUi.Text?;
private var mApiText as WatchUi.Text?;
private var mApiStatus as WatchUi.Text?;
private var mMenuText as WatchUi.Text?;
private var mMenuStatus as WatchUi.Text?;
private var mGlanceContent as WatchUi.TextArea?;
private var mAntiAlias as Lang.Boolean = false;
//! Class Constructor

View File

@@ -30,7 +30,7 @@ class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
icon as WatchUi.Drawable,
options as {
:alignment as WatchUi.MenuItem.Alignment
} or Null
}?
) {
if (options != null) {
options.put(:icon, icon);

View File

@@ -20,7 +20,7 @@ using Toybox.Graphics;
//! Generic menu button with an icon that optionally renders a Home Assistant Template.
//
class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
private var mTemplate as Lang.String or Null;
private var mTemplate as Lang.String?;
//! Class Constructor
//!
@@ -34,13 +34,13 @@ class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
options as {
:alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
} or Null
}?
) {
WatchUi.IconMenuItem.initialize(
label,
null,
null,
options.get(:icon),
options[:icon],
options
);
mTemplate = template;
@@ -56,9 +56,9 @@ class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
//! Return the menu item's template.
//!
//! @return A string with the menu item's template definition.
//! @return A string with the menu item's template definition (or null).
//
function getTemplate() as Lang.String or Null {
function getTemplate() as Lang.String? {
return mTemplate;
}

View File

@@ -74,8 +74,8 @@ class HomeAssistantMenuItemFactory {
//
function toggle(
label as Lang.String or Lang.Symbol,
entity_id as Lang.String or Null,
template as Lang.String or Null,
entity_id as Lang.String?,
template as Lang.String?,
options as {
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
@@ -105,10 +105,10 @@ class HomeAssistantMenuItemFactory {
//
function tap(
label as Lang.String or Lang.Symbol,
entity_id as Lang.String or Null,
template as Lang.String or Null,
service as Lang.String or Null,
data as Lang.Dictionary or Null,
entity_id as Lang.String?,
template as Lang.String?,
service as Lang.String?,
data as Lang.Dictionary?,
options as {
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
@@ -156,7 +156,7 @@ class HomeAssistantMenuItemFactory {
//
function group(
definition as Lang.Dictionary,
template as Lang.String or Null
template as Lang.String?
) as WatchUi.MenuItem {
return new HomeAssistantGroupMenuItem(
definition,

View File

@@ -9,7 +9,7 @@
// 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 & Someone0nEarth & moesterheld, 31 October 2023
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld & vincentezw, 31 October 2023
//
//-----------------------------------------------------------------------------------
@@ -45,7 +45,8 @@ class PinDigit extends WatchUi.Selectable {
var button = new PinDigitButton({
:width => width,
:height => height,
:label => digit
:label => digit,
:touched => false
});
var buttonTouched = new PinDigitButton({
@@ -83,16 +84,23 @@ class PinDigit extends WatchUi.Selectable {
//! Class Constructor
//!
//! @param options See `Drawable.initialize()`, but with `:label` and `:touched` added.<br>
//! &lbrace;<br>
//! &emsp; :label as Lang.Number, // The digit 0..9 to display<br>
//! &emsp; :touched as Lang.Boolean, // Should the digit be filled to indicate it has been pressed?<br>
//! `{`<br>
//! &emsp; `:label as Lang.Number,` // The digit 0..9 to display<br>
//! &emsp; `:touched as Lang.Boolean,` // Should the digit be filled to indicate it has been pressed?<br>
//! &emsp; + those required by `Drawable.initialize()`<br>
//! &rbrace;
//! ``}`
//
function initialize(options) {
function initialize(
options as {
:width as Lang.Float,
:height as Lang.Float,
:label as Lang.Number,
:touched as Lang.Boolean
}
) {
Drawable.initialize(options);
mText = options.get(:label);
mTouched = options.get(:touched);
mText = options[:label];
mTouched = options[:touched];
}
//! Draw the PIN digit button.
@@ -182,10 +190,10 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
private var mPin as Lang.String;
private var mEnteredPin as Lang.String;
private var mConfirmMethod as Method(state as Lang.Boolean) as Void;
private var mTimer as Timer.Timer or Null;
private var mTimer as Timer.Timer?;
private var mState as Lang.Boolean;
private var mFailures as PinFailures;
private var mToggleMethod as Method(state as Lang.Boolean) as Void or Null;
private var mToggleMethod as Method(state as Lang.Boolean) as Void?;
private var mView as HomeAssistantPinConfirmationView;
//! Class Constructor
@@ -202,7 +210,7 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
:pin as Lang.String,
:state as Lang.Boolean,
:view as HomeAssistantPinConfirmationView,
:toggleMethod as (Method(state as Lang.Boolean) as Void) or Null,
:toggleMethod as (Method(state as Lang.Boolean) as Void)?,
}) {
BehaviorDelegate.initialize();
mFailures = new PinFailures();
@@ -334,7 +342,7 @@ class PinFailures {
const STORAGE_KEY_LOCKED as Lang.String = "pin_locked";
private var mFailures as Lang.Array<Lang.Number>;
private var mLockedUntil as Lang.Number or Null;
private var mLockedUntil as Lang.Number?;
//! Class Constructor
//

View File

@@ -9,7 +9,7 @@
// 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 & Someone0nEarth, 19 November 2023
// P A Abbey & J D Abbey & Someone0nEarth & vincentezw, 19 November 2023
//
//-----------------------------------------------------------------------------------
@@ -49,8 +49,8 @@ class HomeAssistantService {
var entity_id;
var exit = false;
if (c != null) {
entity_id = c.get(:entity_id) as Lang.String;
exit = c.get(:exit) as Lang.Boolean;
entity_id = c[:entity_id] as Lang.String;
exit = c[:exit] as Lang.Boolean;
}
// System.println("HomeAssistantService onReturnCall() Response Code: " + responseCode);
// System.println("HomeAssistantService onReturnCall() Response Data: " + data);
@@ -125,7 +125,7 @@ class HomeAssistantService {
//
function call(
service as Lang.String,
data as Lang.Dictionary or Null,
data as Lang.Dictionary?,
exit as Lang.Boolean
) as Void {
var phoneConnected = System.getDeviceSettings().phoneConnected;

View File

@@ -1,25 +1,44 @@
//-----------------------------------------------------------------------------------
//
// 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 & vincentezw, 22 July 2025
//
//-----------------------------------------------------------------------------------
using Toybox.Communications;
using Toybox.Lang;
// SyncDelegate to execute single command via POST request to Home Assistant
//! SyncDelegate to execute single command via POST request to the Home Assistant
//! server.
//
class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
private static var syncError as Lang.String or Null;
//! Retain the last synchronisation error.
private static var syncError as Lang.String?;
// Initialize an instance of this delegate
//! Class Constructor
//
public function initialize() {
SyncDelegate.initialize();
}
//! Called by the system to determine if a sync is needed
//! Called by the system to determine if a synchronisation is needed
//
public function isSyncNeeded() as Lang.Boolean {
return true;
}
//! Called by the system when starting a bulk sync.
//! Called by the system when starting a bulk synchronisation.
//
public function onStartSync() as Void {
syncError = null;
if (WifiLteExecutionConfirmDelegate.mCommandData == null) {
syncError = WatchUi.loadResource($.Rez.Strings.WifiLteExecutionDataError) as Lang.String;
onStopSync();
@@ -50,8 +69,13 @@ class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
}
}
// Performs a POST request to Hass with a given payload and URL, and calls haCallback
private function performRequest(url as Lang.String, data as Lang.Dictionary or Null) {
//! Performs a POST request to the Home Assistant server with a given payload and URL, and calls
//! haCallback.
//!
//! @param url URL for the API call.
//! @param data Data to be supplied to the API call.
//
private function performRequest(url as Lang.String, data as Lang.Dictionary?) {
Communications.makeWebRequest(
url,
data, // May include {"entity_id": xxxx} for service calls
@@ -68,7 +92,11 @@ class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
}
//! Handle callback from request
public function haCallback(code as Lang.Number, data as Null or Lang.Dictionary) as Void {
//!
//! @param responseCode Response code.
//! @param data Response data.
//
public function haCallback(code as Lang.Number, data as Lang.Dictionary?) as Void {
Communications.notifySyncProgress(100);
if (code == 200) {
syncError = null;
@@ -100,11 +128,11 @@ class HomeAssistantSyncDelegate extends Communications.SyncDelegate {
}
//! Clean up
//
public function onStopSync() as Void {
if (WifiLteExecutionConfirmDelegate.mCommandData[:exit]) {
System.exit();
}
Communications.cancelAllRequests();
Communications.notifySyncComplete(syncError);
}

View File

@@ -9,7 +9,7 @@
// 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 & Someone0nEarth & moesterheld, 31 October 2023
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld & vincentezw, 31 October 2023
//
//-----------------------------------------------------------------------------------
@@ -21,11 +21,11 @@ using Toybox.Graphics;
//
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
private var mHomeAssistantService as HomeAssistantService;
private var mService as Lang.String or Null;
private var mService as Lang.String?;
private var mConfirm as Lang.Boolean;
private var mExit as Lang.Boolean;
private var mPin as Lang.Boolean;
private var mData as Lang.Dictionary or Null;
private var mData as Lang.Dictionary?;
//! Class Constructor
//!
@@ -44,32 +44,32 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
function initialize(
label as Lang.String or Lang.Symbol,
template as Lang.String,
service as Lang.String or Null,
data as Lang.Dictionary or Null,
service as Lang.String?,
data as Lang.Dictionary?,
options as {
:alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol,
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
:pin as Lang.Boolean
} or Null,
}?,
haService as HomeAssistantService
) {
HomeAssistantMenuItem.initialize(
label,
template,
{
:alignment => options.get(:alignment),
:icon => options.get(:icon)
:alignment => options[:alignment],
:icon => options[:icon]
}
);
mHomeAssistantService = haService;
mService = service;
mData = data;
mExit = options.get(:exit);
mConfirm = options.get(:confirm);
mPin = options.get(:pin);
mExit = options[:exit];
mConfirm = options[:confirm];
mPin = options[:pin];
}
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.

View File

@@ -9,7 +9,7 @@
// 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 & Someone0nEarth & moesterheld, 31 October 2023
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld & vincentezw, 31 October 2023
//
//-----------------------------------------------------------------------------------
@@ -39,14 +39,14 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
function initialize(
label as Lang.String or Lang.Symbol,
template as Lang.String,
data as Lang.Dictionary or Null,
data as Lang.Dictionary?,
options as {
:alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol,
:exit as Lang.Boolean,
:confirm as Lang.Boolean,
:pin as Lang.Boolean
} or Null
}?
) {
WatchUi.ToggleMenuItem.initialize(
label,
@@ -54,8 +54,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
null,
false,
{
:alignment => options.get(:alignment),
:icon => options.get(:icon)
:alignment => options[:alignment],
:icon => options[:icon]
}
);
if (Attention has :vibrate) {
@@ -63,9 +63,9 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
}
mData = data;
mTemplate = template;
mExit = options.get(:exit);
mConfirm = options.get(:confirm);
mPin = options.get(:pin);
mExit = options[:exit];
mConfirm = options[:confirm];
mPin = options[:pin];
}
//! Set the state of a toggle menu item.
@@ -82,17 +82,17 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
//! Return the menu item's template.
//!
//! @return A string with the menu item's template definition.
//! @return A string with the menu item's template definition (or null).
//
function getTemplate() as Lang.String or Null {
function getTemplate() as Lang.String? {
return mTemplate;
}
//! Return a toggle menu item's state template.
//!
//! @return A string with the menu item's template definition.
//! @return A string with the menu item's template definition (or null).
//
function getToggleTemplate() as Lang.String or Null {
function getToggleTemplate() as Lang.String? {
return "{{states('" + mData.get("entity_id") + "')}}";
}
@@ -218,7 +218,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
//! @param data An array of dictionaries, each representing a Home Assistant entity state.
//
function setToggleStateWithData(data as Lang.Array) {
// if there's no response body, let's assume that what we did, happened, and flip the toggle
// If there's no response body, let's assume that what we did actually happened and flip the toggle.
if (data.size() == 0) {
setEnabled(!isEnabled());
}
@@ -350,7 +350,6 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
private function wifiPrompt(s as Lang.Boolean) as Void {
var id = mData.get("entity_id") as Lang.String;
var url = getUrl(id, s);
var dialogMsg = WatchUi.loadResource($.Rez.Strings.WifiLtePrompt) as Lang.String;
var dialog = new WatchUi.Confirmation(dialogMsg);
WatchUi.pushView(
@@ -374,7 +373,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
//!
//! @return Full service URL string.
//
private function getUrl(id as Lang.String, s as Lang.Boolean) as Lang.String {
private static function getUrl(id as Lang.String, s as Lang.Boolean) as Lang.String {
var url = Settings.getApiUrl() + "/services/";
if (s) {
url = url + id.substring(0, id.find(".")) + "/turn_on";

View File

@@ -30,7 +30,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
options as {
:focus as Lang.Number,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
} or Null
}?
) {
if (options == null) {
options = { :title => definition.get("title") as Lang.String };
@@ -42,17 +42,17 @@ class HomeAssistantView extends WatchUi.Menu2 {
var items = definition.get("items") as Lang.Array<Lang.Dictionary>;
for (var i = 0; i < items.size(); i++) {
if (items[i] instanceof(Lang.Dictionary)) {
var type = items[i].get("type") as Lang.String or Null;
var name = items[i].get("name") as Lang.String or Null;
var content = items[i].get("content") as Lang.String or Null;
var entity = items[i].get("entity") as Lang.String or Null;
var tap_action = items[i].get("tap_action") as Lang.Dictionary or Null;
var service = items[i].get("service") as Lang.String or Null; // Deprecated schema
var confirm = false as Lang.Boolean or Null;
var pin = false as Lang.Boolean or Null;
var data = null as Lang.Dictionary or Null;
var enabled = true as Lang.Boolean or Null;
var exit = false as Lang.Boolean or Null;
var type = items[i].get("type") as Lang.String?;
var name = items[i].get("name") as Lang.String?;
var content = items[i].get("content") as Lang.String?;
var entity = items[i].get("entity") as Lang.String?;
var tap_action = items[i].get("tap_action") as Lang.Dictionary?;
var service = items[i].get("service") as Lang.String?; // Deprecated schema
var confirm = false as Lang.Boolean?;
var pin = false as Lang.Boolean?;
var data = null as Lang.Dictionary?;
var enabled = true as Lang.Boolean?;
var exit = false as Lang.Boolean?;
if (items[i].get("enabled") != null) {
enabled = items[i].get("enabled"); // Optional
}
@@ -207,7 +207,7 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
mTimer = getApp().getQuitTimer();
}
//! Back button event
//! Handle the back button (ESC)
//
function onBack() {
mTimer.reset();

View File

@@ -9,7 +9,7 @@
// 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, SomeoneOnEarth & moesterheld, 23 November 2023
// P A Abbey & J D Abbey, SomeoneOnEarth & moesterheld & vincentezw, 23 November 2023
//
//-----------------------------------------------------------------------------------
@@ -42,7 +42,7 @@ class Settings {
private static var mPollDelay as Lang.Number = 0;
//! seconds
private static var mConfirmTimeout as Lang.Number = 3;
private static var mPin as Lang.String or Null = "0000";
private static var mPin as Lang.String? = "0000";
private static var mMenuAlignment as Lang.Number = WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_LEFT;
private static var mIsSensorsLevelEnabled as Lang.Boolean = false;
//! minutes
@@ -50,7 +50,7 @@ class Settings {
private static var mIsApp as Lang.Boolean = false;
private static var mHasService as Lang.Boolean = false;
//! Must keep the object so it doesn't get garbage collected.
private static var mWebhookManager as WebhookManager or Null;
private static var mWebhookManager as WebhookManager?;
//! Called on application start and then whenever the settings are changed.
//
@@ -62,6 +62,7 @@ class Settings {
mConfigUrl = Properties.getValue("config_url");
mCacheConfig = Properties.getValue("cache_config");
mClearCache = Properties.getValue("clear_cache");
mWifiLteExecution = Properties.getValue("wifi_lte_execution");
mVibrate = Properties.getValue("enable_vibration");
mAppTimeout = Properties.getValue("app_timeout");
mPollDelay = Properties.getValue("poll_delay_combined");
@@ -70,7 +71,6 @@ class Settings {
mMenuAlignment = Properties.getValue("menu_alignment");
mIsSensorsLevelEnabled = Properties.getValue("enable_battery_level");
mBatteryRefreshRate = Properties.getValue("battery_level_refresh_rate");
mWifiLteExecution = Properties.getValue("wifi_lte_execution");
}
//! A webhook is required for non-privileged API calls.
@@ -193,6 +193,18 @@ class Settings {
Properties.setValue("clear_cache", mClearCache);
}
//! Get the value of the Wi-Fi/LTE toggle in settings.
//!
//! @return The state of the toggle.
//
static function getWifiLteExecutionEnabled() as Lang.Boolean {
// Wi-Fi/LTE sync execution on a cached menu
if (!mCacheConfig) {
return false;
}
return mWifiLteExecution;
}
//! Get the vibration Boolean option supplied as part of the Settings.
//!
//! @return Boolean for whether vibration is enabled.
@@ -229,7 +241,7 @@ class Settings {
//!
//! @return The menu item security PIN.
//
static function getPin() as Lang.String or Null {
static function getPin() as Lang.String? {
return mPin;
}
@@ -237,7 +249,7 @@ class Settings {
//!
//! @return The validated 4 digit string.
//
private static function validatePin() as Lang.String or Null {
private static function validatePin() as Lang.String? {
var pin = Properties.getValue("pin");
if (pin.toNumber() == null || pin.length() != 4) {
return null;
@@ -272,16 +284,4 @@ class Settings {
}
}
//! Get the value of the WiFi/LTE toggle in settings.
//!
//! @return The state of the toggle.
//
static function getWifiLteExecutionEnabled() as Lang.Boolean {
// Wifi/LTE sync execution on a cached menu
if (!mCacheConfig) {
return false;
}
return mWifiLteExecution;
}
}

View File

@@ -71,7 +71,7 @@ class WebhookManager {
case 200:
case 201:
var id = data.get("webhook_id") as Lang.String or Null;
var id = data.get("webhook_id") as Lang.String?;
if (id != null) {
Settings.setWebhookId(id);
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Registering first sensor: Battery Level");
@@ -177,7 +177,7 @@ class WebhookManager {
case 201:
if (data instanceof Lang.Dictionary) {
var d = data as Lang.Dictionary;
var b = d.get("success") as Lang.Boolean or Null;
var b = d.get("success") as Lang.Boolean?;
if (b != null and b != false) {
if (sensors.size() == 0) {
getApp().startUpdates();

View File

@@ -1,43 +1,65 @@
//-----------------------------------------------------------------------------------
//
// 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 & vincentezw, 22 July 2025
//
//-----------------------------------------------------------------------------------
using Toybox.WatchUi;
using Toybox.System;
using Toybox.Communications;
using Toybox.Lang;
using Toybox.Timer;
// Delegate to respond to a confirmation to execute command via bulk sync
//! Delegate to respond to a confirmation to execute an API request via bulk
//! synchronisation.
//
class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
public static var mCommandData as {
:type as Lang.String,
:service as Lang.String or Null,
:data as Lang.Dictionary or Null,
:url as Lang.String or Null,
:id as Lang.Number or Null,
:service as Lang.String?,
:data as Lang.Dictionary?,
:url as Lang.String?,
:id as Lang.Number?,
:exit as Lang.Boolean
};
private static var mTimer as Timer.Timer or Null;
private static var mTimer as Timer.Timer?;
private var mHasToast as Lang.Boolean = false;
private var mConfirmationView as WatchUi.Confirmation;
//! Initializes a confirmation delegate to confirm a Wi-Fi or LTE command exection
//! Initializes a confirmation delegate to confirm a Wi-Fi or LTE command execution
//!
//! @param options A dictionary describing the command to be executed:
//! - type: The command type, either `"service"` or `"entity"`.
//! - service: (For type `"service"`) The Home Assistant service to call (e.g., "light.turn_on").
//! - url: (For type `"entity"`) The full Home Assistant entity API URL.
//! - callback: (For type `"entity"`) A callback method (Method<data as Dictionary>) to handle the response.
//! - data: (Optional) A dictionary of data to send with the request.
//! - exit: Boolean: if set to true: exit after running command.
//! @param options A dictionary describing the command to be executed:<br>
//! `{`<br>
//! &emsp; `:type: as Lang.String,` // The command type, either `"service"` or `"entity"`.<br>
//! &emsp; `:service: as Lang.String?,` // (For type `"service"`) The Home Assistant service to call (e.g., "light.turn_on").<br>
//! &emsp; `:url: as Lang.Dictionary?,` // (For type `"entity"`) The full Home Assistant entity API URL.<br>
//! &emsp; `:callback: as Lang.String?,` // (For type `"entity"`) A callback method (Method<data as Dictionary>) to handle the response.<br>
//! &emsp; `:data: as Lang.Method?,` // (Optional) A dictionary of data to send with the request.<br>
//! &emsp; `:exit: as Lang.Boolean,` // Boolean: if set to true: exit after running command.<br>
//! &rbrace;<br>
//! @param view The Confirmation view the delegate is active for
function initialize(cOptions as {
//
function initialize(
cOptions as {
:type as Lang.String,
:service as Lang.String or Null,
:data as Lang.Dictionary or Null,
:url as Lang.String or Null,
:callback as Lang.Method or Null,
:service as Lang.String?,
:data as Lang.Dictionary?,
:url as Lang.String?,
:callback as Lang.Method?,
:exit as Lang.Boolean,
}, view as WatchUi.Confirmation) {
},
view as WatchUi.Confirmation
) {
ConfirmationDelegate.initialize();
if (mTimer != null) {
@@ -63,7 +85,6 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
if (mTimer == null) {
mTimer = new Timer.Timer();
}
mTimer.start(method(:onTimeout), timeout, true);
}
}
@@ -72,6 +93,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
//!
//! @param response The user's confirmation response as `WatchUi.Confirm`
//! @return Always returns `true` to indicate the response was handled.
//
function onResponse(response) as Lang.Boolean {
getApp().getQuitTimer().reset();
if (mTimer != null) {
@@ -85,6 +107,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
}
//! Initiates a bulk sync process to execute a command, if connections are available
//
private function trySync() as Void {
var connectionInfo = System.getDeviceSettings().connectionInfo;
var keys = connectionInfo.keys();
@@ -92,8 +115,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
for(var i = 0; i < keys.size(); i++) {
if (keys[i] != :bluetooth) {
var connection = connectionInfo[keys[i]];
if (connection.state != System.CONNECTION_STATE_NOT_INITIALIZED) {
if (connectionInfo[keys[i]].state != System.CONNECTION_STATE_NOT_INITIALIZED) {
possibleConnection = true;
break;
}
@@ -102,8 +124,9 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
if (possibleConnection) {
if (Communications has :startSync2) {
var syncString = WatchUi.loadResource($.Rez.Strings.WifiLteExecutionTitle) as Lang.String;
Communications.startSync2({:message => syncString});
Communications.startSync2({
:message => WatchUi.loadResource($.Rez.Strings.WifiLteExecutionTitle) as Lang.String
});
} else {
Communications.startSync();
}
@@ -124,6 +147,7 @@ class WifiLteExecutionConfirmDelegate extends WatchUi.ConfirmationDelegate {
}
//! Function supplied to a timer in order to limit the time for which the confirmation can be provided.
//
function onTimeout() as Void {
mTimer.stop();
var getCurrentView = WatchUi.getCurrentView();