mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-07-12 07:48:38 +00:00
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:
@ -4,5 +4,9 @@
|
|||||||
"path": "."
|
"path": "."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {}
|
"settings": {
|
||||||
|
"cSpell.words": [
|
||||||
|
"Initialiser"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,15 +11,6 @@
|
|||||||
//
|
//
|
||||||
// J D Abbey & P A Abbey, 28 December 2022
|
// 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;
|
using Toybox.Lang;
|
||||||
@ -27,6 +18,12 @@ using Toybox.Graphics;
|
|||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
using Toybox.Timer;
|
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 {
|
class Alert extends WatchUi.View {
|
||||||
private static const scRadius = 10;
|
private static const scRadius = 10;
|
||||||
private var mTimer as Timer.Timer;
|
private var mTimer as Timer.Timer;
|
||||||
@ -36,6 +33,16 @@ class Alert extends WatchUi.View {
|
|||||||
private var mFgcolor as Graphics.ColorType;
|
private var mFgcolor as Graphics.ColorType;
|
||||||
private var mBgcolor as Graphics.ColorType;
|
private var mBgcolor as Graphics.ColorType;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//! @param params A dictionary object as follows:<br>
|
||||||
|
//! {<br>
|
||||||
|
//!   :timeout as Lang.Number, // Timeout in millseconds<br>
|
||||||
|
//!   :font as Graphics.FontType, // Text font size<br>
|
||||||
|
//!   :text as Lang.String, // Text to display<br>
|
||||||
|
//!   :fgcolor as Graphics.ColorType, // Foreground Colour<br>
|
||||||
|
//!   :bgcolor as Graphics.ColorType // Background Colour<br>
|
||||||
|
//! }
|
||||||
|
//
|
||||||
function initialize(params as Lang.Dictionary) {
|
function initialize(params as Lang.Dictionary) {
|
||||||
View.initialize();
|
View.initialize();
|
||||||
|
|
||||||
@ -67,14 +74,22 @@ class Alert extends WatchUi.View {
|
|||||||
mTimer = new Timer.Timer();
|
mTimer = new Timer.Timer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Setup a timer to dismiss the alert.
|
||||||
|
//
|
||||||
function onShow() {
|
function onShow() {
|
||||||
mTimer.start(method(:dismiss), mTimeout, false);
|
mTimer.start(method(:dismiss), mTimeout, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Prematurely stop the timer.
|
||||||
|
//
|
||||||
function onHide() {
|
function onHide() {
|
||||||
mTimer.stop();
|
mTimer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Draw the Alert view.
|
||||||
|
//!
|
||||||
|
//! @param dc Device context
|
||||||
|
//
|
||||||
function onUpdate(dc as Graphics.Dc) {
|
function onUpdate(dc as Graphics.Dc) {
|
||||||
var tWidth = dc.getTextWidthInPixels(mText, mFont);
|
var tWidth = dc.getTextWidthInPixels(mText, mFont);
|
||||||
var tHeight = dc.getFontHeight(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);
|
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 {
|
function dismiss() as Void {
|
||||||
WatchUi.popView(SLIDE_IMMEDIATE);
|
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);
|
WatchUi.pushView(self, new AlertDelegate(self), transition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Input Delegate for the Alert view.
|
||||||
|
//
|
||||||
class AlertDelegate extends WatchUi.InputDelegate {
|
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();
|
InputDelegate.initialize();
|
||||||
mView = view;
|
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();
|
mView.dismiss();
|
||||||
getApp().getQuitTimer().reset();
|
getApp().getQuitTimer().reset();
|
||||||
return true;
|
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();
|
mView.dismiss();
|
||||||
getApp().getQuitTimer().reset();
|
getApp().getQuitTimer().reset();
|
||||||
return true;
|
return true;
|
||||||
|
@ -11,12 +11,6 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
// 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;
|
using Toybox.Lang;
|
||||||
@ -25,20 +19,43 @@ using Toybox.Background;
|
|||||||
using Toybox.System;
|
using Toybox.System;
|
||||||
using Toybox.Activity;
|
using Toybox.Activity;
|
||||||
|
|
||||||
|
//! The background service delegate reports the Garmin watch's various status values
|
||||||
|
//! back to the Home Assistant instance.
|
||||||
|
//
|
||||||
(:background)
|
(:background)
|
||||||
class BackgroundServiceDelegate extends System.ServiceDelegate {
|
class BackgroundServiceDelegate extends System.ServiceDelegate {
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
ServiceDelegate.initialize();
|
ServiceDelegate.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReturnBatteryUpdate(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
//! Callback function for doUpdate().
|
||||||
// System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Code: " + responseCode);
|
//!
|
||||||
// System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Data: " + data);
|
//! @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);
|
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>
|
||||||
|
//! {<br>
|
||||||
|
//!   :sport as Activity.Sport<br>
|
||||||
|
//!   :subSport as Activity.SubSport<br>
|
||||||
|
//! }
|
||||||
|
//
|
||||||
|
function onActivityCompleted(
|
||||||
|
activity as {
|
||||||
|
:sport as Activity.Sport,
|
||||||
|
:subSport as Activity.SubSport
|
||||||
|
}
|
||||||
|
) as Void {
|
||||||
if (!System.getDeviceSettings().phoneConnected) {
|
if (!System.getDeviceSettings().phoneConnected) {
|
||||||
// System.println("BackgroundServiceDelegate onActivityCompleted(): No Phone connection, skipping API call.");
|
// System.println("BackgroundServiceDelegate onActivityCompleted(): No Phone connection, skipping API call.");
|
||||||
} else if (!System.getDeviceSettings().connectionAvailable) {
|
} 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 {
|
function onTemporalEvent() as Void {
|
||||||
if (!System.getDeviceSettings().phoneConnected) {
|
if (!System.getDeviceSettings().phoneConnected) {
|
||||||
// System.println("BackgroundServiceDelegate onTemporalEvent(): No Phone connection, skipping API call.");
|
// 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.");
|
// System.println("BackgroundServiceDelegate onTemporalEvent(): Making API call.");
|
||||||
var position = Position.getInfo();
|
var position = Position.getInfo();
|
||||||
// System.println("BackgroundServiceDelegate onTemporalEvent(): GPS : " + position.position.toDegrees());
|
// System.println("BackgroundServiceDelegate onTemporalEvent(): GPS : " + position.position.toDegrees());
|
||||||
@ -136,7 +163,7 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
|
|||||||
},
|
},
|
||||||
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
||||||
},
|
},
|
||||||
method(:onReturnBatteryUpdate)
|
method(:onReturnDoUpdate)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
var activityInfo = ActivityMonitor.getInfo();
|
var activityInfo = ActivityMonitor.getInfo();
|
||||||
@ -222,7 +249,7 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
|
|||||||
},
|
},
|
||||||
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
||||||
},
|
},
|
||||||
method(:onReturnBatteryUpdate)
|
method(:onReturnDoUpdate)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,15 +11,12 @@
|
|||||||
//
|
//
|
||||||
// J D Abbey & P A Abbey, 28 December 2022
|
// 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)
|
(:glance)
|
||||||
class ClientId {
|
class ClientId {
|
||||||
static const webLogUrl = "https://...";
|
static const webLogUrl = "https://...";
|
||||||
|
@ -11,22 +11,6 @@
|
|||||||
//
|
//
|
||||||
// J D Abbey & P A Abbey, 28 December 2022
|
// 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;
|
using Toybox.Graphics;
|
||||||
@ -35,6 +19,19 @@ using Toybox.WatchUi;
|
|||||||
using Toybox.Communications;
|
using Toybox.Communications;
|
||||||
using Toybox.Timer;
|
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 {
|
class ErrorView extends ScalableView {
|
||||||
private static const scErrorIconMargin as Lang.Float = 7f;
|
private static const scErrorIconMargin as Lang.Float = 7f;
|
||||||
private var mText as Lang.String = "";
|
private var mText as Lang.String = "";
|
||||||
@ -48,9 +45,11 @@ class ErrorView extends ScalableView {
|
|||||||
private static var instance;
|
private static var instance;
|
||||||
private static var mShown as Lang.Boolean = false;
|
private static var mShown as Lang.Boolean = false;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
ScalableView.initialize();
|
ScalableView.initialize();
|
||||||
mDelegate = new ErrorDelegate(self);
|
mDelegate = new ErrorDelegate();
|
||||||
// Convert the settings from % of screen size to pixels
|
// Convert the settings from % of screen size to pixels
|
||||||
mErrorIconMargin = pixelsForScreen(scErrorIconMargin);
|
mErrorIconMargin = pixelsForScreen(scErrorIconMargin);
|
||||||
mErrorIcon = Application.loadResource(Rez.Drawables.ErrorIcon) as Graphics.BitmapResource;
|
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 {
|
function onLayout(dc as Graphics.Dc) as Void {
|
||||||
var w = dc.getWidth();
|
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 {
|
function onUpdate(dc as Graphics.Dc) as Void {
|
||||||
var w = dc.getWidth();
|
var w = dc.getWidth();
|
||||||
if (mAntiAlias) {
|
if (mAntiAlias) {
|
||||||
@ -87,10 +92,18 @@ class ErrorView extends ScalableView {
|
|||||||
mTextArea.draw(dc);
|
mTextArea.draw(dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Get this view's delegate for processing events.
|
||||||
|
//
|
||||||
function getDelegate() as ErrorDelegate {
|
function getDelegate() as ErrorDelegate {
|
||||||
return mDelegate;
|
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 ] {
|
static function create(text as Lang.String) as [ WatchUi.Views ] or [ WatchUi.Views, WatchUi.InputDelegates ] {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = new ErrorView();
|
instance = new ErrorView();
|
||||||
@ -102,7 +115,10 @@ class ErrorView extends ScalableView {
|
|||||||
return [instance, instance.getDelegate()];
|
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 {
|
static function show(text as Lang.String) as Void {
|
||||||
if (!mShown) {
|
if (!mShown) {
|
||||||
create(text); // Ignore returned values
|
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 {
|
static function unShow() as Void {
|
||||||
if (mShown) {
|
if (mShown) {
|
||||||
WatchUi.popView(WatchUi.SLIDE_DOWN);
|
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 {
|
function setText(text as Lang.String) as Void {
|
||||||
mText = text;
|
mText = text;
|
||||||
if (mTextArea != null) {
|
if (mTextArea != null) {
|
||||||
@ -137,12 +158,19 @@ class ErrorView extends ScalableView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! Delegate for the ErrorView.
|
||||||
|
//
|
||||||
class ErrorDelegate extends WatchUi.BehaviorDelegate {
|
class ErrorDelegate extends WatchUi.BehaviorDelegate {
|
||||||
|
|
||||||
function initialize(view as ErrorView) {
|
//! Class Constructor
|
||||||
|
//!
|
||||||
|
function initialize() {
|
||||||
WatchUi.BehaviorDelegate.initialize();
|
WatchUi.BehaviorDelegate.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Process the event to clear the ErrorView.
|
||||||
|
//
|
||||||
function onBack() as Lang.Boolean {
|
function onBack() as Lang.Boolean {
|
||||||
getApp().getQuitTimer().reset();
|
getApp().getQuitTimer().reset();
|
||||||
ErrorView.unShow();
|
ErrorView.unShow();
|
||||||
|
@ -11,30 +11,38 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// Home Assistant centralised constants.
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Toybox.Lang;
|
using Toybox.Lang;
|
||||||
|
|
||||||
|
//! Home Assistant centralised constants.
|
||||||
|
//
|
||||||
(:glance)
|
(:glance)
|
||||||
class Globals {
|
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 scAlertTimeout = 2000; // ms
|
||||||
static const scTapTimeout = 1000; // ms
|
|
||||||
// Time to let the existing HTTP responses get serviced after a
|
//! Time to let the existing HTTP responses get serviced after a
|
||||||
// Communications.NETWORK_RESPONSE_OUT_OF_MEMORY response code.
|
//! `Communications.NETWORK_RESPONSE_OUT_OF_MEMORY` response code.
|
||||||
static const scApiBackoff = 1000; // ms
|
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
|
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.
|
static const scLowMem = 0.90; // percent as a fraction.
|
||||||
|
|
||||||
// Constants for PIN confirmation dialog
|
//! Constant for PIN confirmation dialog.<br>
|
||||||
static const scPinMaxFailures = 5; // Maximum number of failed PIN confirmation attemps allwed in ...
|
//! Maximum number of failed PIN confirmation attempts allowed in `scPinMaxFailureMinutes`.
|
||||||
static const scPinMaxFailureMinutes = 2; // ... this number of minutes before PIN confirmation is locked for ...
|
static const scPinMaxFailures = 5;
|
||||||
static const scPinLockTimeMinutes = 10; // ... this number of minutes
|
|
||||||
|
//! 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;
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,6 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// Application root for GarminHomeAssistant
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Toybox.Application;
|
using Toybox.Application;
|
||||||
@ -25,6 +20,8 @@ using Toybox.System;
|
|||||||
using Toybox.Application.Properties;
|
using Toybox.Application.Properties;
|
||||||
using Toybox.Timer;
|
using Toybox.Timer;
|
||||||
|
|
||||||
|
//! Application root for GarminHomeAssistant
|
||||||
|
//
|
||||||
(:glance, :background)
|
(:glance, :background)
|
||||||
class HomeAssistantApp extends Application.AppBase {
|
class HomeAssistantApp extends Application.AppBase {
|
||||||
private var mApiStatus as Lang.String or Null;
|
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 mUpdating as Lang.Boolean = false; // Don't start a second chain of updates
|
||||||
private var mTemplates as Lang.Dictionary = {};
|
private var mTemplates as Lang.Dictionary = {};
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
AppBase.initialize();
|
AppBase.initialize();
|
||||||
// ATTENTION when adding stuff into this block:
|
// ATTENTION when adding stuff into this block:
|
||||||
@ -55,7 +54,10 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
// with "(:glance)".
|
// 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 {
|
function onStart(state as Lang.Dictionary?) as Void {
|
||||||
AppBase.onStart(state);
|
AppBase.onStart(state);
|
||||||
// ATTENTION when adding stuff into this block:
|
// ATTENTION when adding stuff into this block:
|
||||||
@ -71,7 +73,11 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
// with "(:glance)".
|
// 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 {
|
function onStop(state as Lang.Dictionary?) as Void {
|
||||||
AppBase.onStop(state);
|
AppBase.onStop(state);
|
||||||
// ATTENTION when adding stuff into this block:
|
// ATTENTION when adding stuff into this block:
|
||||||
@ -87,7 +93,10 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
// with "(:glance)".
|
// 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 ] {
|
function getInitialView() as [ WatchUi.Views ] or [ WatchUi.Views, WatchUi.InputDelegates ] {
|
||||||
mIsApp = true;
|
mIsApp = true;
|
||||||
mQuitTimer = new QuitTimer();
|
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)
|
(: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 Code: " + responseCode);
|
||||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Data: " + data);
|
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Data: " + data);
|
||||||
|
|
||||||
@ -208,8 +223,11 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
WatchUi.requestUpdate();
|
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
|
//! Fetch the menu configuration over HTTPS, which might be locally cached.
|
||||||
// asynchronous and affects how the views are managed.
|
//!
|
||||||
|
//! @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)
|
(:glance)
|
||||||
function fetchMenuConfig() as Lang.Boolean {
|
function fetchMenuConfig() as Lang.Boolean {
|
||||||
// System.println("Menu URL = " + Settings.getConfigUrl());
|
// System.println("Menu URL = " + Settings.getConfigUrl());
|
||||||
@ -263,6 +281,10 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
return false;
|
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) {
|
private function buildMenu(menu as Lang.Dictionary) {
|
||||||
mHaMenu = new HomeAssistantView(menu, null);
|
mHaMenu = new HomeAssistantView(menu, null);
|
||||||
mQuitTimer.begin();
|
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.
|
} // 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() {
|
function startUpdates() {
|
||||||
if (mHaMenu != null and !mUpdating) {
|
if (mHaMenu != null and !mUpdating) {
|
||||||
// Start the continuous update process that continues for as long as the application is running.
|
// 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 Code: " + responseCode);
|
||||||
// System.println("HomeAssistantApp onReturnUpdateMenuItems() Response Data: " + data);
|
// System.println("HomeAssistantApp onReturnUpdateMenuItems() Response Data: " + data);
|
||||||
|
|
||||||
@ -353,6 +385,8 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
setApiStatus(status);
|
setApiStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Construct the GET request to update all menu items.
|
||||||
|
//
|
||||||
function updateMenuItems() as Void {
|
function updateMenuItems() as Void {
|
||||||
if (! System.getDeviceSettings().phoneConnected) {
|
if (! System.getDeviceSettings().phoneConnected) {
|
||||||
// System.println("HomeAssistantApp updateMenuItems(): No Phone connection, skipping API call.");
|
// System.println("HomeAssistantApp updateMenuItems(): No Phone connection, skipping API call.");
|
||||||
@ -368,17 +402,17 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
mTemplates = {};
|
mTemplates = {};
|
||||||
for (var i = 0; i < mItemsToUpdate.size(); i++) {
|
for (var i = 0; i < mItemsToUpdate.size(); i++) {
|
||||||
var item = mItemsToUpdate[i];
|
var item = mItemsToUpdate[i];
|
||||||
var template = item.buildTemplate();
|
var template = item.getTemplate();
|
||||||
if (template != null) {
|
if (template != null) {
|
||||||
mTemplates.put(i.toString(), {
|
mTemplates.put(i.toString(), {
|
||||||
"template" => template
|
"template" => template
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||||
mTemplates.put(i.toString() + "t", {
|
mTemplates.put(i.toString() + "t", {
|
||||||
"template" => (item as HomeAssistantToggleMenuItem).buildToggleTemplate()
|
"template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
|
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
|
||||||
@ -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)
|
(: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 Code: " + responseCode);
|
||||||
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Data: " + data);
|
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Data: " + data);
|
||||||
|
|
||||||
@ -466,6 +506,8 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
WatchUi.requestUpdate();
|
WatchUi.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Construct the GET request to test the API status, is it accessible?
|
||||||
|
//
|
||||||
(:glance)
|
(:glance)
|
||||||
function fetchApiStatus() as Void {
|
function fetchApiStatus() as Void {
|
||||||
// System.println("API URL = " + Settings.getApiUrl());
|
// 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) {
|
function setApiStatus(s as Lang.String) {
|
||||||
mApiStatus = s;
|
mApiStatus = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Return the API status result.
|
||||||
|
//!
|
||||||
|
//! @return A string describing the API status
|
||||||
|
//
|
||||||
(:glance)
|
(:glance)
|
||||||
function getApiStatus() as Lang.String {
|
function getApiStatus() as Lang.String {
|
||||||
return mApiStatus;
|
return mApiStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Return the Menu status result.
|
||||||
|
//!
|
||||||
|
//! @return A string describing the Menu status
|
||||||
|
//
|
||||||
(:glance)
|
(:glance)
|
||||||
function getMenuStatus() as Lang.String {
|
function getMenuStatus() as Lang.String {
|
||||||
return mMenuStatus;
|
return mMenuStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Return the Menu construction status.
|
||||||
|
//!
|
||||||
|
//! @return A Boolean indicating if the menu is loaded into the application.
|
||||||
|
//
|
||||||
function isHomeAssistantMenuLoaded() as Lang.Boolean {
|
function isHomeAssistantMenuLoaded() as Lang.Boolean {
|
||||||
return mHaMenu != null;
|
return mHaMenu != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Make the menu visible on the watch face.
|
||||||
|
//
|
||||||
function pushHomeAssistantMenuView() as Void {
|
function pushHomeAssistantMenuView() as Void {
|
||||||
WatchUi.pushView(mHaMenu, new HomeAssistantViewDelegate(true), WatchUi.SLIDE_IMMEDIATE);
|
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
|
//! Force status updates. Only take action if `Settings.getPollDelay() > 0`. This must be tested
|
||||||
// alternative action if the test fails.
|
//! locally as it is then efficient to take alternative action if the test fails.
|
||||||
|
//
|
||||||
function forceStatusUpdates() as Void {
|
function forceStatusUpdates() as Void {
|
||||||
// Don't mess with updates unless we are using a timer.
|
// Don't mess with updates unless we are using a timer.
|
||||||
if (Settings.getPollDelay() > 0) {
|
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 {
|
function getQuitTimer() as QuitTimer {
|
||||||
return mQuitTimer;
|
return mQuitTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Return the glance view.
|
||||||
|
//!
|
||||||
|
//! @return The glance view
|
||||||
|
//
|
||||||
function getGlanceView() as [ WatchUi.GlanceView ] or [ WatchUi.GlanceView, WatchUi.GlanceViewDelegate ] or Null {
|
function getGlanceView() as [ WatchUi.GlanceView ] or [ WatchUi.GlanceView, WatchUi.GlanceViewDelegate ] or Null {
|
||||||
mIsGlance = true;
|
mIsGlance = true;
|
||||||
mApiStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
|
mApiStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
|
||||||
@ -554,30 +623,38 @@ class HomeAssistantApp extends Application.AppBase {
|
|||||||
return [new HomeAssistantGlanceView(self)];
|
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 {
|
function updateStatus() as Void {
|
||||||
mGlanceTimer = null;
|
mGlanceTimer = null;
|
||||||
fetchMenuConfig();
|
fetchMenuConfig();
|
||||||
fetchApiStatus();
|
fetchApiStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Code for when the application settings are updated.
|
||||||
|
//
|
||||||
function onSettingsChanged() as Void {
|
function onSettingsChanged() as Void {
|
||||||
// System.println("HomeAssistantApp onSettingsChanged()");
|
// System.println("HomeAssistantApp onSettingsChanged()");
|
||||||
Settings.update();
|
Settings.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called each time the Registered Temporal Event is to be invoked. So the object is created each time on request and
|
//! Called each time the Registered Temporal Event is to be invoked. So the object is created each time
|
||||||
// then destroyed on completion (to save resources).
|
//! on request and then destroyed on completion (to save resources).
|
||||||
|
//
|
||||||
function getServiceDelegate() as [ System.ServiceDelegate ] {
|
function getServiceDelegate() as [ System.ServiceDelegate ] {
|
||||||
return [new BackgroundServiceDelegate()];
|
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 {
|
function getIsApp() as Lang.Boolean {
|
||||||
return mIsApp;
|
return mIsApp;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Global function to return the application object.
|
||||||
|
//
|
||||||
(:glance, :background)
|
(:glance, :background)
|
||||||
function getApp() as HomeAssistantApp {
|
function getApp() as HomeAssistantApp {
|
||||||
return Application.getApp() as HomeAssistantApp;
|
return Application.getApp() as HomeAssistantApp;
|
||||||
|
@ -11,11 +11,6 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth, 19 November 2023
|
// P A Abbey & J D Abbey & Someone0nEarth, 19 November 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// Calling a Home Assistant confirmation dialogue view.
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Toybox.Lang;
|
using Toybox.Lang;
|
||||||
@ -25,19 +20,27 @@ using Toybox.WatchUi;
|
|||||||
using Toybox.Timer;
|
using Toybox.Timer;
|
||||||
using Toybox.Application.Properties;
|
using Toybox.Application.Properties;
|
||||||
|
|
||||||
|
//! Calling a Home Assistant confirmation dialogue view.
|
||||||
|
//
|
||||||
class HomeAssistantConfirmation extends WatchUi.Confirmation {
|
class HomeAssistantConfirmation extends WatchUi.Confirmation {
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
|
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Delegate to respond to the confirmation request.
|
||||||
|
//
|
||||||
class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
|
class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
|
||||||
private var mConfirmMethod as Method(state as Lang.Boolean) as Void;
|
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 or Null;
|
||||||
private var mState as Lang.Boolean;
|
private var mState as Lang.Boolean;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize(callback as Method(state as Lang.Boolean) as Void, state as Lang.Boolean) {
|
function initialize(callback as Method(state as Lang.Boolean) as Void, state as Lang.Boolean) {
|
||||||
WatchUi.ConfirmationDelegate.initialize();
|
WatchUi.ConfirmationDelegate.initialize();
|
||||||
mConfirmMethod = callback;
|
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();
|
getApp().getQuitTimer().reset();
|
||||||
if (mTimer != null) {
|
if (mTimer != null) {
|
||||||
mTimer.stop();
|
mTimer.stop();
|
||||||
@ -60,6 +68,7 @@ class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Function supplied to a timer in order to limit the time for which the confirmation can be provided.
|
||||||
function onTimeout() as Void {
|
function onTimeout() as Void {
|
||||||
mTimer.stop();
|
mTimer.stop();
|
||||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||||
|
@ -11,17 +11,14 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth, 23 November 2023
|
// P A Abbey & J D Abbey & Someone0nEarth, 23 November 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// Glance view for GarminHomeAssistant
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Toybox.Lang;
|
using Toybox.Lang;
|
||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
using Toybox.Graphics;
|
using Toybox.Graphics;
|
||||||
|
|
||||||
|
//! Glance view for GarminHomeAssistant
|
||||||
|
//
|
||||||
(:glance)
|
(:glance)
|
||||||
class HomeAssistantGlanceView extends WatchUi.GlanceView {
|
class HomeAssistantGlanceView extends WatchUi.GlanceView {
|
||||||
private static const scLeftMargin = 5; // in pixels
|
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 mMenuStatus as WatchUi.Text or Null;
|
||||||
private var mAntiAlias as Lang.Boolean = false;
|
private var mAntiAlias as Lang.Boolean = false;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize(app as HomeAssistantApp) {
|
function initialize(app as HomeAssistantApp) {
|
||||||
GlanceView.initialize();
|
GlanceView.initialize();
|
||||||
mApp = app;
|
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 {
|
function onLayout(dc as Graphics.Dc) as Void {
|
||||||
var h = dc.getHeight();
|
var h = dc.getHeight();
|
||||||
var tw = dc.getTextWidthInPixels(WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String, Graphics.FONT_XTINY);
|
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 {
|
function onUpdate(dc as Graphics.Dc) as Void {
|
||||||
GlanceView.onUpdate(dc);
|
GlanceView.onUpdate(dc);
|
||||||
if(mAntiAlias) {
|
if(mAntiAlias) {
|
||||||
|
@ -11,20 +11,19 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
// 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.Lang;
|
||||||
using Toybox.WatchUi;
|
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 {
|
class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
|
||||||
private var mMenu as HomeAssistantView;
|
private var mMenu as HomeAssistantView;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize(
|
function initialize(
|
||||||
definition as Lang.Dictionary,
|
definition as Lang.Dictionary,
|
||||||
template as Lang.String,
|
template as Lang.String,
|
||||||
@ -48,6 +47,8 @@ class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
|
|||||||
mMenu = new HomeAssistantView(definition, null);
|
mMenu = new HomeAssistantView(definition, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Return the submenu for this group menu item.
|
||||||
|
//
|
||||||
function getMenuView() as HomeAssistantView {
|
function getMenuView() as HomeAssistantView {
|
||||||
return mMenu;
|
return mMenu;
|
||||||
}
|
}
|
||||||
|
@ -11,20 +11,23 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
// 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.Lang;
|
||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
using Toybox.Graphics;
|
using Toybox.Graphics;
|
||||||
|
|
||||||
|
//! Generic menu button with an icon that optionally renders a Home Assistant Template.
|
||||||
|
//
|
||||||
class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
|
class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
|
||||||
private var mTemplate as Lang.String or Null;
|
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(
|
function initialize(
|
||||||
label as Lang.String or Lang.Symbol,
|
label as Lang.String or Lang.Symbol,
|
||||||
template as Lang.String,
|
template as Lang.String,
|
||||||
@ -43,14 +46,27 @@ class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
|
|||||||
mTemplate = template;
|
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 {
|
function hasTemplate() as Lang.Boolean {
|
||||||
return mTemplate != null;
|
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;
|
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 {
|
function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
setSubLabel($.Rez.Strings.Empty);
|
setSubLabel($.Rez.Strings.Empty);
|
||||||
|
@ -11,17 +11,14 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth, 17 November 2023
|
// P A Abbey & J D Abbey & Someone0nEarth, 17 November 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// MenuItems Factory.
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Toybox.Application;
|
using Toybox.Application;
|
||||||
using Toybox.Lang;
|
using Toybox.Lang;
|
||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
|
|
||||||
|
//! MenuItems Factory class.
|
||||||
|
//
|
||||||
class HomeAssistantMenuItemFactory {
|
class HomeAssistantMenuItemFactory {
|
||||||
private var mMenuItemOptions as Lang.Dictionary;
|
private var mMenuItemOptions as Lang.Dictionary;
|
||||||
private var mTapTypeIcon as WatchUi.Bitmap;
|
private var mTapTypeIcon as WatchUi.Bitmap;
|
||||||
@ -31,6 +28,8 @@ class HomeAssistantMenuItemFactory {
|
|||||||
|
|
||||||
private static var instance;
|
private static var instance;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
private function initialize() {
|
private function initialize() {
|
||||||
mMenuItemOptions = {
|
mMenuItemOptions = {
|
||||||
:alignment => Settings.getMenuAlignment()
|
:alignment => Settings.getMenuAlignment()
|
||||||
@ -57,6 +56,8 @@ class HomeAssistantMenuItemFactory {
|
|||||||
mHomeAssistantService = new HomeAssistantService();
|
mHomeAssistantService = new HomeAssistantService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Create the one and only instance of this class.
|
||||||
|
//
|
||||||
static function create() as HomeAssistantMenuItemFactory {
|
static function create() as HomeAssistantMenuItemFactory {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = new HomeAssistantMenuItemFactory();
|
instance = new HomeAssistantMenuItemFactory();
|
||||||
@ -64,6 +65,14 @@ class HomeAssistantMenuItemFactory {
|
|||||||
return instance;
|
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(
|
function toggle(
|
||||||
label as Lang.String or Lang.Symbol,
|
label as Lang.String or Lang.Symbol,
|
||||||
entity_id as Lang.String or Null,
|
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(
|
function tap(
|
||||||
label as Lang.String or Lang.Symbol,
|
label as Lang.String or Lang.Symbol,
|
||||||
entity as Lang.String or Null,
|
entity_id as Lang.String or Null,
|
||||||
template as Lang.String or Null,
|
template as Lang.String or Null,
|
||||||
service as Lang.String or Null,
|
service as Lang.String or Null,
|
||||||
confirm as Lang.Boolean,
|
confirm as Lang.Boolean,
|
||||||
pin as Lang.Boolean,
|
pin as Lang.Boolean,
|
||||||
data as Lang.Dictionary or Null
|
data as Lang.Dictionary or Null
|
||||||
) as WatchUi.MenuItem {
|
) as WatchUi.MenuItem {
|
||||||
if (entity != null) {
|
if (entity_id != null) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
data = { "entity_id" => entity };
|
data = { "entity_id" => entity_id };
|
||||||
} else {
|
} else {
|
||||||
data.put("entity_id", entity);
|
data.put("entity_id", entity_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (service != null) {
|
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(
|
function group(
|
||||||
definition as Lang.Dictionary,
|
definition as Lang.Dictionary,
|
||||||
template as Lang.String or Null
|
template as Lang.String or Null
|
||||||
|
@ -11,25 +11,28 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// Pin Confirmation dialog and logic.
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
import Toybox.Graphics;
|
using Toybox.Graphics;
|
||||||
import Toybox.Lang;
|
using Toybox.Lang;
|
||||||
import Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
import Toybox.Timer;
|
using Toybox.Timer;
|
||||||
import Toybox.Attention;
|
using Toybox.Attention;
|
||||||
import Toybox.Time;
|
using Toybox.Time;
|
||||||
|
|
||||||
|
//! Pin digit used for number 0..9
|
||||||
|
//
|
||||||
class PinDigit extends WatchUi.Selectable {
|
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 marginX = stepX * 0.05; // 5% margin on all sides
|
||||||
var marginY = stepY * 0.05;
|
var marginY = stepY * 0.05;
|
||||||
var x = (digit == 0) ? stepX : stepX * ((digit+2) % 3); // layout '0' in 2nd col, others ltr in 3 columns
|
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;
|
mDigit = digit;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDigit() as Number {
|
//! Return the digit 0..9 represented by this button
|
||||||
|
//
|
||||||
|
function getDigit() as Lang.Number {
|
||||||
return mDigit;
|
return mDigit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Customised drawing of a PIN digit's button.
|
||||||
|
//
|
||||||
class PinDigitButton extends WatchUi.Drawable {
|
class PinDigitButton extends WatchUi.Drawable {
|
||||||
private var mText as Number;
|
private var mText as Lang.Number;
|
||||||
private var mTouched as Boolean = false;
|
private var mTouched as Lang.Boolean = false;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//!
|
||||||
|
//! @param options See `Drawable.initialize()`, but with `:label` and `:touched` added.<br>
|
||||||
|
//! {<br>
|
||||||
|
//!   :label as Lang.Number, // The digit 0..9 to display<br>
|
||||||
|
//!   :touched as Lang.Boolean, // Should the digit be filled to indicate it has been pressed?<br>
|
||||||
|
//!   + those required by `Drawable.initialize()`<br>
|
||||||
|
//! }
|
||||||
|
//
|
||||||
function initialize(options) {
|
function initialize(options) {
|
||||||
Drawable.initialize(options);
|
Drawable.initialize(options);
|
||||||
mText = options.get(:label);
|
mText = options.get(:label);
|
||||||
mTouched = options.get(:touched);
|
mTouched = options.get(:touched);
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw(dc) {
|
//! Draw the PIN digit button.
|
||||||
|
//!
|
||||||
|
//! @param dc Device context
|
||||||
|
//
|
||||||
|
function draw(dc as Graphics.Dc) {
|
||||||
if (mTouched) {
|
if (mTouched) {
|
||||||
dc.setColor(Graphics.COLOR_ORANGE, Graphics.COLOR_ORANGE);
|
dc.setColor(Graphics.COLOR_ORANGE, Graphics.COLOR_ORANGE);
|
||||||
} else {
|
} else {
|
||||||
@ -98,17 +117,27 @@ class PinDigit extends WatchUi.Selectable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! Pin Confirmation dialog and logic.
|
||||||
|
//
|
||||||
class HomeAssistantPinConfirmationView extends WatchUi.View {
|
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;
|
||||||
var mPinMask as String = "";
|
//! Indicates how many digits have been entered so far.
|
||||||
|
var mPinMask as Lang.String = "";
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
View.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 stepX = (dc.getWidth() - MARGIN_X * 2) / 3; // three columns
|
||||||
var stepY = dc.getHeight() / 5; // five rows (first row for masked pin entry)
|
var stepY = dc.getHeight() / 5; // five rows (first row for masked pin entry)
|
||||||
var digits = [];
|
var digits = [];
|
||||||
@ -119,7 +148,11 @@ class HomeAssistantPinConfirmationView extends WatchUi.View {
|
|||||||
setLayout(digits);
|
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);
|
View.onUpdate(dc);
|
||||||
if (mPinMask.length() != 0) {
|
if (mPinMask.length() != 0) {
|
||||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
|
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 = "";
|
mPinMask = "";
|
||||||
for (var i=0; i<length; i++) {
|
for (var i=0; i<length; i++) {
|
||||||
mPinMask += "*";
|
mPinMask += "*";
|
||||||
@ -138,17 +175,31 @@ class HomeAssistantPinConfirmationView extends WatchUi.View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! Delegate for the HomeAssistantPinConfirmationView.
|
||||||
|
//
|
||||||
class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||||
|
|
||||||
private var mPin as String;
|
private var mPin as Lang.String;
|
||||||
private var mEnteredPin as String;
|
private var mEnteredPin as Lang.String;
|
||||||
private var mConfirmMethod as Method(state as Lang.Boolean) as Void;
|
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 or Null;
|
||||||
private var mState as Lang.Boolean;
|
private var mState as Lang.Boolean;
|
||||||
private var mFailures as PinFailures;
|
private var mFailures as PinFailures;
|
||||||
private var mView as HomeAssistantPinConfirmationView;
|
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();
|
BehaviorDelegate.initialize();
|
||||||
mFailures = new PinFailures();
|
mFailures = new PinFailures();
|
||||||
if (mFailures.isLocked()) {
|
if (mFailures.isLocked()) {
|
||||||
@ -165,7 +216,12 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
|||||||
resetTimer();
|
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()) {
|
if (mFailures.isLocked()) {
|
||||||
goBack();
|
goBack();
|
||||||
}
|
}
|
||||||
@ -173,7 +229,7 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
|||||||
if (instance instanceof PinDigit && event.getPreviousState() == :stateSelected) {
|
if (instance instanceof PinDigit && event.getPreviousState() == :stateSelected) {
|
||||||
mEnteredPin += instance.getDigit();
|
mEnteredPin += instance.getDigit();
|
||||||
createUserFeedback();
|
createUserFeedback();
|
||||||
// System.println("HomeAssitantPinConfirmationDelegate onSelectable() mEnteredPin = " + mEnteredPin);
|
// System.println("HomeAssistantPinConfirmationDelegate onSelectable() mEnteredPin = " + mEnteredPin);
|
||||||
if (mEnteredPin.length() == mPin.length()) {
|
if (mEnteredPin.length() == mPin.length()) {
|
||||||
if (mEnteredPin.equals(mPin)) {
|
if (mEnteredPin.equals(mPin)) {
|
||||||
mFailures.reset();
|
mFailures.reset();
|
||||||
@ -193,6 +249,8 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Hepatic feedback.
|
||||||
|
//
|
||||||
function createUserFeedback() {
|
function createUserFeedback() {
|
||||||
if (Attention has :vibrate && Settings.getVibrate()) {
|
if (Attention has :vibrate && Settings.getVibrate()) {
|
||||||
Attention.vibrate([new Attention.VibeProfile(25, 25)]);
|
Attention.vibrate([new Attention.VibeProfile(25, 25)]);
|
||||||
@ -200,6 +258,9 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
|||||||
mView.updatePinMask(mEnteredPin.length());
|
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() {
|
function resetTimer() {
|
||||||
var timeout = Settings.getConfirmTimeout(); // ms
|
var timeout = Settings.getConfirmTimeout(); // ms
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
@ -212,6 +273,8 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Cancel PIN entry.
|
||||||
|
//
|
||||||
function goBack() as Void {
|
function goBack() as Void {
|
||||||
if (mTimer != null) {
|
if (mTimer != null) {
|
||||||
mTimer.stop();
|
mTimer.stop();
|
||||||
@ -219,18 +282,20 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
|||||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Hepatic feedback for a wrong PIN and cancel entry.
|
||||||
|
//
|
||||||
function error() as Void {
|
function error() as Void {
|
||||||
// System.println("HomeAssistantPinConfirmationDelegate error() Wrong PIN entered");
|
// System.println("HomeAssistantPinConfirmationDelegate error() Wrong PIN entered");
|
||||||
mFailures.addFailure();
|
mFailures.addFailure();
|
||||||
if (Attention has :vibrate && Settings.getVibrate()) {
|
if (Attention has :vibrate && Settings.getVibrate()) {
|
||||||
Attention.vibrate([
|
Attention.vibrate([
|
||||||
new Attention.VibeProfile(100, 100),
|
new Attention.VibeProfile(100, 100),
|
||||||
new Attention.VibeProfile(0, 200),
|
new Attention.VibeProfile( 0, 200),
|
||||||
new Attention.VibeProfile(75, 100),
|
new Attention.VibeProfile( 75, 100),
|
||||||
new Attention.VibeProfile(0, 200),
|
new Attention.VibeProfile( 0, 200),
|
||||||
new Attention.VibeProfile(50, 100),
|
new Attention.VibeProfile( 50, 100),
|
||||||
new Attention.VibeProfile(0, 200),
|
new Attention.VibeProfile( 0, 200),
|
||||||
new Attention.VibeProfile(25, 100)
|
new Attention.VibeProfile( 25, 100)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (WatchUi has :showToast) {
|
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 {
|
class PinFailures {
|
||||||
|
|
||||||
const STORAGE_KEY_FAILURES as String = "pin_failures";
|
const STORAGE_KEY_FAILURES as Lang.String = "pin_failures";
|
||||||
const STORAGE_KEY_LOCKED as String = "pin_locked";
|
const STORAGE_KEY_LOCKED as Lang.String = "pin_locked";
|
||||||
|
|
||||||
private var mFailures as Array<Number>;
|
private var mFailures as Lang.Array<Lang.Number>;
|
||||||
private var mLockedUntil as Number or Null;
|
private var mLockedUntil as Lang.Number or Null;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
// System.println("PinFailures initialize() Initializing PIN failures from storage");
|
// System.println("PinFailures initialize() Initializing PIN failures from storage");
|
||||||
var failures = Application.Storage.getValue(PinFailures.STORAGE_KEY_FAILURES);
|
var failures = Application.Storage.getValue(PinFailures.STORAGE_KEY_FAILURES);
|
||||||
@ -256,6 +326,8 @@ class PinFailures {
|
|||||||
mLockedUntil = Application.Storage.getValue(PinFailures.STORAGE_KEY_LOCKED);
|
mLockedUntil = Application.Storage.getValue(PinFailures.STORAGE_KEY_LOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Record a PIN entry failure. If too many have occurred lock the application.
|
||||||
|
//
|
||||||
function addFailure() {
|
function addFailure() {
|
||||||
mFailures.add(Time.now().value());
|
mFailures.add(Time.now().value());
|
||||||
// System.println("PinFailures addFailure() " + mFailures.size() + " PIN confirmation failures recorded");
|
// System.println("PinFailures addFailure() " + mFailures.size() + " PIN confirmation failures recorded");
|
||||||
@ -268,7 +340,7 @@ class PinFailures {
|
|||||||
mFailures = mFailures.slice(1, null);
|
mFailures = mFailures.slice(1, null);
|
||||||
} else {
|
} else {
|
||||||
mFailures = [];
|
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);
|
Application.Storage.setValue(STORAGE_KEY_LOCKED, mLockedUntil);
|
||||||
// System.println("PinFailures addFailure() Locked until " + mLockedUntil);
|
// System.println("PinFailures addFailure() Locked until " + mLockedUntil);
|
||||||
}
|
}
|
||||||
@ -276,6 +348,9 @@ class PinFailures {
|
|||||||
Application.Storage.setValue(STORAGE_KEY_FAILURES, mFailures);
|
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() {
|
function reset() {
|
||||||
// System.println("PinFailures reset() Resetting failures");
|
// System.println("PinFailures reset() Resetting failures");
|
||||||
mFailures = [];
|
mFailures = [];
|
||||||
@ -284,11 +359,18 @@ class PinFailures {
|
|||||||
Application.Storage.deleteValue(STORAGE_KEY_LOCKED);
|
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();
|
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) {
|
if (mLockedUntil == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,6 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth, 19 November 2023
|
// P A Abbey & J D Abbey & Someone0nEarth, 19 November 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// Calling a Home Assistant Service.
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Toybox.Lang;
|
using Toybox.Lang;
|
||||||
@ -23,10 +18,14 @@ using Toybox.WatchUi;
|
|||||||
using Toybox.Graphics;
|
using Toybox.Graphics;
|
||||||
using Toybox.Application.Properties;
|
using Toybox.Application.Properties;
|
||||||
|
|
||||||
|
//! Calling a Home Assistant Service.
|
||||||
|
//
|
||||||
class HomeAssistantService {
|
class HomeAssistantService {
|
||||||
private var mHasToast as Lang.Boolean = false;
|
private var mHasToast as Lang.Boolean = false;
|
||||||
private var mHasVibrate as Lang.Boolean = false;
|
private var mHasVibrate as Lang.Boolean = false;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
if (WatchUi has :showToast) {
|
if (WatchUi has :showToast) {
|
||||||
mHasToast = true;
|
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(
|
function onReturnCall(
|
||||||
responseCode as Lang.Number,
|
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(
|
function call(
|
||||||
service as Lang.String,
|
service as Lang.String,
|
||||||
data as Lang.Dictionary or Null
|
data as Lang.Dictionary or Null
|
||||||
|
@ -11,17 +11,14 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// Menu button that triggers a service.
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Toybox.Lang;
|
using Toybox.Lang;
|
||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
using Toybox.Graphics;
|
using Toybox.Graphics;
|
||||||
|
|
||||||
|
//! Menu button that triggers a service.
|
||||||
|
//
|
||||||
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||||
private var mHomeAssistantService as HomeAssistantService;
|
private var mHomeAssistantService as HomeAssistantService;
|
||||||
private var mService as Lang.String or Null;
|
private var mService as Lang.String or Null;
|
||||||
@ -29,6 +26,19 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
|||||||
private var mPin as Lang.Boolean;
|
private var mPin as Lang.Boolean;
|
||||||
private var mData as Lang.Dictionary or Null;
|
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(
|
function initialize(
|
||||||
label as Lang.String or Lang.Symbol,
|
label as Lang.String or Lang.Symbol,
|
||||||
template as Lang.String,
|
template as Lang.String,
|
||||||
@ -62,6 +72,8 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
|||||||
mData = data;
|
mData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
|
||||||
|
//
|
||||||
function callService() as Void {
|
function callService() as Void {
|
||||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||||
if (mPin && hasTouchScreen) {
|
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 {
|
function onConfirm(b as Lang.Boolean) as Void {
|
||||||
if (mService != null) {
|
if (mService != null) {
|
||||||
mHomeAssistantService.call(mService, mData);
|
mHomeAssistantService.call(mService, mData);
|
||||||
|
@ -11,11 +11,6 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
// 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;
|
using Toybox.Lang;
|
||||||
@ -24,6 +19,8 @@ using Toybox.Graphics;
|
|||||||
using Toybox.Application.Properties;
|
using Toybox.Application.Properties;
|
||||||
using Toybox.Timer;
|
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 {
|
class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||||
private var mConfirm as Lang.Boolean;
|
private var mConfirm as Lang.Boolean;
|
||||||
private var mPin 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 mTemplate as Lang.String;
|
||||||
private var mHasVibrate as Lang.Boolean = false;
|
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(
|
function initialize(
|
||||||
label as Lang.String or Lang.Symbol,
|
label as Lang.String or Lang.Symbol,
|
||||||
template as Lang.String,
|
template as Lang.String,
|
||||||
@ -58,6 +64,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
mTemplate = template;
|
mTemplate = template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Set the state of a toggle menu item.
|
||||||
|
//
|
||||||
private function setUiToggle(state as Null or Lang.String) as Void {
|
private function setUiToggle(state as Null or Lang.String) as Void {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
if (state.equals("on") && !isEnabled()) {
|
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;
|
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") + "')}}";
|
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 {
|
function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
setSubLabel(null);
|
setSubLabel(null);
|
||||||
@ -100,6 +121,10 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
WatchUi.requestUpdate();
|
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 {
|
function updateToggleState(data as Lang.String or Lang.Dictionary or Null) as Void {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
setUiToggle("off");
|
setUiToggle("off");
|
||||||
@ -126,9 +151,15 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
WatchUi.requestUpdate();
|
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 Code: " + responseCode);
|
||||||
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Data: " + data);
|
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Data: " + data);
|
||||||
|
|
||||||
@ -183,6 +214,10 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
|||||||
getApp().setApiStatus(status);
|
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 {
|
function setState(s as Lang.Boolean) as Void {
|
||||||
// Toggle the UI back, we'll wait for confirmation from the Home Assistant
|
// Toggle the UI back, we'll wait for confirmation from the Home Assistant
|
||||||
setEnabled(!isEnabled());
|
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 {
|
function callService(b as Lang.Boolean) as Void {
|
||||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||||
if (mPin && hasTouchScreen) {
|
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 {
|
function onConfirm(b as Lang.Boolean) as Void {
|
||||||
setState(b);
|
setState(b);
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,6 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
// Home Assistant menu construction.
|
|
||||||
//
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
using Toybox.Application;
|
using Toybox.Application;
|
||||||
@ -24,8 +19,12 @@ using Toybox.Graphics;
|
|||||||
using Toybox.System;
|
using Toybox.System;
|
||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
|
|
||||||
|
//! Home Assistant menu construction.
|
||||||
|
//
|
||||||
class HomeAssistantView extends WatchUi.Menu2 {
|
class HomeAssistantView extends WatchUi.Menu2 {
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize(
|
function initialize(
|
||||||
definition as Lang.Dictionary,
|
definition as Lang.Dictionary,
|
||||||
options as {
|
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> {
|
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or Null> {
|
||||||
var fullList = [];
|
var fullList = [];
|
||||||
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
|
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
|
||||||
@ -102,26 +106,35 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
|||||||
return fullList;
|
return fullList;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when this View is brought to the foreground. Restore
|
//! Called when this View is brought to the foreground. Restore
|
||||||
// the state of this View and prepare it to be shown. This includes
|
//! the state of this View and prepare it to be shown. This includes
|
||||||
// loading resources into memory.
|
//! loading resources into memory.
|
||||||
function onShow() as Void {}
|
function onShow() as Void {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//! Delegate for the HomeAssistantView.
|
||||||
// Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/
|
//!
|
||||||
|
//! Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/
|
||||||
//
|
//
|
||||||
class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
||||||
private var mIsRootMenuView as Lang.Boolean = false;
|
private var mIsRootMenuView as Lang.Boolean = false;
|
||||||
private var mTimer as QuitTimer;
|
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) {
|
function initialize(isRootMenuView as Lang.Boolean) {
|
||||||
Menu2InputDelegate.initialize();
|
Menu2InputDelegate.initialize();
|
||||||
mIsRootMenuView = isRootMenuView;
|
mIsRootMenuView = isRootMenuView;
|
||||||
mTimer = getApp().getQuitTimer();
|
mTimer = getApp().getQuitTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Back button event
|
||||||
|
//
|
||||||
function onBack() {
|
function onBack() {
|
||||||
mTimer.reset();
|
mTimer.reset();
|
||||||
|
|
||||||
@ -135,16 +148,22 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
|||||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only for CheckboxMenu
|
//! Only for CheckboxMenu
|
||||||
|
//
|
||||||
function onDone() {
|
function onDone() {
|
||||||
mTimer.reset();
|
mTimer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only for CustomMenu
|
//! Only for CustomMenu
|
||||||
|
//
|
||||||
function onFooter() {
|
function onFooter() {
|
||||||
mTimer.reset();
|
mTimer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Select event
|
||||||
|
//!
|
||||||
|
//! @param item Selected menu item.
|
||||||
|
//
|
||||||
function onSelect(item as WatchUi.MenuItem) as Void {
|
function onSelect(item as WatchUi.MenuItem) as Void {
|
||||||
mTimer.reset();
|
mTimer.reset();
|
||||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||||
@ -164,7 +183,8 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only for CustomMenu
|
//! Only for CustomMenu
|
||||||
|
//
|
||||||
function onTitle() {
|
function onTitle() {
|
||||||
mTimer.reset();
|
mTimer.reset();
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,6 @@
|
|||||||
//
|
//
|
||||||
// J D Abbey & P A Abbey, 28 December 2022
|
// 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;
|
using Toybox.Lang;
|
||||||
@ -23,18 +18,27 @@ using Toybox.Timer;
|
|||||||
using Toybox.Application.Properties;
|
using Toybox.Application.Properties;
|
||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
|
|
||||||
|
//! Quit the application after a period of inactivity in order to save the battery.
|
||||||
|
//!
|
||||||
class QuitTimer extends Timer.Timer {
|
class QuitTimer extends Timer.Timer {
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
Timer.Timer.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 {
|
function exitApp() as Void {
|
||||||
// System.println("QuitTimer exitApp(): Exiting");
|
// System.println("QuitTimer exitApp(): Exiting");
|
||||||
// This will exit the system cleanly from any point within an app.
|
// This will exit the system cleanly from any point within an app.
|
||||||
System.exit();
|
System.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Kick off the quit timer.
|
||||||
|
//
|
||||||
function begin() {
|
function begin() {
|
||||||
var api_timeout = Settings.getAppTimeout(); // ms
|
var api_timeout = Settings.getAppTimeout(); // ms
|
||||||
if (api_timeout > 0) {
|
if (api_timeout > 0) {
|
||||||
@ -42,6 +46,8 @@ class QuitTimer extends Timer.Timer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Reset the quit timer.
|
||||||
|
//
|
||||||
function reset() {
|
function reset() {
|
||||||
// System.println("QuitTimer reset(): Restarted quit timer");
|
// System.println("QuitTimer reset(): Restarted quit timer");
|
||||||
stop();
|
stop();
|
||||||
|
@ -11,35 +11,36 @@
|
|||||||
//
|
//
|
||||||
// J D Abbey & P A Abbey, 28 December 2022
|
// 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.Lang;
|
||||||
using Toybox.WatchUi;
|
using Toybox.WatchUi;
|
||||||
using Toybox.Math;
|
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 {
|
class ScalableView extends WatchUi.View {
|
||||||
|
//! Retain the local screen width for efficiency
|
||||||
private var mScreenWidth;
|
private var mScreenWidth;
|
||||||
|
|
||||||
|
//! Class Constructor
|
||||||
|
//
|
||||||
function initialize() {
|
function initialize() {
|
||||||
View.initialize();
|
View.initialize();
|
||||||
mScreenWidth = System.getDeviceSettings().screenWidth;
|
mScreenWidth = System.getDeviceSettings().screenWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a fraction expressed as a percentage (%) to a number of pixels for the
|
//! Convert a fraction expressed as a percentage (%) to a number of pixels for the
|
||||||
// screen's dimensions.
|
//! screen's dimensions.
|
||||||
//
|
//!
|
||||||
// Parameters:
|
//! Uses screen width rather than screen height as rectangular screens tend to have
|
||||||
// * dc - Device context
|
//! height > width.
|
||||||
// * pc - Percentage (%) expressed as a number in the range 0.0..100.0
|
//!
|
||||||
//
|
//! @param 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.
|
//! @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 {
|
function pixelsForScreen(pc as Lang.Float) as Lang.Number {
|
||||||
return Math.round(pc * mScreenWidth) / 100;
|
return Math.round(pc * mScreenWidth) / 100;
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,6 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey, SomeoneOnEarth & moesterheld, 23 November 2023
|
// 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;
|
using Toybox.Lang;
|
||||||
@ -31,6 +21,11 @@ using Toybox.System;
|
|||||||
using Toybox.Background;
|
using Toybox.Background;
|
||||||
using Toybox.Time;
|
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)
|
(:glance, :background)
|
||||||
class Settings {
|
class Settings {
|
||||||
private static var mApiKey as Lang.String = "";
|
private static var mApiKey as Lang.String = "";
|
||||||
@ -40,19 +35,24 @@ class Settings {
|
|||||||
private static var mCacheConfig as Lang.Boolean = false;
|
private static var mCacheConfig as Lang.Boolean = false;
|
||||||
private static var mClearCache as Lang.Boolean = false;
|
private static var mClearCache as Lang.Boolean = false;
|
||||||
private static var mVibrate as Lang.Boolean = false;
|
private static var mVibrate as Lang.Boolean = false;
|
||||||
private static var mAppTimeout as Lang.Number = 0; // seconds
|
//! seconds
|
||||||
private static var mPollDelay as Lang.Number = 0; // seconds
|
private static var mAppTimeout as Lang.Number = 0;
|
||||||
private static var mConfirmTimeout as Lang.Number = 3; // seconds
|
//! 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 mPin as Lang.String or Null = "0000";
|
||||||
private static var mMenuAlignment as Lang.Number = WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_LEFT;
|
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 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 mIsApp as Lang.Boolean = false;
|
||||||
private static var mHasService 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;
|
private static var mWebhookManager as WebhookManager or Null;
|
||||||
|
|
||||||
// Called on application start and then whenever the settings are changed.
|
//! Called on application start and then whenever the settings are changed.
|
||||||
|
//
|
||||||
static function update() {
|
static function update() {
|
||||||
mIsApp = getApp().getIsApp();
|
mIsApp = getApp().getIsApp();
|
||||||
mApiKey = Properties.getValue("api_key");
|
mApiKey = Properties.getValue("api_key");
|
||||||
@ -71,6 +71,8 @@ class Settings {
|
|||||||
mBatteryRefreshRate = Properties.getValue("battery_level_refresh_rate");
|
mBatteryRefreshRate = Properties.getValue("battery_level_refresh_rate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! A webhook is required for non-privileged API calls.
|
||||||
|
//
|
||||||
static function webhook() {
|
static function webhook() {
|
||||||
if (System has :ServiceDelegate) {
|
if (System has :ServiceDelegate) {
|
||||||
mHasService = true;
|
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 {
|
static function getApiKey() as Lang.String {
|
||||||
return mApiKey;
|
return mApiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Get the Webhook ID supplied as part of the Settings.
|
||||||
|
//!
|
||||||
|
//! @return The Webhook ID
|
||||||
|
//
|
||||||
static function getWebhookId() as Lang.String {
|
static function getWebhookId() as Lang.String {
|
||||||
return mWebhookId;
|
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) {
|
static function setWebhookId(webhookId as Lang.String) {
|
||||||
mWebhookId = webhookId;
|
mWebhookId = webhookId;
|
||||||
Properties.setValue("webhook_id", mWebhookId);
|
Properties.setValue("webhook_id", mWebhookId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Delete the Webhook ID saved as part of the Settings.
|
||||||
|
//
|
||||||
static function unsetWebhookId() {
|
static function unsetWebhookId() {
|
||||||
mWebhookId = "";
|
mWebhookId = "";
|
||||||
Properties.setValue("webhook_id", 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 {
|
static function getApiUrl() as Lang.String {
|
||||||
return mApiUrl;
|
return mApiUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Get the menu configuration URL supplied as part of the Settings.
|
||||||
|
//!
|
||||||
|
//! @return The menu configuration URL
|
||||||
|
//
|
||||||
static function getConfigUrl() as Lang.String {
|
static function getConfigUrl() as Lang.String {
|
||||||
return mConfigUrl;
|
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 {
|
static function getCacheConfig() as Lang.Boolean {
|
||||||
return mCacheConfig;
|
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 {
|
static function getClearCache() as Lang.Boolean {
|
||||||
return mClearCache;
|
return mClearCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Unset the clear cache Boolean option supplied as part of the Settings.
|
||||||
|
//
|
||||||
static function unsetClearCache() {
|
static function unsetClearCache() {
|
||||||
mClearCache = false;
|
mClearCache = false;
|
||||||
Properties.setValue("clear_cache", mClearCache);
|
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 {
|
static function getVibrate() as Lang.Boolean {
|
||||||
return mVibrate;
|
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 {
|
static function getAppTimeout() as Lang.Number {
|
||||||
return mAppTimeout * 1000; // Convert to milliseconds
|
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 {
|
static function getPollDelay() as Lang.Number {
|
||||||
return mPollDelay * 1000; // Convert to milliseconds
|
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 {
|
static function getConfirmTimeout() as Lang.Number {
|
||||||
return mConfirmTimeout * 1000; // Convert to milliseconds
|
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 {
|
static function getPin() as Lang.String or Null {
|
||||||
return mPin;
|
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 {
|
private static function validatePin() as Lang.String or Null {
|
||||||
var pin = Properties.getValue("pin");
|
var pin = Properties.getValue("pin");
|
||||||
if (pin.toNumber() == null || pin.length() != 4) {
|
if (pin.toNumber() == null || pin.length() != 4) {
|
||||||
@ -183,14 +243,24 @@ class Settings {
|
|||||||
return pin;
|
return pin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Get the menu item alignment as part of the Settings.
|
||||||
|
//!
|
||||||
|
//! @return The menu item alignment.
|
||||||
|
//
|
||||||
static function getMenuAlignment() as Lang.Number {
|
static function getMenuAlignment() as Lang.Number {
|
||||||
return mMenuAlignment; // Either WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_RIGHT or WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_LEFT
|
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 {
|
static function isSensorsLevelEnabled() as Lang.Boolean {
|
||||||
return mIsSensorsLevelEnabled;
|
return mIsSensorsLevelEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Disable logging of the watch's sensors.
|
||||||
|
//
|
||||||
static function unsetIsSensorsLevelEnabled() {
|
static function unsetIsSensorsLevelEnabled() {
|
||||||
mIsSensorsLevelEnabled = false;
|
mIsSensorsLevelEnabled = false;
|
||||||
Properties.setValue("enable_battery_level", mIsSensorsLevelEnabled);
|
Properties.setValue("enable_battery_level", mIsSensorsLevelEnabled);
|
||||||
|
123
source/WebLog.mc
123
source/WebLog.mc
@ -11,88 +11,99 @@
|
|||||||
//
|
//
|
||||||
// J D Abbey & P A Abbey, 28 December 2022
|
// 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.Communications;
|
||||||
using Toybox.Lang;
|
using Toybox.Lang;
|
||||||
using Toybox.System;
|
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>
|
||||||
|
//! <?php
|
||||||
|
//! $myfile = fopen("log", "a");
|
||||||
|
//! $queries = array();
|
||||||
|
//! parse_str($_SERVER['QUERY_STRING'], $queries);
|
||||||
|
//! fwrite($myfile, $queries['log']);
|
||||||
|
//! print "Success";
|
||||||
|
//! ?>
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! Logs published to https://domain.name/path/log.
|
||||||
|
//!
|
||||||
|
//! File: https://domain.name/path/log_clear.php
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! <?php
|
||||||
|
//! $myfile = fopen("log", "w");
|
||||||
|
//! fwrite($myfile, "");
|
||||||
|
//! print "Success";
|
||||||
|
//! ?>
|
||||||
|
//! </pre>
|
||||||
|
//
|
||||||
(:glance, :background)
|
(:glance, :background)
|
||||||
class WebLog {
|
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 numCalls = 0 as Lang.Number;
|
||||||
private var buffer = "" as Lang.String;
|
private var buffer = "" as Lang.String;
|
||||||
|
|
||||||
// Set the number of calls to print() before sending the buffer to the online
|
//! Set the number of calls to print() before sending the buffer to the online
|
||||||
// logger.
|
//! logger.
|
||||||
|
//!
|
||||||
|
//! @param l The number of log calls to buffer before writing to the online service.
|
||||||
//
|
//
|
||||||
function setCallsBuffer(l as Lang.Number) {
|
function setCallsBuffer(l as Lang.Number) {
|
||||||
callsbuffer = l;
|
callsBuffer = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the number of calls to print() before sending the buffer to the online
|
//! Get the number of calls to print() before sending the buffer to the online
|
||||||
// logger.
|
//! logger.
|
||||||
|
//!
|
||||||
|
//! @return The number of log calls to buffer before writing to the online service.
|
||||||
//
|
//
|
||||||
function getCallsBuffer() as Lang.Number {
|
function getCallsBuffer() as Lang.Number {
|
||||||
return callsbuffer;
|
return callsBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a debug log over the Internet to keep track of the watch's runtime
|
//! Create a debug log over the Internet to keep track of the watch's runtime
|
||||||
// execution.
|
//! execution.
|
||||||
|
//!
|
||||||
|
//! @param str The string to log.
|
||||||
//
|
//
|
||||||
function print(str as Lang.String) {
|
function print(str as Lang.String) {
|
||||||
var myTime = System.getClockTime();
|
var myTime = System.getClockTime();
|
||||||
buffer += myTime.hour.format("%02d") + ":" + myTime.min.format("%02d") + ":" + myTime.sec.format("%02d") + " " + str;
|
buffer += myTime.hour.format("%02d") + ":" + myTime.min.format("%02d") + ":" + myTime.sec.format("%02d") + " " + str;
|
||||||
numCalls++;
|
numCalls++;
|
||||||
// System.println("WebLog print() str = " + str);
|
// System.println("WebLog print() str = " + str);
|
||||||
if (numCalls >= callsbuffer) {
|
if (numCalls >= callsBuffer) {
|
||||||
doPrint();
|
doPrint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a debug log over the Internet to keep track of the watch's runtime
|
//! Create a debug log over the Internet to keep track of the watch's runtime
|
||||||
// execution. Add a new line character to the end.
|
//! execution. Add a new line character to the end.
|
||||||
|
//!
|
||||||
|
//! @param str The string to log.
|
||||||
//
|
//
|
||||||
function println(str as Lang.String) {
|
function println(str as Lang.String) {
|
||||||
print(str + "\n");
|
print(str + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush the current buffer to the online logger even if it has not reach the
|
//! Flush the current buffer to the online logger even if it has not reach the
|
||||||
// submission level set by 'callsbuffer'.
|
//! submission level set by 'callsBuffer'.
|
||||||
//
|
//
|
||||||
function flush() {
|
function flush() {
|
||||||
// System.println("WebLog 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() {
|
function doPrint() {
|
||||||
// System.println("WebLog doPrint()");
|
// System.println("WebLog doPrint()");
|
||||||
@ -122,8 +133,8 @@ class WebLog {
|
|||||||
buffer = "";
|
buffer = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the debug log over the Internet to start a new track of the watch's runtime
|
//! Clear the debug log over the Internet to start a new track of the watch's runtime
|
||||||
// execution.
|
//! execution.
|
||||||
//
|
//
|
||||||
function clear() {
|
function clear() {
|
||||||
// System.println("WebLog clear()");
|
// System.println("WebLog clear()");
|
||||||
@ -143,7 +154,10 @@ class WebLog {
|
|||||||
buffer = "";
|
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 {
|
function onLog(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||||
// if (responseCode != 200) {
|
// 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 {
|
function onClear(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||||
// if (responseCode != 200) {
|
// if (responseCode != 200) {
|
||||||
|
@ -11,14 +11,6 @@
|
|||||||
//
|
//
|
||||||
// P A Abbey & J D Abbey, 10 January 2024
|
// 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;
|
using Toybox.Lang;
|
||||||
@ -26,10 +18,24 @@ using Toybox.Communications;
|
|||||||
using Toybox.System;
|
using Toybox.System;
|
||||||
using Toybox.WatchUi;
|
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 {
|
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) {
|
switch (responseCode) {
|
||||||
case Communications.BLE_HOST_TIMEOUT:
|
case Communications.BLE_HOST_TIMEOUT:
|
||||||
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
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() {
|
function requestWebhookId() {
|
||||||
var deviceSettings = System.getDeviceSettings();
|
var deviceSettings = System.getDeviceSettings();
|
||||||
// System.println("WebhookManager requestWebhookId(): Requesting webhook id for device = " + deviceSettings.uniqueIdentifier);
|
// 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) {
|
switch (responseCode) {
|
||||||
case Communications.BLE_HOST_TIMEOUT:
|
case Communications.BLE_HOST_TIMEOUT:
|
||||||
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
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();
|
var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId();
|
||||||
// System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
|
// System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
|
||||||
// System.println("WebhookManager registerWebhookSensor(): URL=" + url);
|
// System.println("WebhookManager registerWebhookSensor(): URL=" + url);
|
||||||
@ -217,6 +240,8 @@ class WebhookManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Request the creation of all the supported watch sensors on the Home Assistant instance.
|
||||||
|
//
|
||||||
function registerWebhookSensors() {
|
function registerWebhookSensors() {
|
||||||
var heartRate = Activity.getActivityInfo().currentHeartRate;
|
var heartRate = Activity.getActivityInfo().currentHeartRate;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user