Added correctly formatted code comments (#246)

The newer SDK support tooltips to show the function prototype and help
text, so best to make good use of it.

I'm not expecting this to be 100% on the first iteration.
This commit is contained in:
__JosephAbbey
2025-07-05 13:58:35 +01:00
committed by GitHub
22 changed files with 831 additions and 316 deletions

View File

@ -4,5 +4,9 @@
"path": "."
}
],
"settings": {}
"settings": {
"cSpell.words": [
"Initialiser"
]
}
}

View File

@ -11,15 +11,6 @@
//
// J D Abbey & P A Abbey, 28 December 2022
//
//
// Description:
//
// Alert provides a means to present application notifications to the user
// briefly. Credit to travis.vitek on forums.garmin.com.
//
// Reference:
// * https://forums.garmin.com/developer/connect-iq/f/discussion/106/how-to-show-alert-messages
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
@ -27,6 +18,12 @@ using Toybox.Graphics;
using Toybox.WatchUi;
using Toybox.Timer;
//! The Alert class provides a means to present application notifications to the user
//! briefly. Credit to travis.vitek on forums.garmin.com.
//!
//! Reference:
//! @url https://forums.garmin.com/developer/connect-iq/f/discussion/106/how-to-show-alert-messages
//
class Alert extends WatchUi.View {
private static const scRadius = 10;
private var mTimer as Timer.Timer;
@ -36,6 +33,16 @@ class Alert extends WatchUi.View {
private var mFgcolor as Graphics.ColorType;
private var mBgcolor as Graphics.ColorType;
//! 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;
//
function initialize(params as Lang.Dictionary) {
View.initialize();
@ -67,14 +74,22 @@ class Alert extends WatchUi.View {
mTimer = new Timer.Timer();
}
//! Setup a timer to dismiss the alert.
//
function onShow() {
mTimer.start(method(:dismiss), mTimeout, false);
}
//! Prematurely stop the timer.
//
function onHide() {
mTimer.stop();
}
//! Draw the Alert view.
//!
//! @param dc Device context
//
function onUpdate(dc as Graphics.Dc) {
var tWidth = dc.getTextWidthInPixels(mText, mFont);
var tHeight = dc.getFontHeight(mFont);
@ -110,32 +125,49 @@ class Alert extends WatchUi.View {
dc.drawText(tX, tY, mFont, mText, Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
}
// Remove the alert from view, usually on user input, but that is defined by the calling function.
//! Remove the alert from view, usually on user input, but that is defined by the calling function.
//
function dismiss() as Void {
WatchUi.popView(SLIDE_IMMEDIATE);
}
function pushView(transition) as Void {
//! Push this view onto the view stack.
//!
//! @param transition Slide Type
function pushView(transition as WatchUi.SlideType) as Void {
WatchUi.pushView(self, new AlertDelegate(self), transition);
}
}
//! Input Delegate for the Alert view.
//
class AlertDelegate extends WatchUi.InputDelegate {
private var mView;
private var mView as Alert;
function initialize(view) {
//! Class Constructor
//!
//! @param view The Alert view for which this class is a delegate.
//!
function initialize(view as Alert) {
InputDelegate.initialize();
mView = view;
}
function onKey(evt) as Lang.Boolean {
//! Handle key events.
//!
//! @param evt The key event whose value is ignored, just fact of key event matters.
//!
function onKey(evt as WatchUi.KeyEvent) as Lang.Boolean {
mView.dismiss();
getApp().getQuitTimer().reset();
return true;
}
function onTap(evt) as Lang.Boolean {
//! Handle click events.
//!
//! @param evt The click event whose value is ignored, just fact of key event matters.
//!
function onTap(evt as WatchUi.ClickEvent) as Lang.Boolean {
mView.dismiss();
getApp().getQuitTimer().reset();
return true;

View File

@ -11,12 +11,6 @@
//
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
//
//
// Description:
//
// The background service delegate currently just reports the Garmin watch's battery
// level.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
@ -25,20 +19,43 @@ using Toybox.Background;
using Toybox.System;
using Toybox.Activity;
//! The background service delegate reports the Garmin watch's various status values
//! back to the Home Assistant instance.
//
(:background)
class BackgroundServiceDelegate extends System.ServiceDelegate {
//! Class Constructor
//
function initialize() {
ServiceDelegate.initialize();
}
function onReturnBatteryUpdate(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
// System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Code: " + responseCode);
// System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Data: " + data);
//! Callback function for doUpdate().
//!
//! @param responseCode Response code
//! @param data Return data
//
function onReturnDoUpdate(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
// System.println("BackgroundServiceDelegate onReturnDoUpdate() Response Code: " + responseCode);
// System.println("BackgroundServiceDelegate onReturnDoUpdate() Response Data: " + data);
Background.exit(null);
}
function onActivityCompleted(activity as { :sport as Activity.Sport, :subSport as Activity.SubSport }) as Void {
//! 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;
//
function onActivityCompleted(
activity as {
:sport as Activity.Sport,
:subSport as Activity.SubSport
}
) as Void {
if (!System.getDeviceSettings().phoneConnected) {
// System.println("BackgroundServiceDelegate onActivityCompleted(): No Phone connection, skipping API call.");
} else if (!System.getDeviceSettings().connectionAvailable) {
@ -50,6 +67,8 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
}
}
//! Called periodically to send status updates to the Home Assistant instance.
//
function onTemporalEvent() as Void {
if (!System.getDeviceSettings().phoneConnected) {
// System.println("BackgroundServiceDelegate onTemporalEvent(): No Phone connection, skipping API call.");
@ -76,7 +95,15 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
}
}
private function doUpdate(activity as Lang.Number or Null, sub_activity as Lang.Number or Null) {
//! Combined update function to collect the data to be sent as updates to the Home Assistant instance.
//!
//! @param activity Activity.Sport
//! @param sub_activity Activity.SubSport
//
private function doUpdate(
activity as Lang.Number or Null,
sub_activity as Lang.Number or Null
) {
// System.println("BackgroundServiceDelegate onTemporalEvent(): Making API call.");
var position = Position.getInfo();
// System.println("BackgroundServiceDelegate onTemporalEvent(): GPS : " + position.position.toDegrees());
@ -136,7 +163,7 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
},
method(:onReturnBatteryUpdate)
method(:onReturnDoUpdate)
);
}
var activityInfo = ActivityMonitor.getInfo();
@ -222,7 +249,7 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
},
method(:onReturnBatteryUpdate)
method(:onReturnDoUpdate)
);
}

View File

@ -11,15 +11,12 @@
//
// J D Abbey & P A Abbey, 28 December 2022
//
//
// Description:
//
// ClientId is somewhere to store personal credentials that should not be shared in
// a separate file that is locally customised to the source code and not commited
// back to GitHub.
//
//-----------------------------------------------------------------------------------
//! ClientId is somewhere to store personal credentials that should not be shared in
//! a separate file that is locally customised to the source code and not committed
//! back to GitHub.
//
(:glance)
class ClientId {
static const webLogUrl = "https://...";

View File

@ -11,22 +11,6 @@
//
// J D Abbey & P A Abbey, 28 December 2022
//
//
// Description:
//
// ErrorView provides a means to present application errors to the user. These
// should not happen of course... but they do, so best make sure errors can be
// reported.
//
// Designed so that a single ErrorView is used for all errors and hence can ensure
// that only the first call to display is honoured until the view is dismissed.
// This compensates for older devices not being able to call WatchUi.getCurrentView()
// due to not supporting API level 3.4.0.
//
// Usage:
// 1) ErrorView.show("Error message");
// 2) return ErrorView.create("Error message"); // as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>
//
//-----------------------------------------------------------------------------------
using Toybox.Graphics;
@ -35,6 +19,19 @@ using Toybox.WatchUi;
using Toybox.Communications;
using Toybox.Timer;
//! ErrorView provides a means to present application errors to the user. These
//! should not happen of course... but they do, so best make sure errors can be
//! reported.
//!
//! Designed so that a single ErrorView is used for all errors and hence can ensure
//! that only the first call to display is honoured until the view is dismissed.
//! This compensates for older devices not being able to call WatchUi.getCurrentView()
//! due to not supporting API level 3.4.0.
//!
//! Usage:
//! 1) `ErrorView.show("Error message");`
//! 2) `return ErrorView.create("Error message"); // as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>`
//
class ErrorView extends ScalableView {
private static const scErrorIconMargin as Lang.Float = 7f;
private var mText as Lang.String = "";
@ -48,9 +45,11 @@ class ErrorView extends ScalableView {
private static var instance;
private static var mShown as Lang.Boolean = false;
//! Class Constructor
//
function initialize() {
ScalableView.initialize();
mDelegate = new ErrorDelegate(self);
mDelegate = new ErrorDelegate();
// Convert the settings from % of screen size to pixels
mErrorIconMargin = pixelsForScreen(scErrorIconMargin);
mErrorIcon = Application.loadResource(Rez.Drawables.ErrorIcon) as Graphics.BitmapResource;
@ -59,7 +58,10 @@ class ErrorView extends ScalableView {
}
}
// Load your resources here
//! Construct the view.
//!
//! @param dc Device context
//
function onLayout(dc as Graphics.Dc) as Void {
var w = dc.getWidth();
@ -75,7 +77,10 @@ class ErrorView extends ScalableView {
});
}
// Update the view
//! Update the view
//!
//! @param dc Device context
//
function onUpdate(dc as Graphics.Dc) as Void {
var w = dc.getWidth();
if (mAntiAlias) {
@ -87,10 +92,18 @@ class ErrorView extends ScalableView {
mTextArea.draw(dc);
}
//! Get this view's delegate for processing events.
//
function getDelegate() as ErrorDelegate {
return mDelegate;
}
//! 'Create' (get) the ErrorView instance, intended to make short work of using this class. E.g.
//!
//! `return ErrorView.create("Went wrong!");`
//!
//! @param text The string to display in the ErrorView.
//
static function create(text as Lang.String) as [ WatchUi.Views ] or [ WatchUi.Views, WatchUi.InputDelegates ] {
if (instance == null) {
instance = new ErrorView();
@ -102,7 +115,10 @@ class ErrorView extends ScalableView {
return [instance, instance.getDelegate()];
}
// Create or reuse an existing ErrorView, and pass on the text.
//! Create or reuse an existing ErrorView, and pass on the text.
//!
//! @param text The string to display in the ErrorView.
//
static function show(text as Lang.String) as Void {
if (!mShown) {
create(text); // Ignore returned values
@ -113,6 +129,8 @@ class ErrorView extends ScalableView {
}
}
//! Pop the view and clean up timers.
//
static function unShow() as Void {
if (mShown) {
WatchUi.popView(WatchUi.SLIDE_DOWN);
@ -126,7 +144,10 @@ class ErrorView extends ScalableView {
}
}
// Internal show now we're not a static method like 'show()'.
//! Internal show now we're not a static method like 'show()'.
//!
//! @param text Change the string tio display in the ErrorView.
//
function setText(text as Lang.String) as Void {
mText = text;
if (mTextArea != null) {
@ -137,12 +158,19 @@ class ErrorView extends ScalableView {
}
//! Delegate for the ErrorView.
//
class ErrorDelegate extends WatchUi.BehaviorDelegate {
function initialize(view as ErrorView) {
//! Class Constructor
//!
function initialize() {
WatchUi.BehaviorDelegate.initialize();
}
//! Process the event to clear the ErrorView.
//
function onBack() as Lang.Boolean {
getApp().getQuitTimer().reset();
ErrorView.unShow();

View File

@ -11,30 +11,38 @@
//
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
//
//
// Description:
//
// Home Assistant centralised constants.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
//! Home Assistant centralised constants.
//
(:glance)
class Globals {
//! Alert is a toast at the top of the watch screen, it stays present until tapped
//! or this timeout has expired.
static const scAlertTimeout = 2000; // ms
static const scTapTimeout = 1000; // ms
// Time to let the existing HTTP responses get serviced after a
// Communications.NETWORK_RESPONSE_OUT_OF_MEMORY response code.
//! Time to let the existing HTTP responses get serviced after a
//! `Communications.NETWORK_RESPONSE_OUT_OF_MEMORY` response code.
static const scApiBackoff = 1000; // ms
// Needs to be long enough to enable a "double ESC" to quit the application from
// an ErrorView.
//! Needs to be long enough to enable a "double ESC" to quit the application from
//! an ErrorView.
static const scApiResume = 200; // ms
// Warn the user after fetching the menu if their watch is low on memory before the device crashes.
//! Warn the user after fetching the menu if their watch is low on memory before the device crashes.
static const scLowMem = 0.90; // percent as a fraction.
// Constants for PIN confirmation dialog
static const scPinMaxFailures = 5; // Maximum number of failed PIN confirmation attemps allwed in ...
static const scPinMaxFailureMinutes = 2; // ... this number of minutes before PIN confirmation is locked for ...
static const scPinLockTimeMinutes = 10; // ... this number of minutes
//! Constant for PIN confirmation dialog.<br>
//! Maximum number of failed PIN confirmation attempts allowed in `scPinMaxFailureMinutes`.
static const scPinMaxFailures = 5;
//! Constant for PIN confirmation dialog.<br>
//! Period in minutes during which no more than `scPinMaxFailures` PIN attempts are tolerated.
static const scPinMaxFailureMinutes = 2;
//! Constant for PIN confirmation dialog.<br>
//! Lock out time in minutes after a failed PIN entry.
static const scPinLockTimeMinutes = 10;
}

View File

@ -11,11 +11,6 @@
//
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
//
//
// Description:
//
// Application root for GarminHomeAssistant
//
//-----------------------------------------------------------------------------------
using Toybox.Application;
@ -25,6 +20,8 @@ using Toybox.System;
using Toybox.Application.Properties;
using Toybox.Timer;
//! Application root for GarminHomeAssistant
//
(:glance, :background)
class HomeAssistantApp extends Application.AppBase {
private var mApiStatus as Lang.String or Null;
@ -40,6 +37,8 @@ class HomeAssistantApp extends Application.AppBase {
private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates
private var mTemplates as Lang.Dictionary = {};
//! Class Constructor
//
function initialize() {
AppBase.initialize();
// ATTENTION when adding stuff into this block:
@ -55,7 +54,10 @@ class HomeAssistantApp extends Application.AppBase {
// with "(:glance)".
}
// onStart() is called on application start up
//! Called on application start up
//!
//! @param state see `AppBase.onStart()`
//
function onStart(state as Lang.Dictionary?) as Void {
AppBase.onStart(state);
// ATTENTION when adding stuff into this block:
@ -71,7 +73,11 @@ class HomeAssistantApp extends Application.AppBase {
// with "(:glance)".
}
// onStop() is called when your application is exiting
//! Called when your application is exiting
//
//!
//! @param state see `AppBase.onStop()`
//
function onStop(state as Lang.Dictionary?) as Void {
AppBase.onStop(state);
// ATTENTION when adding stuff into this block:
@ -87,7 +93,10 @@ class HomeAssistantApp extends Application.AppBase {
// with "(:glance)".
}
// Return the initial view of your application here
//! Returns the initial view of the application.
//!
//! @return The initial view.
//
function getInitialView() as [ WatchUi.Views ] or [ WatchUi.Views, WatchUi.InputDelegates ] {
mIsApp = true;
mQuitTimer = new QuitTimer();
@ -132,10 +141,16 @@ class HomeAssistantApp extends Application.AppBase {
}
}
// Callback function after completing the GET request to fetch the configuration menu.
//! Callback function after completing the GET request to fetch the configuration menu.
//!
//! @param responseCode Response code.
//! @param data Response data.
//
(:glance)
function onReturnFetchMenuConfig(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
function onReturnFetchMenuConfig(
responseCode as Lang.Number,
data as Null or Lang.Dictionary or Lang.String
) as Void {
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: " + responseCode);
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Data: " + data);
@ -208,8 +223,11 @@ 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.
//! Fetch the menu configuration over HTTPS, which might be locally cached.
//!
//! @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.
//
(:glance)
function fetchMenuConfig() as Lang.Boolean {
// System.println("Menu URL = " + Settings.getConfigUrl());
@ -263,6 +281,10 @@ class HomeAssistantApp extends Application.AppBase {
return false;
}
//! Build the menu and store in `mHaMenu`. Then start updates if necessary.
//!
//! @param menu The dictionary derived from the JSON menu fetched by `fetchMenuConfig()`.
//
private function buildMenu(menu as Lang.Dictionary) {
mHaMenu = new HomeAssistantView(menu, null);
mQuitTimer.begin();
@ -271,6 +293,8 @@ class HomeAssistantApp extends Application.AppBase {
} // If not, this will be done via a chain in Settings.webhook() and mWebhookManager.requestWebhookId() that registers the sensors.
}
//! Start the periodic menu updates for as long as the application is running.
//
function startUpdates() {
if (mHaMenu != null and !mUpdating) {
// Start the continuous update process that continues for as long as the application is running.
@ -279,7 +303,15 @@ class HomeAssistantApp extends Application.AppBase {
}
}
function onReturnUpdateMenuItems(responseCode as Lang.Number, data as Null or Lang.Dictionary) as Void {
//! Callback function for each menu update GET request.
//!
//! @param responseCode Response code.
//! @param data Response data.
//
function onReturnUpdateMenuItems(
responseCode as Lang.Number,
data as Null or Lang.Dictionary
) as Void {
// System.println("HomeAssistantApp onReturnUpdateMenuItems() Response Code: " + responseCode);
// System.println("HomeAssistantApp onReturnUpdateMenuItems() Response Data: " + data);
@ -353,6 +385,8 @@ class HomeAssistantApp extends Application.AppBase {
setApiStatus(status);
}
//! Construct the GET request to update all menu items.
//
function updateMenuItems() as Void {
if (! System.getDeviceSettings().phoneConnected) {
// System.println("HomeAssistantApp updateMenuItems(): No Phone connection, skipping API call.");
@ -368,17 +402,17 @@ class HomeAssistantApp extends Application.AppBase {
mTemplates = {};
for (var i = 0; i < mItemsToUpdate.size(); i++) {
var item = mItemsToUpdate[i];
var template = item.buildTemplate();
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).buildToggleTemplate()
});
}
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
@ -402,10 +436,16 @@ class HomeAssistantApp extends Application.AppBase {
}
}
// Callback function after completing the GET request to fetch the API status.
//! Callback function after completing the GET request to fetch the API status.
//!
//! @param responseCode Response code.
//! @param data Response data.
//
(:glance)
function onReturnFetchApiStatus(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
function onReturnFetchApiStatus(
responseCode as Lang.Number,
data as Null or Lang.Dictionary or Lang.String
) as Void {
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: " + responseCode);
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Data: " + data);
@ -466,6 +506,8 @@ class HomeAssistantApp extends Application.AppBase {
WatchUi.requestUpdate();
}
//! Construct the GET request to test the API status, is it accessible?
//
(:glance)
function fetchApiStatus() as Void {
// System.println("API URL = " + Settings.getApiUrl());
@ -506,30 +548,49 @@ class HomeAssistantApp extends Application.AppBase {
}
}
//! Record the API status result.
//!
//! @param s A string describing the API status
//
function setApiStatus(s as Lang.String) {
mApiStatus = s;
}
//! Return the API status result.
//!
//! @return A string describing the API status
//
(:glance)
function getApiStatus() as Lang.String {
return mApiStatus;
}
//! Return the Menu status result.
//!
//! @return A string describing the Menu status
//
(:glance)
function getMenuStatus() as Lang.String {
return mMenuStatus;
}
//! Return the Menu construction status.
//!
//! @return A Boolean indicating if the menu is loaded into the application.
//
function isHomeAssistantMenuLoaded() as Lang.Boolean {
return mHaMenu != null;
}
//! Make the menu visible on the watch face.
//
function pushHomeAssistantMenuView() as Void {
WatchUi.pushView(mHaMenu, new HomeAssistantViewDelegate(true), WatchUi.SLIDE_IMMEDIATE);
}
// Only call this function if Settings.getPollDelay() > 0. This must be tested locally as it is then efficient to take
// alternative action if the test fails.
//! Force status updates. Only take action if `Settings.getPollDelay() > 0`. This must be tested
//! locally as it is then efficient to take alternative action if the test fails.
//
function forceStatusUpdates() as Void {
// Don't mess with updates unless we are using a timer.
if (Settings.getPollDelay() > 0) {
@ -539,10 +600,18 @@ class HomeAssistantApp extends Application.AppBase {
}
}
//! Return the timer used to quit the application.
//!
//! @return Timer object
//
function getQuitTimer() as QuitTimer {
return mQuitTimer;
}
//! Return the glance view.
//!
//! @return The glance view
//
function getGlanceView() as [ WatchUi.GlanceView ] or [ WatchUi.GlanceView, WatchUi.GlanceViewDelegate ] or Null {
mIsGlance = true;
mApiStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
@ -554,30 +623,38 @@ class HomeAssistantApp extends Application.AppBase {
return [new HomeAssistantGlanceView(self)];
}
// Required for the Glance update timer.
//! Update the menu and API statuses. Required for the Glance update timer.
//
function updateStatus() as Void {
mGlanceTimer = null;
fetchMenuConfig();
fetchApiStatus();
}
//! Code for when the application settings are updated.
//
function onSettingsChanged() as Void {
// System.println("HomeAssistantApp onSettingsChanged()");
Settings.update();
}
// Called each time the Registered Temporal Event is to be invoked. So the object is created each time on request and
// then destroyed on completion (to save resources).
//! Called each time the Registered Temporal Event is to be invoked. So the object is created each time
//! on request and then destroyed on completion (to save resources).
//
function getServiceDelegate() as [ System.ServiceDelegate ] {
return [new BackgroundServiceDelegate()];
}
//! Determine is we are a glance or the full application. Glances should be considered to be separate applications.
//
function getIsApp() as Lang.Boolean {
return mIsApp;
}
}
//! Global function to return the application object.
//
(:glance, :background)
function getApp() as HomeAssistantApp {
return Application.getApp() as HomeAssistantApp;

View File

@ -11,11 +11,6 @@
//
// P A Abbey & J D Abbey & Someone0nEarth, 19 November 2023
//
//
// Description:
//
// Calling a Home Assistant confirmation dialogue view.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
@ -25,19 +20,27 @@ using Toybox.WatchUi;
using Toybox.Timer;
using Toybox.Application.Properties;
//! Calling a Home Assistant confirmation dialogue view.
//
class HomeAssistantConfirmation extends WatchUi.Confirmation {
//! Class Constructor
//
function initialize() {
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
}
}
//! Delegate to respond to the confirmation request.
//
class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
private var mConfirmMethod as Method(state as Lang.Boolean) as Void;
private var mTimer as Timer.Timer or Null;
private var mState as Lang.Boolean;
//! Class Constructor
//
function initialize(callback as Method(state as Lang.Boolean) as Void, state as Lang.Boolean) {
WatchUi.ConfirmationDelegate.initialize();
mConfirmMethod = callback;
@ -49,7 +52,12 @@ class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
}
}
function onResponse(response) as Lang.Boolean {
//! Respond to the confirmation event.
//!
//! @param 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 {
getApp().getQuitTimer().reset();
if (mTimer != null) {
mTimer.stop();
@ -60,6 +68,7 @@ class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
return true;
}
//! Function supplied to a timer in order to limit the time for which the confirmation can be provided.
function onTimeout() as Void {
mTimer.stop();
WatchUi.popView(WatchUi.SLIDE_RIGHT);

View File

@ -11,17 +11,14 @@
//
// P A Abbey & J D Abbey & Someone0nEarth, 23 November 2023
//
//
// Description:
//
// Glance view for GarminHomeAssistant
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Graphics;
//! Glance view for GarminHomeAssistant
//
(:glance)
class HomeAssistantGlanceView extends WatchUi.GlanceView {
private static const scLeftMargin = 5; // in pixels
@ -34,6 +31,8 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
private var mMenuStatus as WatchUi.Text or Null;
private var mAntiAlias as Lang.Boolean = false;
//! Class Constructor
//
function initialize(app as HomeAssistantApp) {
GlanceView.initialize();
mApp = app;
@ -42,6 +41,10 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
}
}
//! Construct the view.
//!
//! @param dc Device context
//
function onLayout(dc as Graphics.Dc) as Void {
var h = dc.getHeight();
var tw = dc.getTextWidthInPixels(WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String, Graphics.FONT_XTINY);
@ -89,6 +92,10 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
});
}
//! Update the view with the latest status text.
//!
//! @param dc Device context
//
function onUpdate(dc as Graphics.Dc) as Void {
GlanceView.onUpdate(dc);
if(mAntiAlias) {

View File

@ -11,20 +11,19 @@
//
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
//
//
// Description:
//
// Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders
// a Home Assistant Template.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
//! Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders
//! a Home Assistant Template.
//
class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
private var mMenu as HomeAssistantView;
//! Class Constructor
//
function initialize(
definition as Lang.Dictionary,
template as Lang.String,
@ -48,6 +47,8 @@ class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
mMenu = new HomeAssistantView(definition, null);
}
//! Return the submenu for this group menu item.
//
function getMenuView() as HomeAssistantView {
return mMenu;
}

View File

@ -11,20 +11,23 @@
//
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
//
//
// Description:
//
// Generic menu button with an icon that optionally renders a Home Assistant Template.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
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;
//! Class Constructor
//!
//! @param label Menu item label
//! @param template Menu item template
//! @param options Menu item options to be passed on.
//
function initialize(
label as Lang.String or Lang.Symbol,
template as Lang.String,
@ -43,14 +46,27 @@ class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
mTemplate = template;
}
//! Does this menu item use a template?
//!
//! @return True if the menu has a defined template else false.
//
function hasTemplate() as Lang.Boolean {
return mTemplate != null;
}
function buildTemplate() as Lang.String or Null {
//! Return the menu item's template.
//!
//! @return A string with the menu item's template definition.
//
function getTemplate() as Lang.String or Null {
return mTemplate;
}
//! Update the menu item's sub label to display the template rendered by Home Assistant.
//!
//! @param data The rendered template (typically a string) to be placed in the sub label. This may
//! unusually be a number if the SDK interprets the JSON returned by Home Assistant as such.
//
function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
if (data == null) {
setSubLabel($.Rez.Strings.Empty);
@ -76,4 +92,4 @@ class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
WatchUi.requestUpdate();
}
}
}

View File

@ -11,17 +11,14 @@
//
// P A Abbey & J D Abbey & Someone0nEarth, 17 November 2023
//
//
// Description:
//
// MenuItems Factory.
//
//-----------------------------------------------------------------------------------
using Toybox.Application;
using Toybox.Lang;
using Toybox.WatchUi;
//! MenuItems Factory class.
//
class HomeAssistantMenuItemFactory {
private var mMenuItemOptions as Lang.Dictionary;
private var mTapTypeIcon as WatchUi.Bitmap;
@ -31,6 +28,8 @@ class HomeAssistantMenuItemFactory {
private static var instance;
//! Class Constructor
//
private function initialize() {
mMenuItemOptions = {
:alignment => Settings.getMenuAlignment()
@ -57,6 +56,8 @@ class HomeAssistantMenuItemFactory {
mHomeAssistantService = new HomeAssistantService();
}
//! Create the one and only instance of this class.
//
static function create() as HomeAssistantMenuItemFactory {
if (instance == null) {
instance = new HomeAssistantMenuItemFactory();
@ -64,6 +65,14 @@ class HomeAssistantMenuItemFactory {
return instance;
}
//! Toggle menu item.
//!
//! @param label Menu item label.
//! @param entity_id Home Assistant Entity ID (optional)
//! @param template Template for Home Assistant to render (optional)
//! @param confirm Should this menu item selection be confirmed?
//! @param pin Should this menu item selection request the security PIN?
//
function toggle(
label as Lang.String or Lang.Symbol,
entity_id as Lang.String or Null,
@ -81,20 +90,30 @@ class HomeAssistantMenuItemFactory {
);
}
//! Tap menu item.
//!
//! @param label Menu item label.
//! @param entity_id Home Assistant Entity ID (optional)
//! @param template Template for Home Assistant to render (optional)
//! @param service Template for Home Assistant to render (optional)
//! @param confirm Should this menu item selection be confirmed?
//! @param pin Should this menu item selection request the security PIN?
//! @param data Sourced from the menu JSON, this is the `data` field from the `tap_action` field.
//
function tap(
label as Lang.String or Lang.Symbol,
entity as Lang.String or Null,
template as Lang.String or Null,
service as Lang.String or Null,
confirm as Lang.Boolean,
pin as Lang.Boolean,
data as Lang.Dictionary or Null
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,
confirm as Lang.Boolean,
pin as Lang.Boolean,
data as Lang.Dictionary or Null
) as WatchUi.MenuItem {
if (entity != null) {
if (entity_id != null) {
if (data == null) {
data = { "entity_id" => entity };
data = { "entity_id" => entity_id };
} else {
data.put("entity_id", entity);
data.put("entity_id", entity_id);
}
}
if (service != null) {
@ -124,6 +143,11 @@ class HomeAssistantMenuItemFactory {
}
}
//! Group menu item.
//!
//! @param definition Items array from the JSON that defines this sub menu.
//! @param template Template for Home Assistant to render (optional)
//
function group(
definition as Lang.Dictionary,
template as Lang.String or Null

View File

@ -11,25 +11,28 @@
//
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
//
//
// Description:
//
// Pin Confirmation dialog and logic.
//
//-----------------------------------------------------------------------------------
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.WatchUi;
import Toybox.Timer;
import Toybox.Attention;
import Toybox.Time;
using Toybox.Graphics;
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Timer;
using Toybox.Attention;
using Toybox.Time;
//! Pin digit used for number 0..9
//
class PinDigit extends WatchUi.Selectable {
private var mDigit as Number;
private var mDigit as Lang.Number;
function initialize(digit as Number, stepX as Number, stepY as Number) {
//! Class Constructor
//!
//! @param digit The digit this instance of the class represents and to display.
//! @param stepX Horizontal spacing.
//! @param stepY Vertical spacing.
//
function initialize(digit as Lang.Number, stepX as Lang.Number, stepY as Lang.Number) {
var marginX = stepX * 0.05; // 5% margin on all sides
var marginY = stepY * 0.05;
var x = (digit == 0) ? stepX : stepX * ((digit+2) % 3); // layout '0' in 2nd col, others ltr in 3 columns
@ -63,24 +66,40 @@ class PinDigit extends WatchUi.Selectable {
});
mDigit = digit;
}
function getDigit() as Number {
//! Return the digit 0..9 represented by this button
//
function getDigit() as Lang.Number {
return mDigit;
}
//! Customised drawing of a PIN digit's button.
//
class PinDigitButton extends WatchUi.Drawable {
private var mText as Number;
private var mTouched as Boolean = false;
private var mText as Lang.Number;
private var mTouched as Lang.Boolean = false;
//! 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>
//! &emsp; + those required by `Drawable.initialize()`<br>
//! &rbrace;
//
function initialize(options) {
Drawable.initialize(options);
mText = options.get(:label);
mTouched = options.get(:touched);
}
function draw(dc) {
//! Draw the PIN digit button.
//!
//! @param dc Device context
//
function draw(dc as Graphics.Dc) {
if (mTouched) {
dc.setColor(Graphics.COLOR_ORANGE, Graphics.COLOR_ORANGE);
} else {
@ -98,17 +117,27 @@ class PinDigit extends WatchUi.Selectable {
}
//! Pin Confirmation dialog and logic.
//
class HomeAssistantPinConfirmationView extends WatchUi.View {
static const MARGIN_X = 20; // margin on left & right side of screen (overall prettier and works better on round displays)
//! Margin on left & right side of screen (overall prettier and works better on round displays)
static const MARGIN_X = 20;
//! Indicates how many digits have been entered so far.
var mPinMask as Lang.String = "";
var mPinMask as String = "";
//! Class Constructor
//
function initialize() {
View.initialize();
}
function onLayout(dc as Dc) as Void {
//! Construct the view.
//!
//! @param dc Device context
//
function onLayout(dc as Graphics.Dc) as Void {
var stepX = (dc.getWidth() - MARGIN_X * 2) / 3; // three columns
var stepY = dc.getHeight() / 5; // five rows (first row for masked pin entry)
var digits = [];
@ -119,7 +148,11 @@ class HomeAssistantPinConfirmationView extends WatchUi.View {
setLayout(digits);
}
function onUpdate(dc as Dc) as Void {
//! Update the view.
//!
//! @param dc Device context
//
function onUpdate(dc as Graphics.Dc) as Void {
View.onUpdate(dc);
if (mPinMask.length() != 0) {
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
@ -127,7 +160,11 @@ class HomeAssistantPinConfirmationView extends WatchUi.View {
}
}
function updatePinMask(length as Number) {
//! Update the PIN mask displayed.
//!
//! @param length Number of `*` characters to use for the mask string.
//
function updatePinMask(length as Lang.Number) {
mPinMask = "";
for (var i=0; i<length; i++) {
mPinMask += "*";
@ -138,17 +175,31 @@ class HomeAssistantPinConfirmationView extends WatchUi.View {
}
//! Delegate for the HomeAssistantPinConfirmationView.
//
class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
private var mPin as String;
private var mEnteredPin as String;
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 mState as Lang.Boolean;
private var mFailures as PinFailures;
private var mView as HomeAssistantPinConfirmationView;
function initialize(callback as Method(state as Lang.Boolean) as Void, state as Lang.Boolean, pin as String, view as HomeAssistantPinConfirmationView) {
//! Class Constructor
//!
//! @param callback Method to call on confirmation.
//! @param state Current state of a toggle button.
//! @param pin PIN to be matched.
//! @param view PIN confirmation view.
//
function initialize(
callback as Method(state as Lang.Boolean) as Void,
state as Lang.Boolean,
pin as Lang.String,
view as HomeAssistantPinConfirmationView
) {
BehaviorDelegate.initialize();
mFailures = new PinFailures();
if (mFailures.isLocked()) {
@ -165,7 +216,12 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
resetTimer();
}
function onSelectable(event as SelectableEvent) as Boolean {
//! Add another entered digit to the "PIN so far". When it is long enough verify the PIN is correct and the
//! invoke the supplied call back function.
//!
//! @param event The digit pressed by the user tapping the screen.
//
function onSelectable(event as WatchUi.SelectableEvent) as Lang.Boolean {
if (mFailures.isLocked()) {
goBack();
}
@ -173,7 +229,7 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
if (instance instanceof PinDigit && event.getPreviousState() == :stateSelected) {
mEnteredPin += instance.getDigit();
createUserFeedback();
// System.println("HomeAssitantPinConfirmationDelegate onSelectable() mEnteredPin = " + mEnteredPin);
// System.println("HomeAssistantPinConfirmationDelegate onSelectable() mEnteredPin = " + mEnteredPin);
if (mEnteredPin.length() == mPin.length()) {
if (mEnteredPin.equals(mPin)) {
mFailures.reset();
@ -193,6 +249,8 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
return true;
}
//! Hepatic feedback.
//
function createUserFeedback() {
if (Attention has :vibrate && Settings.getVibrate()) {
Attention.vibrate([new Attention.VibeProfile(25, 25)]);
@ -200,6 +258,9 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
mView.updatePinMask(mEnteredPin.length());
}
//! A timer is used to clear the PIN entry view if digits are not pressed. So each time a digit is pressed the
//! timer is reset.
//
function resetTimer() {
var timeout = Settings.getConfirmTimeout(); // ms
if (timeout > 0) {
@ -212,6 +273,8 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
}
}
//! Cancel PIN entry.
//
function goBack() as Void {
if (mTimer != null) {
mTimer.stop();
@ -219,18 +282,20 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
WatchUi.popView(WatchUi.SLIDE_RIGHT);
}
//! Hepatic feedback for a wrong PIN and cancel entry.
//
function error() as Void {
// System.println("HomeAssistantPinConfirmationDelegate error() Wrong PIN entered");
mFailures.addFailure();
if (Attention has :vibrate && Settings.getVibrate()) {
Attention.vibrate([
new Attention.VibeProfile(100, 100),
new Attention.VibeProfile(0, 200),
new Attention.VibeProfile(75, 100),
new Attention.VibeProfile(0, 200),
new Attention.VibeProfile(50, 100),
new Attention.VibeProfile(0, 200),
new Attention.VibeProfile(25, 100)
new Attention.VibeProfile( 0, 200),
new Attention.VibeProfile( 75, 100),
new Attention.VibeProfile( 0, 200),
new Attention.VibeProfile( 50, 100),
new Attention.VibeProfile( 0, 200),
new Attention.VibeProfile( 25, 100)
]);
}
if (WatchUi has :showToast) {
@ -241,14 +306,19 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
}
//! Manage PIN entry failures to try and prevent brute force exhaustion by inserting delays in retries.
//
class PinFailures {
const STORAGE_KEY_FAILURES as String = "pin_failures";
const STORAGE_KEY_LOCKED as String = "pin_locked";
const STORAGE_KEY_FAILURES as Lang.String = "pin_failures";
const STORAGE_KEY_LOCKED as Lang.String = "pin_locked";
private var mFailures as Array<Number>;
private var mLockedUntil as Number or Null;
private var mFailures as Lang.Array<Lang.Number>;
private var mLockedUntil as Lang.Number or Null;
//! Class Constructor
//
function initialize() {
// System.println("PinFailures initialize() Initializing PIN failures from storage");
var failures = Application.Storage.getValue(PinFailures.STORAGE_KEY_FAILURES);
@ -256,6 +326,8 @@ class PinFailures {
mLockedUntil = Application.Storage.getValue(PinFailures.STORAGE_KEY_LOCKED);
}
//! Record a PIN entry failure. If too many have occurred lock the application.
//
function addFailure() {
mFailures.add(Time.now().value());
// System.println("PinFailures addFailure() " + mFailures.size() + " PIN confirmation failures recorded");
@ -268,7 +340,7 @@ class PinFailures {
mFailures = mFailures.slice(1, null);
} else {
mFailures = [];
mLockedUntil = Time.now().add(new Time.Duration(Globals.scPinLockTimeMinutes * Gregorian.SECONDS_PER_MINUTE)).value();
mLockedUntil = Time.now().add(new Time.Duration(Globals.scPinLockTimeMinutes * Time.Gregorian.SECONDS_PER_MINUTE)).value();
Application.Storage.setValue(STORAGE_KEY_LOCKED, mLockedUntil);
// System.println("PinFailures addFailure() Locked until " + mLockedUntil);
}
@ -276,6 +348,9 @@ class PinFailures {
Application.Storage.setValue(STORAGE_KEY_FAILURES, mFailures);
}
//! Clear the record of previous PIN entry failures, e.g. because the correct PIN has now been entered
//! within tolerance.
//
function reset() {
// System.println("PinFailures reset() Resetting failures");
mFailures = [];
@ -284,11 +359,18 @@ class PinFailures {
Application.Storage.deleteValue(STORAGE_KEY_LOCKED);
}
function getLockedUntilSeconds() as Number {
//! Retrieve the remaining time the application must be locked out for.
//
function getLockedUntilSeconds() as Lang.Number {
return new Time.Moment(mLockedUntil).subtract(Time.now()).value();
}
function isLocked() as Boolean {
//! Is the application currently locked out? If the application is no longer locked out, then clear the
//! stored values used to determine this state.
//!
//! @return Boolean indicating if the application is currently locked out.
//
function isLocked() as Lang.Boolean {
if (mLockedUntil == null) {
return false;
}

View File

@ -11,11 +11,6 @@
//
// P A Abbey & J D Abbey & Someone0nEarth, 19 November 2023
//
//
// Description:
//
// Calling a Home Assistant Service.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
@ -23,10 +18,14 @@ using Toybox.WatchUi;
using Toybox.Graphics;
using Toybox.Application.Properties;
//! Calling a Home Assistant Service.
//
class HomeAssistantService {
private var mHasToast as Lang.Boolean = false;
private var mHasVibrate as Lang.Boolean = false;
//! Class Constructor
//
function initialize() {
if (WatchUi has :showToast) {
mHasToast = true;
@ -36,7 +35,11 @@ class HomeAssistantService {
}
}
// Callback function after completing the POST request to call a service.
//! Callback function after completing the POST request to call a service.
//!
//! @param responseCode Response code.
//! @param data Response data.
//! @param context An `entity_id` supplied in the GET request `options` `Lang.Dictionary` `context` field.
//
function onReturnCall(
responseCode as Lang.Number,
@ -107,6 +110,11 @@ class HomeAssistantService {
}
}
//! Invoke a service call for a menu item.
//!
//! @param service The Home Assistant service to be run, e.g. from the JSON `service` field.
//! @param data Data to be supplied to the service call.
//
function call(
service as Lang.String,
data as Lang.Dictionary or Null

View File

@ -11,17 +11,14 @@
//
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
//
//
// Description:
//
// Menu button that triggers a service.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Graphics;
//! Menu button that triggers a service.
//
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
private var mHomeAssistantService as HomeAssistantService;
private var mService as Lang.String or Null;
@ -29,6 +26,19 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
private var mPin as Lang.Boolean;
private var mData as Lang.Dictionary or Null;
//! Class Constructor
//!
//! @param label Menu item label.
//! @param template Menu item template.
//! @param service Menu item service.
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
//! @param pin Should the service call be protected with a PIN for some low level of security?
//! @param data Data to supply to the service call.
//! @param icon Icon to use for the menu item.
//! @param options Menu item options to be passed on.
//! @param haService Shared Home Assistant service object that will perform the required call. Only
//! one of these objects is created for all menu items to re-use.
//
function initialize(
label as Lang.String or Lang.Symbol,
template as Lang.String,
@ -62,6 +72,8 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
mData = data;
}
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
//
function callService() as Void {
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
if (mPin && hasTouchScreen) {
@ -85,7 +97,10 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
}
}
// NB. Parameter 'b' is ignored
//! Callback function after the menu items selection has been (optionally) confirmed.
//!
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
//
function onConfirm(b as Lang.Boolean) as Void {
if (mService != null) {
mHomeAssistantService.call(mService, mData);

View File

@ -11,11 +11,6 @@
//
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
//
//
// Description:
//
// Light or switch toggle button that calls the API to maintain the up to date state.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
@ -24,6 +19,8 @@ using Toybox.Graphics;
using Toybox.Application.Properties;
using Toybox.Timer;
//! Light or switch toggle menu button that calls the API to maintain the up to date state.
//
class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
private var mConfirm as Lang.Boolean;
private var mPin as Lang.Boolean;
@ -31,6 +28,15 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
private var mTemplate as Lang.String;
private var mHasVibrate as Lang.Boolean = false;
//! Class Constructor
//!
//! @param label Menu item label.
//! @param template Menu item template.
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
//! @param pin Should the service call be protected with a PIN for some low level of security?
//! @param data Data to supply to the service call.
//! @param options Menu item options to be passed on.
//
function initialize(
label as Lang.String or Lang.Symbol,
template as Lang.String,
@ -58,6 +64,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
mTemplate = template;
}
//! Set the state of a toggle menu item.
//
private function setUiToggle(state as Null or Lang.String) as Void {
if (state != null) {
if (state.equals("on") && !isEnabled()) {
@ -68,13 +76,26 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
}
}
function buildTemplate() as Lang.String or Null {
//! Return the menu item's template.
//!
//! @return A string with the menu item's template definition.
//
function getTemplate() as Lang.String or Null {
return mTemplate;
}
function buildToggleTemplate() as Lang.String or Null {
//! Return a toggle menu item's state template.
//!
//! @return A string with the menu item's template definition.
//
function getToggleTemplate() as Lang.String or Null {
return "{{states('" + mData.get("entity_id") + "')}}";
}
//! Update the menu item's label from a recent GET request.
//!
//! @param data This should be a string, but the way the GET response is parsed, it can also be a number.
//
function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
if (data == null) {
setSubLabel(null);
@ -100,6 +121,10 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
WatchUi.requestUpdate();
}
//! Update the menu item's toggle state from a recent GET request.
//!
//! @param data This should be a string of either "on" or "off".
//
function updateToggleState(data as Lang.String or Lang.Dictionary or Null) as Void {
if (data == null) {
setUiToggle("off");
@ -126,9 +151,15 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
WatchUi.requestUpdate();
}
// Callback function after completing the POST request to set the status.
//! Callback function after completing the POST request to set the status.
//!
//! @param responseCode Response code.
//! @param data Response data.
//
function onReturnSetState(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
function onReturnSetState(
responseCode as Lang.Number,
data as Null or Lang.Dictionary or Lang.String
) as Void {
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: " + responseCode);
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Data: " + data);
@ -183,6 +214,10 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
getApp().setApiStatus(status);
}
//! Set the state of the toggle menu item.
//!
//! @param s Boolean indicating the desired state of the toggle switch.
//
function setState(s as Lang.Boolean) as Void {
// Toggle the UI back, we'll wait for confirmation from the Home Assistant
setEnabled(!isEnabled());
@ -228,6 +263,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
}
}
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
//
function callService(b as Lang.Boolean) as Void {
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
if (mPin && hasTouchScreen) {
@ -251,6 +288,10 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
}
}
//! Callback function to toggle state of this item after (optional) confirmation.
//!
//! @param b Desired toggle button state.
//
function onConfirm(b as Lang.Boolean) as Void {
setState(b);
}

View File

@ -11,11 +11,6 @@
//
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
//
//
// Description:
//
// Home Assistant menu construction.
//
//-----------------------------------------------------------------------------------
using Toybox.Application;
@ -24,8 +19,12 @@ using Toybox.Graphics;
using Toybox.System;
using Toybox.WatchUi;
//! Home Assistant menu construction.
//
class HomeAssistantView extends WatchUi.Menu2 {
//! Class Constructor
//
function initialize(
definition as Lang.Dictionary,
options as {
@ -75,7 +74,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
}
}
// Lang.Array.addAll() fails structural type checking without including "Null" in the return type
//! Return a list of items that need to be updated within this menu structure.
//!
//! MN. Lang.Array.addAll() fails structural type checking without including "Null" in the return type
//!
//! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state.
//
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or Null> {
var fullList = [];
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
@ -102,26 +106,35 @@ class HomeAssistantView extends WatchUi.Menu2 {
return fullList;
}
// Called when this View is brought to the foreground. Restore
// the state of this View and prepare it to be shown. This includes
// loading resources into memory.
//! Called when this View is brought to the foreground. Restore
//! the state of this View and prepare it to be shown. This includes
//! loading resources into memory.
function onShow() as Void {}
}
//
// Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/
//! Delegate for the HomeAssistantView.
//!
//! Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/
//
class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
private var mIsRootMenuView as Lang.Boolean = false;
private var mTimer as QuitTimer;
//! Class Constructor
//!
//! @param isRootMenuView As menus can be nested, this state marks the top level menu so that the
//! back event can exit the application completely rather than just popping
//! a menu view.
//
function initialize(isRootMenuView as Lang.Boolean) {
Menu2InputDelegate.initialize();
mIsRootMenuView = isRootMenuView;
mTimer = getApp().getQuitTimer();
}
//! Back button event
//
function onBack() {
mTimer.reset();
@ -135,16 +148,22 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
WatchUi.popView(WatchUi.SLIDE_RIGHT);
}
// Only for CheckboxMenu
//! Only for CheckboxMenu
//
function onDone() {
mTimer.reset();
}
// Only for CustomMenu
//! Only for CustomMenu
//
function onFooter() {
mTimer.reset();
}
//! Select event
//!
//! @param item Selected menu item.
//
function onSelect(item as WatchUi.MenuItem) as Void {
mTimer.reset();
if (item instanceof HomeAssistantToggleMenuItem) {
@ -164,7 +183,8 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
}
}
// Only for CustomMenu
//! Only for CustomMenu
//
function onTitle() {
mTimer.reset();
}

View File

@ -11,11 +11,6 @@
//
// J D Abbey & P A Abbey, 28 December 2022
//
//
// Description:
//
// Quit the application after a period of inactivity in order to save the battery.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
@ -23,18 +18,27 @@ using Toybox.Timer;
using Toybox.Application.Properties;
using Toybox.WatchUi;
//! Quit the application after a period of inactivity in order to save the battery.
//!
class QuitTimer extends Timer.Timer {
//! Class Constructor
//
function initialize() {
Timer.Timer.initialize();
}
//! Can't see how to make a method object from `System.exit()` without this layer of
//! indirection. I assume this is because `System` is a static class.
//
function exitApp() as Void {
// System.println("QuitTimer exitApp(): Exiting");
// This will exit the system cleanly from any point within an app.
System.exit();
}
//! Kick off the quit timer.
//
function begin() {
var api_timeout = Settings.getAppTimeout(); // ms
if (api_timeout > 0) {
@ -42,6 +46,8 @@ class QuitTimer extends Timer.Timer {
}
}
//! Reset the quit timer.
//
function reset() {
// System.println("QuitTimer reset(): Restarted quit timer");
stop();

View File

@ -11,35 +11,36 @@
//
// J D Abbey & P A Abbey, 28 December 2022
//
//
// Description:
//
// A view with added methods to scale from percentages of scrren size to pixels.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Math;
//! A view that provides a common method 'pixelsForScreen' to make Views easier to layout on different
//! sized watch screens.
//
class ScalableView extends WatchUi.View {
//! Retain the local screen width for efficiency
private var mScreenWidth;
//! Class Constructor
//
function initialize() {
View.initialize();
mScreenWidth = System.getDeviceSettings().screenWidth;
}
// Convert a fraction expressed as a percentage (%) to a number of pixels for the
// screen's dimensions.
//
// Parameters:
// * dc - Device context
// * pc - Percentage (%) expressed as a number in the range 0.0..100.0
//
// Uses screen width rather than screen height as rectangular screens tend to have
// height > width.
//
//! Convert a fraction expressed as a percentage (%) to a number of pixels for the
//! screen's dimensions.
//!
//! Uses screen width rather than screen height as rectangular screens tend to have
//! height > width.
//!
//! @param pc Percentage (%) expressed as a number in the range 0.0..100.0
//!
//! @return Number of pixels for the screen's dimensions for a fraction expressed as a percentage (%).
//!
function pixelsForScreen(pc as Lang.Float) as Lang.Number {
return Math.round(pc * mScreenWidth) / 100;
}

View File

@ -11,16 +11,6 @@
//
// P A Abbey & J D Abbey, SomeoneOnEarth & moesterheld, 23 November 2023
//
//
// Description:
//
// Home Assistant settings.
//
// WARNING!
//
// Careful putting ErrorView.show() calls in here. They need to be guarded so that
// they do not get called when only displaying the glance view.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
@ -31,6 +21,11 @@ using Toybox.System;
using Toybox.Background;
using Toybox.Time;
//! Home Assistant settings.
//!
//! <em>WARNING!</em> Careful putting ErrorView.show() calls in here. They need to be
//! guarded so that they do not get called when only displaying the glance view.
//
(:glance, :background)
class Settings {
private static var mApiKey as Lang.String = "";
@ -40,19 +35,24 @@ class Settings {
private static var mCacheConfig as Lang.Boolean = false;
private static var mClearCache as Lang.Boolean = false;
private static var mVibrate as Lang.Boolean = false;
private static var mAppTimeout as Lang.Number = 0; // seconds
private static var mPollDelay as Lang.Number = 0; // seconds
private static var mConfirmTimeout as Lang.Number = 3; // seconds
//! seconds
private static var mAppTimeout as Lang.Number = 0;
//! seconds
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 mMenuAlignment as Lang.Number = WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_LEFT;
private static var mIsSensorsLevelEnabled as Lang.Boolean = false;
private static var mBatteryRefreshRate as Lang.Number = 15; // minutes
//! minutes
private static var mBatteryRefreshRate as Lang.Number = 15;
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.
//! Must keep the object so it doesn't get garbage collected.
private static var mWebhookManager as WebhookManager or Null;
// Called on application start and then whenever the settings are changed.
//! Called on application start and then whenever the settings are changed.
//
static function update() {
mIsApp = getApp().getIsApp();
mApiKey = Properties.getValue("api_key");
@ -71,6 +71,8 @@ class Settings {
mBatteryRefreshRate = Properties.getValue("battery_level_refresh_rate");
}
//! A webhook is required for non-privileged API calls.
//
static function webhook() {
if (System has :ServiceDelegate) {
mHasService = true;
@ -116,65 +118,123 @@ class Settings {
// }
}
//! Get the API key supplied as part of the Settings.
//!
//! @return The API Key
//
static function getApiKey() as Lang.String {
return mApiKey;
}
//! Get the Webhook ID supplied as part of the Settings.
//!
//! @return The Webhook ID
//
static function getWebhookId() as Lang.String {
return mWebhookId;
}
//! Set the Webhook ID supplied as part of the Settings.
//!
//! @param webhookId The Webhook ID value to be saved.
//
static function setWebhookId(webhookId as Lang.String) {
mWebhookId = webhookId;
Properties.setValue("webhook_id", mWebhookId);
}
//! Delete the Webhook ID saved as part of the Settings.
//
static function unsetWebhookId() {
mWebhookId = "";
Properties.setValue("webhook_id", mWebhookId);
}
//! Get the API URL supplied as part of the Settings.
//!
//! @return The API URL
//
static function getApiUrl() as Lang.String {
return mApiUrl;
}
//! Get the menu configuration URL supplied as part of the Settings.
//!
//! @return The menu configuration URL
//
static function getConfigUrl() as Lang.String {
return mConfigUrl;
}
//! Get the menu cache Boolean option supplied as part of the Settings.
//!
//! @return Boolean for whether the menu should be cached to save application
//! start up time.
//
static function getCacheConfig() as Lang.Boolean {
return mCacheConfig;
}
//! Get the clear cache Boolean option supplied as part of the Settings.
//!
//! @return Boolean for whether the cache should be cleared next time the
//! application is started, forcing a menu refresh.
//
static function getClearCache() as Lang.Boolean {
return mClearCache;
}
//! Unset the clear cache Boolean option supplied as part of the Settings.
//
static function unsetClearCache() {
mClearCache = false;
Properties.setValue("clear_cache", mClearCache);
}
//! Get the vibration Boolean option supplied as part of the Settings.
//!
//! @return Boolean for whether vibration is enabled.
//
static function getVibrate() as Lang.Boolean {
return mVibrate;
}
//! Get the application timeout value supplied as part of the Settings.
//!
//! @return The application timeout in milliseconds.
//
static function getAppTimeout() as Lang.Number {
return mAppTimeout * 1000; // Convert to milliseconds
}
//! Get the application API polling interval supplied as part of the Settings.
//!
//! @return The application API polling interval in milliseconds.
//
static function getPollDelay() as Lang.Number {
return mPollDelay * 1000; // Convert to milliseconds
}
//! Get the menu item confirmation delay supplied as part of the Settings.
//!
//! @return The menu item confirmation delay in milliseconds.
//
static function getConfirmTimeout() as Lang.Number {
return mConfirmTimeout * 1000; // Convert to milliseconds
}
//! Get the menu item security PIN supplied as part of the Settings.
//!
//! @return The menu item security PIN.
//
static function getPin() as Lang.String or Null {
return mPin;
}
//! Check the user selected PIN confirms to 4 digits as a string.
//!
//! @return The validated 4 digit string.
//
private static function validatePin() as Lang.String or Null {
var pin = Properties.getValue("pin");
if (pin.toNumber() == null || pin.length() != 4) {
@ -183,14 +243,24 @@ class Settings {
return pin;
}
//! Get the menu item alignment as part of the Settings.
//!
//! @return The menu item alignment.
//
static function getMenuAlignment() as Lang.Number {
return mMenuAlignment; // Either WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_RIGHT or WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_LEFT
}
//! Is logging of the watch sensors enabled? E.g. battery, activity etc.
//!
//! @return Boolean for whether logging of the watch sensors is enabled.
//
static function isSensorsLevelEnabled() as Lang.Boolean {
return mIsSensorsLevelEnabled;
}
//! Disable logging of the watch's sensors.
//
static function unsetIsSensorsLevelEnabled() {
mIsSensorsLevelEnabled = false;
Properties.setValue("enable_battery_level", mIsSensorsLevelEnabled);

View File

@ -11,88 +11,99 @@
//
// J D Abbey & P A Abbey, 28 December 2022
//
//
// Description:
//
// WebLog provides a logging and hence debugging aid for when the application is
// deployed to the watch. This is only used for development and use of it must not
// persist into a deployed version. It uses a string buffer to group log entries into
// larger submissions in order to prevent overflow of the blue tooth stack.
//
//-----------------------------------------------------------------------------------
//
// Usage:
// wl = new WebLog();
// wl.clear();
// wl.println("Debug Message");
// wl.flush();
//
// https://domain.name/path/log.php
//
// <?php
// $myfile = fopen("log", "a");
// $queries = array();
// parse_str($_SERVER['QUERY_STRING'], $queries);
// fwrite($myfile, $queries['log']);
// print "Success";
// ?>
//
// Logs published to: https://domain.name/path/log
//
// https://domain.name/path/log_clear.php
//
// <?php
// $myfile = fopen("log", "w");
// fwrite($myfile, "");
// print "Success";
// ?>
using Toybox.Communications;
using Toybox.Lang;
using Toybox.System;
//! WebLog provides a logging and hence debugging aid for when the application is
//! deployed to the watch. This is only used for development and use of it must not
//! persist into a deployed version. It uses a string buffer to group log entries into
//! larger submissions in order to prevent overflow of the Bluetooth stack.
//!
//! Usage:
//! <pre>
//! wl = new WebLog();
//! wl.clear();
//! wl.println("Debug Message");
//! wl.flush();
//! </pre>
//!
//! File: https://domain.name/path/log.php
//!
//! <pre>
//! &lt;?php
//! $myfile = fopen("log", "a");
//! $queries = array();
//! parse_str($_SERVER['QUERY_STRING'], $queries);
//! fwrite($myfile, $queries['log']);
//! print "Success";
//! ?&gt;
//! </pre>
//!
//! Logs published to https://domain.name/path/log.
//!
//! File: https://domain.name/path/log_clear.php
//!
//! <pre>
//! &lt;?php
//! $myfile = fopen("log", "w");
//! fwrite($myfile, "");
//! print "Success";
//! ?&gt;
//! </pre>
//
(:glance, :background)
class WebLog {
private var callsbuffer = 4 as Lang.Number;
private var callsBuffer = 4 as Lang.Number;
private var numCalls = 0 as Lang.Number;
private var buffer = "" as Lang.String;
// Set the number of calls to print() before sending the buffer to the online
// logger.
//! Set the number of calls to print() before sending the buffer to the online
//! logger.
//!
//! @param l The number of log calls to buffer before writing to the online service.
//
function setCallsBuffer(l as Lang.Number) {
callsbuffer = l;
callsBuffer = l;
}
// Get the number of calls to print() before sending the buffer to the online
// logger.
//! Get the number of calls to print() before sending the buffer to the online
//! logger.
//!
//! @return The number of log calls to buffer before writing to the online service.
//
function getCallsBuffer() as Lang.Number {
return callsbuffer;
return callsBuffer;
}
// Create a debug log over the Internet to keep track of the watch's runtime
// execution.
//! Create a debug log over the Internet to keep track of the watch's runtime
//! execution.
//!
//! @param str The string to log.
//
function print(str as Lang.String) {
var myTime = System.getClockTime();
buffer += myTime.hour.format("%02d") + ":" + myTime.min.format("%02d") + ":" + myTime.sec.format("%02d") + " " + str;
numCalls++;
// System.println("WebLog print() str = " + str);
if (numCalls >= callsbuffer) {
if (numCalls >= callsBuffer) {
doPrint();
}
}
// Create a debug log over the Internet to keep track of the watch's runtime
// execution. Add a new line character to the end.
//! Create a debug log over the Internet to keep track of the watch's runtime
//! execution. Add a new line character to the end.
//!
//! @param str The string to log.
//
function println(str as Lang.String) {
print(str + "\n");
}
// Flush the current buffer to the online logger even if it has not reach the
// submission level set by 'callsbuffer'.
//! Flush the current buffer to the online logger even if it has not reach the
//! submission level set by 'callsBuffer'.
//
function flush() {
// System.println("WebLog flush()");
@ -101,7 +112,7 @@ class WebLog {
}
}
// Perform the submission to the online logger.
//! Perform the submission to the online logger.
//
function doPrint() {
// System.println("WebLog doPrint()");
@ -122,8 +133,8 @@ class WebLog {
buffer = "";
}
// Clear the debug log over the Internet to start a new track of the watch's runtime
// execution.
//! Clear the debug log over the Internet to start a new track of the watch's runtime
//! execution.
//
function clear() {
// System.println("WebLog clear()");
@ -143,7 +154,10 @@ class WebLog {
buffer = "";
}
// Callback function to print the outcome of a doPrint() method.
//! Callback function to print the outcome of a doPrint() method. Typically used for debugging this class.
//!
//! @param responseCode Response code.
//! @param data Response data.
//
function onLog(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
// if (responseCode != 200) {
@ -153,7 +167,10 @@ class WebLog {
// }
}
// Callback function to print the outcome of a clear() method.
// Callback function to print the outcome of a clear() method. Typically used for debugging this class.
//!
//! @param responseCode Response code.
//! @param data Response data.
//
function onClear(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
// if (responseCode != 200) {

View File

@ -11,14 +11,6 @@
//
// P A Abbey & J D Abbey, 10 January 2024
//
//
// Description:
//
// Home Assistant Webhook creation.
//
// Reference:
// * https://developers.home-assistant.io/docs/api/native-app-integration
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
@ -26,10 +18,24 @@ using Toybox.Communications;
using Toybox.System;
using Toybox.WatchUi;
// Can use push view so must never be run in a glance context
//! Home Assistant Webhook creation.
//!
//! NB. Because we can use push view (E.g. `ErrorView.show()`) this class must never
//! be run in a glance context.
//!
//! Reference: https://developers.home-assistant.io/docs/api/native-app-integration
//
class WebhookManager {
function onReturnRequestWebhookId(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
//! Callback for requesting a Webhook ID.
//!
//! @param responseCode Response code
//! @param data Return data
//
function onReturnRequestWebhookId(
responseCode as Lang.Number,
data as Null or Lang.Dictionary or Lang.String
) as Void {
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
@ -84,6 +90,8 @@ class WebhookManager {
}
}
//! Request a Webhook ID from Home Assistant for use in this application.
//
function requestWebhookId() {
var deviceSettings = System.getDeviceSettings();
// System.println("WebhookManager requestWebhookId(): Requesting webhook id for device = " + deviceSettings.uniqueIdentifier);
@ -115,7 +123,18 @@ class WebhookManager {
);
}
function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String, sensors as Lang.Array<Lang.Object>) as Void {
//! Callback function for the POST request to register the watch's sensors on the Home Assistant instance.
//!
//! @param responseCode Response code.
//! @param data Response data.
//! @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.
//
function onReturnRegisterWebhookSensor(
responseCode as Lang.Number,
data as Null or Lang.Dictionary or Lang.String,
sensors as Lang.Array<Lang.Object>
) as Void {
switch (responseCode) {
case Communications.BLE_HOST_TIMEOUT:
case Communications.BLE_CONNECTION_UNAVAILABLE:
@ -194,7 +213,11 @@ class WebhookManager {
}
}
function registerWebhookSensor(sensors as Lang.Array<Lang.Object>) {
//! Local method to send the POST request to register a number of sensors.
//!
//! @param sensors An array of sensors, e.g. As created by `registerWebhookSensors()`.
//
private function registerWebhookSensor(sensors as Lang.Array<Lang.Object>) {
var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId();
// System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
// System.println("WebhookManager registerWebhookSensor(): URL=" + url);
@ -217,6 +240,8 @@ class WebhookManager {
);
}
//! Request the creation of all the supported watch sensors on the Home Assistant instance.
//
function registerWebhookSensors() {
var heartRate = Activity.getActivityInfo().currentHeartRate;