mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-07-10 06:48:31 +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": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
"settings": {
|
||||
"cSpell.words": [
|
||||
"Initialiser"
|
||||
]
|
||||
}
|
||||
}
|
@ -11,15 +11,6 @@
|
||||
//
|
||||
// J D Abbey & P A Abbey, 28 December 2022
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Alert provides a means to present application notifications to the user
|
||||
// briefly. Credit to travis.vitek on forums.garmin.com.
|
||||
//
|
||||
// Reference:
|
||||
// * https://forums.garmin.com/developer/connect-iq/f/discussion/106/how-to-show-alert-messages
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
@ -27,6 +18,12 @@ using Toybox.Graphics;
|
||||
using Toybox.WatchUi;
|
||||
using Toybox.Timer;
|
||||
|
||||
//! The Alert class provides a means to present application notifications to the user
|
||||
//! briefly. Credit to travis.vitek on forums.garmin.com.
|
||||
//!
|
||||
//! Reference:
|
||||
//! @url https://forums.garmin.com/developer/connect-iq/f/discussion/106/how-to-show-alert-messages
|
||||
//
|
||||
class Alert extends WatchUi.View {
|
||||
private static const scRadius = 10;
|
||||
private var mTimer as Timer.Timer;
|
||||
@ -36,6 +33,16 @@ class Alert extends WatchUi.View {
|
||||
private var mFgcolor as Graphics.ColorType;
|
||||
private var mBgcolor as Graphics.ColorType;
|
||||
|
||||
//! Class Constructor
|
||||
//! @param params A dictionary object as follows:<br>
|
||||
//! {<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) {
|
||||
View.initialize();
|
||||
|
||||
@ -67,14 +74,22 @@ class Alert extends WatchUi.View {
|
||||
mTimer = new Timer.Timer();
|
||||
}
|
||||
|
||||
//! Setup a timer to dismiss the alert.
|
||||
//
|
||||
function onShow() {
|
||||
mTimer.start(method(:dismiss), mTimeout, false);
|
||||
}
|
||||
|
||||
//! Prematurely stop the timer.
|
||||
//
|
||||
function onHide() {
|
||||
mTimer.stop();
|
||||
}
|
||||
|
||||
//! Draw the Alert view.
|
||||
//!
|
||||
//! @param dc Device context
|
||||
//
|
||||
function onUpdate(dc as Graphics.Dc) {
|
||||
var tWidth = dc.getTextWidthInPixels(mText, mFont);
|
||||
var tHeight = dc.getFontHeight(mFont);
|
||||
@ -110,32 +125,49 @@ class Alert extends WatchUi.View {
|
||||
dc.drawText(tX, tY, mFont, mText, Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
|
||||
}
|
||||
|
||||
// Remove the alert from view, usually on user input, but that is defined by the calling function.
|
||||
//! Remove the alert from view, usually on user input, but that is defined by the calling function.
|
||||
//
|
||||
function dismiss() as Void {
|
||||
WatchUi.popView(SLIDE_IMMEDIATE);
|
||||
}
|
||||
|
||||
function pushView(transition) as Void {
|
||||
//! Push this view onto the view stack.
|
||||
//!
|
||||
//! @param transition Slide Type
|
||||
function pushView(transition as WatchUi.SlideType) as Void {
|
||||
WatchUi.pushView(self, new AlertDelegate(self), transition);
|
||||
}
|
||||
}
|
||||
|
||||
//! Input Delegate for the Alert view.
|
||||
//
|
||||
class AlertDelegate extends WatchUi.InputDelegate {
|
||||
private var mView;
|
||||
private var mView as Alert;
|
||||
|
||||
function initialize(view) {
|
||||
//! Class Constructor
|
||||
//!
|
||||
//! @param view The Alert view for which this class is a delegate.
|
||||
//!
|
||||
function initialize(view as Alert) {
|
||||
InputDelegate.initialize();
|
||||
mView = view;
|
||||
}
|
||||
|
||||
function onKey(evt) as Lang.Boolean {
|
||||
//! Handle key events.
|
||||
//!
|
||||
//! @param evt The key event whose value is ignored, just fact of key event matters.
|
||||
//!
|
||||
function onKey(evt as WatchUi.KeyEvent) as Lang.Boolean {
|
||||
mView.dismiss();
|
||||
getApp().getQuitTimer().reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
function onTap(evt) as Lang.Boolean {
|
||||
//! Handle click events.
|
||||
//!
|
||||
//! @param evt The click event whose value is ignored, just fact of key event matters.
|
||||
//!
|
||||
function onTap(evt as WatchUi.ClickEvent) as Lang.Boolean {
|
||||
mView.dismiss();
|
||||
getApp().getQuitTimer().reset();
|
||||
return true;
|
||||
|
@ -11,12 +11,6 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// The background service delegate currently just reports the Garmin watch's battery
|
||||
// level.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
@ -25,20 +19,43 @@ using Toybox.Background;
|
||||
using Toybox.System;
|
||||
using Toybox.Activity;
|
||||
|
||||
//! The background service delegate reports the Garmin watch's various status values
|
||||
//! back to the Home Assistant instance.
|
||||
//
|
||||
(:background)
|
||||
class BackgroundServiceDelegate extends System.ServiceDelegate {
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
ServiceDelegate.initialize();
|
||||
}
|
||||
|
||||
function onReturnBatteryUpdate(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||
// System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Code: " + responseCode);
|
||||
// System.println("BackgroundServiceDelegate onReturnBatteryUpdate() Response Data: " + data);
|
||||
//! Callback function for doUpdate().
|
||||
//!
|
||||
//! @param responseCode Response code
|
||||
//! @param data Return data
|
||||
//
|
||||
function onReturnDoUpdate(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||
// System.println("BackgroundServiceDelegate onReturnDoUpdate() Response Code: " + responseCode);
|
||||
// System.println("BackgroundServiceDelegate onReturnDoUpdate() Response Data: " + data);
|
||||
Background.exit(null);
|
||||
}
|
||||
|
||||
function onActivityCompleted(activity as { :sport as Activity.Sport, :subSport as Activity.SubSport }) as Void {
|
||||
//! Called on completion of an activity.
|
||||
//!
|
||||
//! @param activity Specified as a Dictionary with two items.<br>
|
||||
//! {<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) {
|
||||
// System.println("BackgroundServiceDelegate onActivityCompleted(): No Phone connection, skipping API call.");
|
||||
} else if (!System.getDeviceSettings().connectionAvailable) {
|
||||
@ -50,6 +67,8 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
//! Called periodically to send status updates to the Home Assistant instance.
|
||||
//
|
||||
function onTemporalEvent() as Void {
|
||||
if (!System.getDeviceSettings().phoneConnected) {
|
||||
// System.println("BackgroundServiceDelegate onTemporalEvent(): No Phone connection, skipping API call.");
|
||||
@ -76,7 +95,15 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private function doUpdate(activity as Lang.Number or Null, sub_activity as Lang.Number or Null) {
|
||||
//! Combined update function to collect the data to be sent as updates to the Home Assistant instance.
|
||||
//!
|
||||
//! @param activity Activity.Sport
|
||||
//! @param sub_activity Activity.SubSport
|
||||
//
|
||||
private function doUpdate(
|
||||
activity as Lang.Number or Null,
|
||||
sub_activity as Lang.Number or Null
|
||||
) {
|
||||
// System.println("BackgroundServiceDelegate onTemporalEvent(): Making API call.");
|
||||
var position = Position.getInfo();
|
||||
// System.println("BackgroundServiceDelegate onTemporalEvent(): GPS : " + position.position.toDegrees());
|
||||
@ -136,7 +163,7 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
|
||||
},
|
||||
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
||||
},
|
||||
method(:onReturnBatteryUpdate)
|
||||
method(:onReturnDoUpdate)
|
||||
);
|
||||
}
|
||||
var activityInfo = ActivityMonitor.getInfo();
|
||||
@ -222,7 +249,7 @@ class BackgroundServiceDelegate extends System.ServiceDelegate {
|
||||
},
|
||||
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
|
||||
},
|
||||
method(:onReturnBatteryUpdate)
|
||||
method(:onReturnDoUpdate)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -11,15 +11,12 @@
|
||||
//
|
||||
// J D Abbey & P A Abbey, 28 December 2022
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// ClientId is somewhere to store personal credentials that should not be shared in
|
||||
// a separate file that is locally customised to the source code and not commited
|
||||
// back to GitHub.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
//! ClientId is somewhere to store personal credentials that should not be shared in
|
||||
//! a separate file that is locally customised to the source code and not committed
|
||||
//! back to GitHub.
|
||||
//
|
||||
(:glance)
|
||||
class ClientId {
|
||||
static const webLogUrl = "https://...";
|
||||
|
@ -11,22 +11,6 @@
|
||||
//
|
||||
// J D Abbey & P A Abbey, 28 December 2022
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// ErrorView provides a means to present application errors to the user. These
|
||||
// should not happen of course... but they do, so best make sure errors can be
|
||||
// reported.
|
||||
//
|
||||
// Designed so that a single ErrorView is used for all errors and hence can ensure
|
||||
// that only the first call to display is honoured until the view is dismissed.
|
||||
// This compensates for older devices not being able to call WatchUi.getCurrentView()
|
||||
// due to not supporting API level 3.4.0.
|
||||
//
|
||||
// Usage:
|
||||
// 1) ErrorView.show("Error message");
|
||||
// 2) return ErrorView.create("Error message"); // as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Graphics;
|
||||
@ -35,6 +19,19 @@ using Toybox.WatchUi;
|
||||
using Toybox.Communications;
|
||||
using Toybox.Timer;
|
||||
|
||||
//! ErrorView provides a means to present application errors to the user. These
|
||||
//! should not happen of course... but they do, so best make sure errors can be
|
||||
//! reported.
|
||||
//!
|
||||
//! Designed so that a single ErrorView is used for all errors and hence can ensure
|
||||
//! that only the first call to display is honoured until the view is dismissed.
|
||||
//! This compensates for older devices not being able to call WatchUi.getCurrentView()
|
||||
//! due to not supporting API level 3.4.0.
|
||||
//!
|
||||
//! Usage:
|
||||
//! 1) `ErrorView.show("Error message");`
|
||||
//! 2) `return ErrorView.create("Error message"); // as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>`
|
||||
//
|
||||
class ErrorView extends ScalableView {
|
||||
private static const scErrorIconMargin as Lang.Float = 7f;
|
||||
private var mText as Lang.String = "";
|
||||
@ -48,9 +45,11 @@ class ErrorView extends ScalableView {
|
||||
private static var instance;
|
||||
private static var mShown as Lang.Boolean = false;
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
ScalableView.initialize();
|
||||
mDelegate = new ErrorDelegate(self);
|
||||
mDelegate = new ErrorDelegate();
|
||||
// Convert the settings from % of screen size to pixels
|
||||
mErrorIconMargin = pixelsForScreen(scErrorIconMargin);
|
||||
mErrorIcon = Application.loadResource(Rez.Drawables.ErrorIcon) as Graphics.BitmapResource;
|
||||
@ -59,7 +58,10 @@ class ErrorView extends ScalableView {
|
||||
}
|
||||
}
|
||||
|
||||
// Load your resources here
|
||||
//! Construct the view.
|
||||
//!
|
||||
//! @param dc Device context
|
||||
//
|
||||
function onLayout(dc as Graphics.Dc) as Void {
|
||||
var w = dc.getWidth();
|
||||
|
||||
@ -75,7 +77,10 @@ class ErrorView extends ScalableView {
|
||||
});
|
||||
}
|
||||
|
||||
// Update the view
|
||||
//! Update the view
|
||||
//!
|
||||
//! @param dc Device context
|
||||
//
|
||||
function onUpdate(dc as Graphics.Dc) as Void {
|
||||
var w = dc.getWidth();
|
||||
if (mAntiAlias) {
|
||||
@ -87,10 +92,18 @@ class ErrorView extends ScalableView {
|
||||
mTextArea.draw(dc);
|
||||
}
|
||||
|
||||
//! Get this view's delegate for processing events.
|
||||
//
|
||||
function getDelegate() as ErrorDelegate {
|
||||
return mDelegate;
|
||||
}
|
||||
|
||||
//! 'Create' (get) the ErrorView instance, intended to make short work of using this class. E.g.
|
||||
//!
|
||||
//! `return ErrorView.create("Went wrong!");`
|
||||
//!
|
||||
//! @param text The string to display in the ErrorView.
|
||||
//
|
||||
static function create(text as Lang.String) as [ WatchUi.Views ] or [ WatchUi.Views, WatchUi.InputDelegates ] {
|
||||
if (instance == null) {
|
||||
instance = new ErrorView();
|
||||
@ -102,7 +115,10 @@ class ErrorView extends ScalableView {
|
||||
return [instance, instance.getDelegate()];
|
||||
}
|
||||
|
||||
// Create or reuse an existing ErrorView, and pass on the text.
|
||||
//! Create or reuse an existing ErrorView, and pass on the text.
|
||||
//!
|
||||
//! @param text The string to display in the ErrorView.
|
||||
//
|
||||
static function show(text as Lang.String) as Void {
|
||||
if (!mShown) {
|
||||
create(text); // Ignore returned values
|
||||
@ -113,6 +129,8 @@ class ErrorView extends ScalableView {
|
||||
}
|
||||
}
|
||||
|
||||
//! Pop the view and clean up timers.
|
||||
//
|
||||
static function unShow() as Void {
|
||||
if (mShown) {
|
||||
WatchUi.popView(WatchUi.SLIDE_DOWN);
|
||||
@ -126,7 +144,10 @@ class ErrorView extends ScalableView {
|
||||
}
|
||||
}
|
||||
|
||||
// Internal show now we're not a static method like 'show()'.
|
||||
//! Internal show now we're not a static method like 'show()'.
|
||||
//!
|
||||
//! @param text Change the string tio display in the ErrorView.
|
||||
//
|
||||
function setText(text as Lang.String) as Void {
|
||||
mText = text;
|
||||
if (mTextArea != null) {
|
||||
@ -137,12 +158,19 @@ class ErrorView extends ScalableView {
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! Delegate for the ErrorView.
|
||||
//
|
||||
class ErrorDelegate extends WatchUi.BehaviorDelegate {
|
||||
|
||||
function initialize(view as ErrorView) {
|
||||
//! Class Constructor
|
||||
//!
|
||||
function initialize() {
|
||||
WatchUi.BehaviorDelegate.initialize();
|
||||
}
|
||||
|
||||
//! Process the event to clear the ErrorView.
|
||||
//
|
||||
function onBack() as Lang.Boolean {
|
||||
getApp().getQuitTimer().reset();
|
||||
ErrorView.unShow();
|
||||
|
@ -11,30 +11,38 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Home Assistant centralised constants.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
|
||||
//! Home Assistant centralised constants.
|
||||
//
|
||||
(:glance)
|
||||
class Globals {
|
||||
//! Alert is a toast at the top of the watch screen, it stays present until tapped
|
||||
//! or this timeout has expired.
|
||||
static const scAlertTimeout = 2000; // ms
|
||||
static const scTapTimeout = 1000; // ms
|
||||
// Time to let the existing HTTP responses get serviced after a
|
||||
// Communications.NETWORK_RESPONSE_OUT_OF_MEMORY response code.
|
||||
|
||||
//! Time to let the existing HTTP responses get serviced after a
|
||||
//! `Communications.NETWORK_RESPONSE_OUT_OF_MEMORY` response code.
|
||||
static const scApiBackoff = 1000; // ms
|
||||
// Needs to be long enough to enable a "double ESC" to quit the application from
|
||||
// an ErrorView.
|
||||
|
||||
//! Needs to be long enough to enable a "double ESC" to quit the application from
|
||||
//! an ErrorView.
|
||||
static const scApiResume = 200; // ms
|
||||
// Warn the user after fetching the menu if their watch is low on memory before the device crashes.
|
||||
|
||||
//! Warn the user after fetching the menu if their watch is low on memory before the device crashes.
|
||||
static const scLowMem = 0.90; // percent as a fraction.
|
||||
|
||||
// Constants for PIN confirmation dialog
|
||||
static const scPinMaxFailures = 5; // Maximum number of failed PIN confirmation attemps allwed in ...
|
||||
static const scPinMaxFailureMinutes = 2; // ... this number of minutes before PIN confirmation is locked for ...
|
||||
static const scPinLockTimeMinutes = 10; // ... this number of minutes
|
||||
//! Constant for PIN confirmation dialog.<br>
|
||||
//! Maximum number of failed PIN confirmation attempts allowed in `scPinMaxFailureMinutes`.
|
||||
static const scPinMaxFailures = 5;
|
||||
|
||||
//! Constant for PIN confirmation dialog.<br>
|
||||
//! Period in minutes during which no more than `scPinMaxFailures` PIN attempts are tolerated.
|
||||
static const scPinMaxFailureMinutes = 2;
|
||||
|
||||
//! Constant for PIN confirmation dialog.<br>
|
||||
//! Lock out time in minutes after a failed PIN entry.
|
||||
static const scPinLockTimeMinutes = 10;
|
||||
}
|
||||
|
@ -11,11 +11,6 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Application root for GarminHomeAssistant
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Application;
|
||||
@ -25,6 +20,8 @@ using Toybox.System;
|
||||
using Toybox.Application.Properties;
|
||||
using Toybox.Timer;
|
||||
|
||||
//! Application root for GarminHomeAssistant
|
||||
//
|
||||
(:glance, :background)
|
||||
class HomeAssistantApp extends Application.AppBase {
|
||||
private var mApiStatus as Lang.String or Null;
|
||||
@ -40,6 +37,8 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates
|
||||
private var mTemplates as Lang.Dictionary = {};
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
AppBase.initialize();
|
||||
// ATTENTION when adding stuff into this block:
|
||||
@ -55,7 +54,10 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
// with "(:glance)".
|
||||
}
|
||||
|
||||
// onStart() is called on application start up
|
||||
//! Called on application start up
|
||||
//!
|
||||
//! @param state see `AppBase.onStart()`
|
||||
//
|
||||
function onStart(state as Lang.Dictionary?) as Void {
|
||||
AppBase.onStart(state);
|
||||
// ATTENTION when adding stuff into this block:
|
||||
@ -71,7 +73,11 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
// with "(:glance)".
|
||||
}
|
||||
|
||||
// onStop() is called when your application is exiting
|
||||
//! Called when your application is exiting
|
||||
//
|
||||
//!
|
||||
//! @param state see `AppBase.onStop()`
|
||||
//
|
||||
function onStop(state as Lang.Dictionary?) as Void {
|
||||
AppBase.onStop(state);
|
||||
// ATTENTION when adding stuff into this block:
|
||||
@ -87,7 +93,10 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
// with "(:glance)".
|
||||
}
|
||||
|
||||
// Return the initial view of your application here
|
||||
//! Returns the initial view of the application.
|
||||
//!
|
||||
//! @return The initial view.
|
||||
//
|
||||
function getInitialView() as [ WatchUi.Views ] or [ WatchUi.Views, WatchUi.InputDelegates ] {
|
||||
mIsApp = true;
|
||||
mQuitTimer = new QuitTimer();
|
||||
@ -132,10 +141,16 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Callback function after completing the GET request to fetch the configuration menu.
|
||||
//! Callback function after completing the GET request to fetch the configuration menu.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
//
|
||||
(:glance)
|
||||
function onReturnFetchMenuConfig(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||
function onReturnFetchMenuConfig(
|
||||
responseCode as Lang.Number,
|
||||
data as Null or Lang.Dictionary or Lang.String
|
||||
) as Void {
|
||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: " + responseCode);
|
||||
// System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Data: " + data);
|
||||
|
||||
@ -208,8 +223,11 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
WatchUi.requestUpdate();
|
||||
}
|
||||
|
||||
// Return true if the menu came from the cache, otherwise false. This is because fetching the menu when not in the cache is
|
||||
// asynchronous and affects how the views are managed.
|
||||
//! Fetch the menu configuration over HTTPS, which might be locally cached.
|
||||
//!
|
||||
//! @return Return true if the menu came from the cache, otherwise false. This is because fetching
|
||||
//! the menu when not in the cache is asynchronous and affects how the views are managed.
|
||||
//
|
||||
(:glance)
|
||||
function fetchMenuConfig() as Lang.Boolean {
|
||||
// System.println("Menu URL = " + Settings.getConfigUrl());
|
||||
@ -263,6 +281,10 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
//! Build the menu and store in `mHaMenu`. Then start updates if necessary.
|
||||
//!
|
||||
//! @param menu The dictionary derived from the JSON menu fetched by `fetchMenuConfig()`.
|
||||
//
|
||||
private function buildMenu(menu as Lang.Dictionary) {
|
||||
mHaMenu = new HomeAssistantView(menu, null);
|
||||
mQuitTimer.begin();
|
||||
@ -271,6 +293,8 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
} // If not, this will be done via a chain in Settings.webhook() and mWebhookManager.requestWebhookId() that registers the sensors.
|
||||
}
|
||||
|
||||
//! Start the periodic menu updates for as long as the application is running.
|
||||
//
|
||||
function startUpdates() {
|
||||
if (mHaMenu != null and !mUpdating) {
|
||||
// Start the continuous update process that continues for as long as the application is running.
|
||||
@ -279,7 +303,15 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
}
|
||||
}
|
||||
|
||||
function onReturnUpdateMenuItems(responseCode as Lang.Number, data as Null or Lang.Dictionary) as Void {
|
||||
//! Callback function for each menu update GET request.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
//
|
||||
function onReturnUpdateMenuItems(
|
||||
responseCode as Lang.Number,
|
||||
data as Null or Lang.Dictionary
|
||||
) as Void {
|
||||
// System.println("HomeAssistantApp onReturnUpdateMenuItems() Response Code: " + responseCode);
|
||||
// System.println("HomeAssistantApp onReturnUpdateMenuItems() Response Data: " + data);
|
||||
|
||||
@ -353,6 +385,8 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
setApiStatus(status);
|
||||
}
|
||||
|
||||
//! Construct the GET request to update all menu items.
|
||||
//
|
||||
function updateMenuItems() as Void {
|
||||
if (! System.getDeviceSettings().phoneConnected) {
|
||||
// System.println("HomeAssistantApp updateMenuItems(): No Phone connection, skipping API call.");
|
||||
@ -368,17 +402,17 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
mTemplates = {};
|
||||
for (var i = 0; i < mItemsToUpdate.size(); i++) {
|
||||
var item = mItemsToUpdate[i];
|
||||
var template = item.buildTemplate();
|
||||
var template = item.getTemplate();
|
||||
if (template != null) {
|
||||
mTemplates.put(i.toString(), {
|
||||
"template" => template
|
||||
});
|
||||
}
|
||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||
mTemplates.put(i.toString() + "t", {
|
||||
"template" => (item as HomeAssistantToggleMenuItem).buildToggleTemplate()
|
||||
});
|
||||
}
|
||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||
mTemplates.put(i.toString() + "t", {
|
||||
"template" => (item as HomeAssistantToggleMenuItem).getToggleTemplate()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates
|
||||
@ -402,10 +436,16 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Callback function after completing the GET request to fetch the API status.
|
||||
//! Callback function after completing the GET request to fetch the API status.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
//
|
||||
(:glance)
|
||||
function onReturnFetchApiStatus(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||
function onReturnFetchApiStatus(
|
||||
responseCode as Lang.Number,
|
||||
data as Null or Lang.Dictionary or Lang.String
|
||||
) as Void {
|
||||
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Code: " + responseCode);
|
||||
// System.println("HomeAssistantApp onReturnFetchApiStatus() Response Data: " + data);
|
||||
|
||||
@ -466,6 +506,8 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
WatchUi.requestUpdate();
|
||||
}
|
||||
|
||||
//! Construct the GET request to test the API status, is it accessible?
|
||||
//
|
||||
(:glance)
|
||||
function fetchApiStatus() as Void {
|
||||
// System.println("API URL = " + Settings.getApiUrl());
|
||||
@ -506,30 +548,49 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
}
|
||||
}
|
||||
|
||||
//! Record the API status result.
|
||||
//!
|
||||
//! @param s A string describing the API status
|
||||
//
|
||||
function setApiStatus(s as Lang.String) {
|
||||
mApiStatus = s;
|
||||
}
|
||||
|
||||
//! Return the API status result.
|
||||
//!
|
||||
//! @return A string describing the API status
|
||||
//
|
||||
(:glance)
|
||||
function getApiStatus() as Lang.String {
|
||||
return mApiStatus;
|
||||
}
|
||||
|
||||
//! Return the Menu status result.
|
||||
//!
|
||||
//! @return A string describing the Menu status
|
||||
//
|
||||
(:glance)
|
||||
function getMenuStatus() as Lang.String {
|
||||
return mMenuStatus;
|
||||
}
|
||||
|
||||
//! Return the Menu construction status.
|
||||
//!
|
||||
//! @return A Boolean indicating if the menu is loaded into the application.
|
||||
//
|
||||
function isHomeAssistantMenuLoaded() as Lang.Boolean {
|
||||
return mHaMenu != null;
|
||||
}
|
||||
|
||||
//! Make the menu visible on the watch face.
|
||||
//
|
||||
function pushHomeAssistantMenuView() as Void {
|
||||
WatchUi.pushView(mHaMenu, new HomeAssistantViewDelegate(true), WatchUi.SLIDE_IMMEDIATE);
|
||||
}
|
||||
|
||||
// Only call this function if Settings.getPollDelay() > 0. This must be tested locally as it is then efficient to take
|
||||
// alternative action if the test fails.
|
||||
//! Force status updates. Only take action if `Settings.getPollDelay() > 0`. This must be tested
|
||||
//! locally as it is then efficient to take alternative action if the test fails.
|
||||
//
|
||||
function forceStatusUpdates() as Void {
|
||||
// Don't mess with updates unless we are using a timer.
|
||||
if (Settings.getPollDelay() > 0) {
|
||||
@ -539,10 +600,18 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
}
|
||||
}
|
||||
|
||||
//! Return the timer used to quit the application.
|
||||
//!
|
||||
//! @return Timer object
|
||||
//
|
||||
function getQuitTimer() as QuitTimer {
|
||||
return mQuitTimer;
|
||||
}
|
||||
|
||||
//! Return the glance view.
|
||||
//!
|
||||
//! @return The glance view
|
||||
//
|
||||
function getGlanceView() as [ WatchUi.GlanceView ] or [ WatchUi.GlanceView, WatchUi.GlanceViewDelegate ] or Null {
|
||||
mIsGlance = true;
|
||||
mApiStatus = WatchUi.loadResource($.Rez.Strings.Checking) as Lang.String;
|
||||
@ -554,30 +623,38 @@ class HomeAssistantApp extends Application.AppBase {
|
||||
return [new HomeAssistantGlanceView(self)];
|
||||
}
|
||||
|
||||
// Required for the Glance update timer.
|
||||
//! Update the menu and API statuses. Required for the Glance update timer.
|
||||
//
|
||||
function updateStatus() as Void {
|
||||
mGlanceTimer = null;
|
||||
fetchMenuConfig();
|
||||
fetchApiStatus();
|
||||
}
|
||||
|
||||
//! Code for when the application settings are updated.
|
||||
//
|
||||
function onSettingsChanged() as Void {
|
||||
// System.println("HomeAssistantApp onSettingsChanged()");
|
||||
Settings.update();
|
||||
}
|
||||
|
||||
// Called each time the Registered Temporal Event is to be invoked. So the object is created each time on request and
|
||||
// then destroyed on completion (to save resources).
|
||||
//! Called each time the Registered Temporal Event is to be invoked. So the object is created each time
|
||||
//! on request and then destroyed on completion (to save resources).
|
||||
//
|
||||
function getServiceDelegate() as [ System.ServiceDelegate ] {
|
||||
return [new BackgroundServiceDelegate()];
|
||||
}
|
||||
|
||||
//! Determine is we are a glance or the full application. Glances should be considered to be separate applications.
|
||||
//
|
||||
function getIsApp() as Lang.Boolean {
|
||||
return mIsApp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//! Global function to return the application object.
|
||||
//
|
||||
(:glance, :background)
|
||||
function getApp() as HomeAssistantApp {
|
||||
return Application.getApp() as HomeAssistantApp;
|
||||
|
@ -11,11 +11,6 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 19 November 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Calling a Home Assistant confirmation dialogue view.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
@ -25,19 +20,27 @@ using Toybox.WatchUi;
|
||||
using Toybox.Timer;
|
||||
using Toybox.Application.Properties;
|
||||
|
||||
//! Calling a Home Assistant confirmation dialogue view.
|
||||
//
|
||||
class HomeAssistantConfirmation extends WatchUi.Confirmation {
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm) as Lang.String);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//! Delegate to respond to the confirmation request.
|
||||
//
|
||||
class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
|
||||
private var mConfirmMethod as Method(state as Lang.Boolean) as Void;
|
||||
private var mTimer as Timer.Timer or Null;
|
||||
private var mState as Lang.Boolean;
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize(callback as Method(state as Lang.Boolean) as Void, state as Lang.Boolean) {
|
||||
WatchUi.ConfirmationDelegate.initialize();
|
||||
mConfirmMethod = callback;
|
||||
@ -49,7 +52,12 @@ class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
function onResponse(response) as Lang.Boolean {
|
||||
//! Respond to the confirmation event.
|
||||
//!
|
||||
//! @param response code
|
||||
//! @return Required to meet the function prototype, but the base class does not indicate a definition.
|
||||
//
|
||||
function onResponse(response as WatchUi.Confirm) as Lang.Boolean {
|
||||
getApp().getQuitTimer().reset();
|
||||
if (mTimer != null) {
|
||||
mTimer.stop();
|
||||
@ -60,6 +68,7 @@ class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Function supplied to a timer in order to limit the time for which the confirmation can be provided.
|
||||
function onTimeout() as Void {
|
||||
mTimer.stop();
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
|
@ -11,17 +11,14 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 23 November 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Glance view for GarminHomeAssistant
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
using Toybox.Graphics;
|
||||
|
||||
//! Glance view for GarminHomeAssistant
|
||||
//
|
||||
(:glance)
|
||||
class HomeAssistantGlanceView extends WatchUi.GlanceView {
|
||||
private static const scLeftMargin = 5; // in pixels
|
||||
@ -34,6 +31,8 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
|
||||
private var mMenuStatus as WatchUi.Text or Null;
|
||||
private var mAntiAlias as Lang.Boolean = false;
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize(app as HomeAssistantApp) {
|
||||
GlanceView.initialize();
|
||||
mApp = app;
|
||||
@ -42,6 +41,10 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
|
||||
}
|
||||
}
|
||||
|
||||
//! Construct the view.
|
||||
//!
|
||||
//! @param dc Device context
|
||||
//
|
||||
function onLayout(dc as Graphics.Dc) as Void {
|
||||
var h = dc.getHeight();
|
||||
var tw = dc.getTextWidthInPixels(WatchUi.loadResource($.Rez.Strings.GlanceMenu) as Lang.String, Graphics.FONT_XTINY);
|
||||
@ -89,6 +92,10 @@ class HomeAssistantGlanceView extends WatchUi.GlanceView {
|
||||
});
|
||||
}
|
||||
|
||||
//! Update the view with the latest status text.
|
||||
//!
|
||||
//! @param dc Device context
|
||||
//
|
||||
function onUpdate(dc as Graphics.Dc) as Void {
|
||||
GlanceView.onUpdate(dc);
|
||||
if(mAntiAlias) {
|
||||
|
@ -11,20 +11,19 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders
|
||||
// a Home Assistant Template.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
|
||||
//! Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders
|
||||
//! a Home Assistant Template.
|
||||
//
|
||||
class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
|
||||
private var mMenu as HomeAssistantView;
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize(
|
||||
definition as Lang.Dictionary,
|
||||
template as Lang.String,
|
||||
@ -48,6 +47,8 @@ class HomeAssistantGroupMenuItem extends HomeAssistantMenuItem {
|
||||
mMenu = new HomeAssistantView(definition, null);
|
||||
}
|
||||
|
||||
//! Return the submenu for this group menu item.
|
||||
//
|
||||
function getMenuView() as HomeAssistantView {
|
||||
return mMenu;
|
||||
}
|
||||
|
@ -11,20 +11,23 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Generic menu button with an icon that optionally renders a Home Assistant Template.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
using Toybox.Graphics;
|
||||
|
||||
//! Generic menu button with an icon that optionally renders a Home Assistant Template.
|
||||
//
|
||||
class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
|
||||
private var mTemplate as Lang.String or Null;
|
||||
|
||||
//! Class Constructor
|
||||
//!
|
||||
//! @param label Menu item label
|
||||
//! @param template Menu item template
|
||||
//! @param options Menu item options to be passed on.
|
||||
//
|
||||
function initialize(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
template as Lang.String,
|
||||
@ -43,14 +46,27 @@ class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
|
||||
mTemplate = template;
|
||||
}
|
||||
|
||||
//! Does this menu item use a template?
|
||||
//!
|
||||
//! @return True if the menu has a defined template else false.
|
||||
//
|
||||
function hasTemplate() as Lang.Boolean {
|
||||
return mTemplate != null;
|
||||
}
|
||||
|
||||
function buildTemplate() as Lang.String or Null {
|
||||
//! Return the menu item's template.
|
||||
//!
|
||||
//! @return A string with the menu item's template definition.
|
||||
//
|
||||
function getTemplate() as Lang.String or Null {
|
||||
return mTemplate;
|
||||
}
|
||||
|
||||
//! Update the menu item's sub label to display the template rendered by Home Assistant.
|
||||
//!
|
||||
//! @param data The rendered template (typically a string) to be placed in the sub label. This may
|
||||
//! unusually be a number if the SDK interprets the JSON returned by Home Assistant as such.
|
||||
//
|
||||
function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
|
||||
if (data == null) {
|
||||
setSubLabel($.Rez.Strings.Empty);
|
||||
@ -76,4 +92,4 @@ class HomeAssistantMenuItem extends WatchUi.IconMenuItem {
|
||||
WatchUi.requestUpdate();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -11,17 +11,14 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 17 November 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// MenuItems Factory.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Application;
|
||||
using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
|
||||
//! MenuItems Factory class.
|
||||
//
|
||||
class HomeAssistantMenuItemFactory {
|
||||
private var mMenuItemOptions as Lang.Dictionary;
|
||||
private var mTapTypeIcon as WatchUi.Bitmap;
|
||||
@ -31,6 +28,8 @@ class HomeAssistantMenuItemFactory {
|
||||
|
||||
private static var instance;
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
private function initialize() {
|
||||
mMenuItemOptions = {
|
||||
:alignment => Settings.getMenuAlignment()
|
||||
@ -57,6 +56,8 @@ class HomeAssistantMenuItemFactory {
|
||||
mHomeAssistantService = new HomeAssistantService();
|
||||
}
|
||||
|
||||
//! Create the one and only instance of this class.
|
||||
//
|
||||
static function create() as HomeAssistantMenuItemFactory {
|
||||
if (instance == null) {
|
||||
instance = new HomeAssistantMenuItemFactory();
|
||||
@ -64,6 +65,14 @@ class HomeAssistantMenuItemFactory {
|
||||
return instance;
|
||||
}
|
||||
|
||||
//! Toggle menu item.
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param entity_id Home Assistant Entity ID (optional)
|
||||
//! @param template Template for Home Assistant to render (optional)
|
||||
//! @param confirm Should this menu item selection be confirmed?
|
||||
//! @param pin Should this menu item selection request the security PIN?
|
||||
//
|
||||
function toggle(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
entity_id as Lang.String or Null,
|
||||
@ -81,20 +90,30 @@ class HomeAssistantMenuItemFactory {
|
||||
);
|
||||
}
|
||||
|
||||
//! Tap menu item.
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param entity_id Home Assistant Entity ID (optional)
|
||||
//! @param template Template for Home Assistant to render (optional)
|
||||
//! @param service Template for Home Assistant to render (optional)
|
||||
//! @param confirm Should this menu item selection be confirmed?
|
||||
//! @param pin Should this menu item selection request the security PIN?
|
||||
//! @param data Sourced from the menu JSON, this is the `data` field from the `tap_action` field.
|
||||
//
|
||||
function tap(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
entity as Lang.String or Null,
|
||||
template as Lang.String or Null,
|
||||
service as Lang.String or Null,
|
||||
confirm as Lang.Boolean,
|
||||
pin as Lang.Boolean,
|
||||
data as Lang.Dictionary or Null
|
||||
label as Lang.String or Lang.Symbol,
|
||||
entity_id as Lang.String or Null,
|
||||
template as Lang.String or Null,
|
||||
service as Lang.String or Null,
|
||||
confirm as Lang.Boolean,
|
||||
pin as Lang.Boolean,
|
||||
data as Lang.Dictionary or Null
|
||||
) as WatchUi.MenuItem {
|
||||
if (entity != null) {
|
||||
if (entity_id != null) {
|
||||
if (data == null) {
|
||||
data = { "entity_id" => entity };
|
||||
data = { "entity_id" => entity_id };
|
||||
} else {
|
||||
data.put("entity_id", entity);
|
||||
data.put("entity_id", entity_id);
|
||||
}
|
||||
}
|
||||
if (service != null) {
|
||||
@ -124,6 +143,11 @@ class HomeAssistantMenuItemFactory {
|
||||
}
|
||||
}
|
||||
|
||||
//! Group menu item.
|
||||
//!
|
||||
//! @param definition Items array from the JSON that defines this sub menu.
|
||||
//! @param template Template for Home Assistant to render (optional)
|
||||
//
|
||||
function group(
|
||||
definition as Lang.Dictionary,
|
||||
template as Lang.String or Null
|
||||
|
@ -11,25 +11,28 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Pin Confirmation dialog and logic.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
import Toybox.Graphics;
|
||||
import Toybox.Lang;
|
||||
import Toybox.WatchUi;
|
||||
import Toybox.Timer;
|
||||
import Toybox.Attention;
|
||||
import Toybox.Time;
|
||||
using Toybox.Graphics;
|
||||
using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
using Toybox.Timer;
|
||||
using Toybox.Attention;
|
||||
using Toybox.Time;
|
||||
|
||||
//! Pin digit used for number 0..9
|
||||
//
|
||||
class PinDigit extends WatchUi.Selectable {
|
||||
|
||||
private var mDigit as Number;
|
||||
private var mDigit as Lang.Number;
|
||||
|
||||
function initialize(digit as Number, stepX as Number, stepY as Number) {
|
||||
//! Class Constructor
|
||||
//!
|
||||
//! @param digit The digit this instance of the class represents and to display.
|
||||
//! @param stepX Horizontal spacing.
|
||||
//! @param stepY Vertical spacing.
|
||||
//
|
||||
function initialize(digit as Lang.Number, stepX as Lang.Number, stepY as Lang.Number) {
|
||||
var marginX = stepX * 0.05; // 5% margin on all sides
|
||||
var marginY = stepY * 0.05;
|
||||
var x = (digit == 0) ? stepX : stepX * ((digit+2) % 3); // layout '0' in 2nd col, others ltr in 3 columns
|
||||
@ -63,24 +66,40 @@ class PinDigit extends WatchUi.Selectable {
|
||||
});
|
||||
|
||||
mDigit = digit;
|
||||
|
||||
}
|
||||
|
||||
function getDigit() as Number {
|
||||
//! Return the digit 0..9 represented by this button
|
||||
//
|
||||
function getDigit() as Lang.Number {
|
||||
return mDigit;
|
||||
}
|
||||
|
||||
//! Customised drawing of a PIN digit's button.
|
||||
//
|
||||
class PinDigitButton extends WatchUi.Drawable {
|
||||
private var mText as Number;
|
||||
private var mTouched as Boolean = false;
|
||||
private var mText as Lang.Number;
|
||||
private var mTouched as Lang.Boolean = false;
|
||||
|
||||
//! Class Constructor
|
||||
//!
|
||||
//! @param options See `Drawable.initialize()`, but with `:label` and `:touched` added.<br>
|
||||
//! {<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) {
|
||||
Drawable.initialize(options);
|
||||
mText = options.get(:label);
|
||||
mTouched = options.get(:touched);
|
||||
}
|
||||
|
||||
function draw(dc) {
|
||||
//! Draw the PIN digit button.
|
||||
//!
|
||||
//! @param dc Device context
|
||||
//
|
||||
function draw(dc as Graphics.Dc) {
|
||||
if (mTouched) {
|
||||
dc.setColor(Graphics.COLOR_ORANGE, Graphics.COLOR_ORANGE);
|
||||
} else {
|
||||
@ -98,17 +117,27 @@ class PinDigit extends WatchUi.Selectable {
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! Pin Confirmation dialog and logic.
|
||||
//
|
||||
class HomeAssistantPinConfirmationView extends WatchUi.View {
|
||||
|
||||
static const MARGIN_X = 20; // margin on left & right side of screen (overall prettier and works better on round displays)
|
||||
//! Margin on left & right side of screen (overall prettier and works better on round displays)
|
||||
static const MARGIN_X = 20;
|
||||
//! Indicates how many digits have been entered so far.
|
||||
var mPinMask as Lang.String = "";
|
||||
|
||||
var mPinMask as String = "";
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
View.initialize();
|
||||
}
|
||||
|
||||
function onLayout(dc as Dc) as Void {
|
||||
//! Construct the view.
|
||||
//!
|
||||
//! @param dc Device context
|
||||
//
|
||||
function onLayout(dc as Graphics.Dc) as Void {
|
||||
var stepX = (dc.getWidth() - MARGIN_X * 2) / 3; // three columns
|
||||
var stepY = dc.getHeight() / 5; // five rows (first row for masked pin entry)
|
||||
var digits = [];
|
||||
@ -119,7 +148,11 @@ class HomeAssistantPinConfirmationView extends WatchUi.View {
|
||||
setLayout(digits);
|
||||
}
|
||||
|
||||
function onUpdate(dc as Dc) as Void {
|
||||
//! Update the view.
|
||||
//!
|
||||
//! @param dc Device context
|
||||
//
|
||||
function onUpdate(dc as Graphics.Dc) as Void {
|
||||
View.onUpdate(dc);
|
||||
if (mPinMask.length() != 0) {
|
||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
|
||||
@ -127,7 +160,11 @@ class HomeAssistantPinConfirmationView extends WatchUi.View {
|
||||
}
|
||||
}
|
||||
|
||||
function updatePinMask(length as Number) {
|
||||
//! Update the PIN mask displayed.
|
||||
//!
|
||||
//! @param length Number of `*` characters to use for the mask string.
|
||||
//
|
||||
function updatePinMask(length as Lang.Number) {
|
||||
mPinMask = "";
|
||||
for (var i=0; i<length; i++) {
|
||||
mPinMask += "*";
|
||||
@ -138,17 +175,31 @@ class HomeAssistantPinConfirmationView extends WatchUi.View {
|
||||
}
|
||||
|
||||
|
||||
//! Delegate for the HomeAssistantPinConfirmationView.
|
||||
//
|
||||
class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||
|
||||
private var mPin as String;
|
||||
private var mEnteredPin as String;
|
||||
private var mPin as Lang.String;
|
||||
private var mEnteredPin as Lang.String;
|
||||
private var mConfirmMethod as Method(state as Lang.Boolean) as Void;
|
||||
private var mTimer as Timer.Timer or Null;
|
||||
private var mState as Lang.Boolean;
|
||||
private var mFailures as PinFailures;
|
||||
private var mView as HomeAssistantPinConfirmationView;
|
||||
|
||||
function initialize(callback as Method(state as Lang.Boolean) as Void, state as Lang.Boolean, pin as String, view as HomeAssistantPinConfirmationView) {
|
||||
//! Class Constructor
|
||||
//!
|
||||
//! @param callback Method to call on confirmation.
|
||||
//! @param state Current state of a toggle button.
|
||||
//! @param pin PIN to be matched.
|
||||
//! @param view PIN confirmation view.
|
||||
//
|
||||
function initialize(
|
||||
callback as Method(state as Lang.Boolean) as Void,
|
||||
state as Lang.Boolean,
|
||||
pin as Lang.String,
|
||||
view as HomeAssistantPinConfirmationView
|
||||
) {
|
||||
BehaviorDelegate.initialize();
|
||||
mFailures = new PinFailures();
|
||||
if (mFailures.isLocked()) {
|
||||
@ -165,7 +216,12 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
function onSelectable(event as SelectableEvent) as Boolean {
|
||||
//! Add another entered digit to the "PIN so far". When it is long enough verify the PIN is correct and the
|
||||
//! invoke the supplied call back function.
|
||||
//!
|
||||
//! @param event The digit pressed by the user tapping the screen.
|
||||
//
|
||||
function onSelectable(event as WatchUi.SelectableEvent) as Lang.Boolean {
|
||||
if (mFailures.isLocked()) {
|
||||
goBack();
|
||||
}
|
||||
@ -173,7 +229,7 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||
if (instance instanceof PinDigit && event.getPreviousState() == :stateSelected) {
|
||||
mEnteredPin += instance.getDigit();
|
||||
createUserFeedback();
|
||||
// System.println("HomeAssitantPinConfirmationDelegate onSelectable() mEnteredPin = " + mEnteredPin);
|
||||
// System.println("HomeAssistantPinConfirmationDelegate onSelectable() mEnteredPin = " + mEnteredPin);
|
||||
if (mEnteredPin.length() == mPin.length()) {
|
||||
if (mEnteredPin.equals(mPin)) {
|
||||
mFailures.reset();
|
||||
@ -193,6 +249,8 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Hepatic feedback.
|
||||
//
|
||||
function createUserFeedback() {
|
||||
if (Attention has :vibrate && Settings.getVibrate()) {
|
||||
Attention.vibrate([new Attention.VibeProfile(25, 25)]);
|
||||
@ -200,6 +258,9 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||
mView.updatePinMask(mEnteredPin.length());
|
||||
}
|
||||
|
||||
//! A timer is used to clear the PIN entry view if digits are not pressed. So each time a digit is pressed the
|
||||
//! timer is reset.
|
||||
//
|
||||
function resetTimer() {
|
||||
var timeout = Settings.getConfirmTimeout(); // ms
|
||||
if (timeout > 0) {
|
||||
@ -212,6 +273,8 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
//! Cancel PIN entry.
|
||||
//
|
||||
function goBack() as Void {
|
||||
if (mTimer != null) {
|
||||
mTimer.stop();
|
||||
@ -219,18 +282,20 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
}
|
||||
|
||||
//! Hepatic feedback for a wrong PIN and cancel entry.
|
||||
//
|
||||
function error() as Void {
|
||||
// System.println("HomeAssistantPinConfirmationDelegate error() Wrong PIN entered");
|
||||
mFailures.addFailure();
|
||||
if (Attention has :vibrate && Settings.getVibrate()) {
|
||||
Attention.vibrate([
|
||||
new Attention.VibeProfile(100, 100),
|
||||
new Attention.VibeProfile(0, 200),
|
||||
new Attention.VibeProfile(75, 100),
|
||||
new Attention.VibeProfile(0, 200),
|
||||
new Attention.VibeProfile(50, 100),
|
||||
new Attention.VibeProfile(0, 200),
|
||||
new Attention.VibeProfile(25, 100)
|
||||
new Attention.VibeProfile( 0, 200),
|
||||
new Attention.VibeProfile( 75, 100),
|
||||
new Attention.VibeProfile( 0, 200),
|
||||
new Attention.VibeProfile( 50, 100),
|
||||
new Attention.VibeProfile( 0, 200),
|
||||
new Attention.VibeProfile( 25, 100)
|
||||
]);
|
||||
}
|
||||
if (WatchUi has :showToast) {
|
||||
@ -241,14 +306,19 @@ class HomeAssistantPinConfirmationDelegate extends WatchUi.BehaviorDelegate {
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! Manage PIN entry failures to try and prevent brute force exhaustion by inserting delays in retries.
|
||||
//
|
||||
class PinFailures {
|
||||
|
||||
const STORAGE_KEY_FAILURES as String = "pin_failures";
|
||||
const STORAGE_KEY_LOCKED as String = "pin_locked";
|
||||
const STORAGE_KEY_FAILURES as Lang.String = "pin_failures";
|
||||
const STORAGE_KEY_LOCKED as Lang.String = "pin_locked";
|
||||
|
||||
private var mFailures as Array<Number>;
|
||||
private var mLockedUntil as Number or Null;
|
||||
private var mFailures as Lang.Array<Lang.Number>;
|
||||
private var mLockedUntil as Lang.Number or Null;
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
// System.println("PinFailures initialize() Initializing PIN failures from storage");
|
||||
var failures = Application.Storage.getValue(PinFailures.STORAGE_KEY_FAILURES);
|
||||
@ -256,6 +326,8 @@ class PinFailures {
|
||||
mLockedUntil = Application.Storage.getValue(PinFailures.STORAGE_KEY_LOCKED);
|
||||
}
|
||||
|
||||
//! Record a PIN entry failure. If too many have occurred lock the application.
|
||||
//
|
||||
function addFailure() {
|
||||
mFailures.add(Time.now().value());
|
||||
// System.println("PinFailures addFailure() " + mFailures.size() + " PIN confirmation failures recorded");
|
||||
@ -268,7 +340,7 @@ class PinFailures {
|
||||
mFailures = mFailures.slice(1, null);
|
||||
} else {
|
||||
mFailures = [];
|
||||
mLockedUntil = Time.now().add(new Time.Duration(Globals.scPinLockTimeMinutes * Gregorian.SECONDS_PER_MINUTE)).value();
|
||||
mLockedUntil = Time.now().add(new Time.Duration(Globals.scPinLockTimeMinutes * Time.Gregorian.SECONDS_PER_MINUTE)).value();
|
||||
Application.Storage.setValue(STORAGE_KEY_LOCKED, mLockedUntil);
|
||||
// System.println("PinFailures addFailure() Locked until " + mLockedUntil);
|
||||
}
|
||||
@ -276,6 +348,9 @@ class PinFailures {
|
||||
Application.Storage.setValue(STORAGE_KEY_FAILURES, mFailures);
|
||||
}
|
||||
|
||||
//! Clear the record of previous PIN entry failures, e.g. because the correct PIN has now been entered
|
||||
//! within tolerance.
|
||||
//
|
||||
function reset() {
|
||||
// System.println("PinFailures reset() Resetting failures");
|
||||
mFailures = [];
|
||||
@ -284,11 +359,18 @@ class PinFailures {
|
||||
Application.Storage.deleteValue(STORAGE_KEY_LOCKED);
|
||||
}
|
||||
|
||||
function getLockedUntilSeconds() as Number {
|
||||
//! Retrieve the remaining time the application must be locked out for.
|
||||
//
|
||||
function getLockedUntilSeconds() as Lang.Number {
|
||||
return new Time.Moment(mLockedUntil).subtract(Time.now()).value();
|
||||
}
|
||||
|
||||
function isLocked() as Boolean {
|
||||
//! Is the application currently locked out? If the application is no longer locked out, then clear the
|
||||
//! stored values used to determine this state.
|
||||
//!
|
||||
//! @return Boolean indicating if the application is currently locked out.
|
||||
//
|
||||
function isLocked() as Lang.Boolean {
|
||||
if (mLockedUntil == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -11,11 +11,6 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth, 19 November 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Calling a Home Assistant Service.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
@ -23,10 +18,14 @@ using Toybox.WatchUi;
|
||||
using Toybox.Graphics;
|
||||
using Toybox.Application.Properties;
|
||||
|
||||
//! Calling a Home Assistant Service.
|
||||
//
|
||||
class HomeAssistantService {
|
||||
private var mHasToast as Lang.Boolean = false;
|
||||
private var mHasVibrate as Lang.Boolean = false;
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
if (WatchUi has :showToast) {
|
||||
mHasToast = true;
|
||||
@ -36,7 +35,11 @@ class HomeAssistantService {
|
||||
}
|
||||
}
|
||||
|
||||
// Callback function after completing the POST request to call a service.
|
||||
//! Callback function after completing the POST request to call a service.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
//! @param context An `entity_id` supplied in the GET request `options` `Lang.Dictionary` `context` field.
|
||||
//
|
||||
function onReturnCall(
|
||||
responseCode as Lang.Number,
|
||||
@ -107,6 +110,11 @@ class HomeAssistantService {
|
||||
}
|
||||
}
|
||||
|
||||
//! Invoke a service call for a menu item.
|
||||
//!
|
||||
//! @param service The Home Assistant service to be run, e.g. from the JSON `service` field.
|
||||
//! @param data Data to be supplied to the service call.
|
||||
//
|
||||
function call(
|
||||
service as Lang.String,
|
||||
data as Lang.Dictionary or Null
|
||||
|
@ -11,17 +11,14 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Menu button that triggers a service.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
using Toybox.Graphics;
|
||||
|
||||
//! Menu button that triggers a service.
|
||||
//
|
||||
class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
private var mHomeAssistantService as HomeAssistantService;
|
||||
private var mService as Lang.String or Null;
|
||||
@ -29,6 +26,19 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
private var mPin as Lang.Boolean;
|
||||
private var mData as Lang.Dictionary or Null;
|
||||
|
||||
//! Class Constructor
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param template Menu item template.
|
||||
//! @param service Menu item service.
|
||||
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the service call be protected with a PIN for some low level of security?
|
||||
//! @param data Data to supply to the service call.
|
||||
//! @param icon Icon to use for the menu item.
|
||||
//! @param options Menu item options to be passed on.
|
||||
//! @param haService Shared Home Assistant service object that will perform the required call. Only
|
||||
//! one of these objects is created for all menu items to re-use.
|
||||
//
|
||||
function initialize(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
template as Lang.String,
|
||||
@ -62,6 +72,8 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
mData = data;
|
||||
}
|
||||
|
||||
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
|
||||
//
|
||||
function callService() as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
@ -85,7 +97,10 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
// NB. Parameter 'b' is ignored
|
||||
//! Callback function after the menu items selection has been (optionally) confirmed.
|
||||
//!
|
||||
//! @param b Ignored. It is included in order to match the expected function prototype of the callback method.
|
||||
//
|
||||
function onConfirm(b as Lang.Boolean) as Void {
|
||||
if (mService != null) {
|
||||
mHomeAssistantService.call(mService, mData);
|
||||
|
@ -11,11 +11,6 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Light or switch toggle button that calls the API to maintain the up to date state.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
@ -24,6 +19,8 @@ using Toybox.Graphics;
|
||||
using Toybox.Application.Properties;
|
||||
using Toybox.Timer;
|
||||
|
||||
//! Light or switch toggle menu button that calls the API to maintain the up to date state.
|
||||
//
|
||||
class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
private var mConfirm as Lang.Boolean;
|
||||
private var mPin as Lang.Boolean;
|
||||
@ -31,6 +28,15 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
private var mTemplate as Lang.String;
|
||||
private var mHasVibrate as Lang.Boolean = false;
|
||||
|
||||
//! Class Constructor
|
||||
//!
|
||||
//! @param label Menu item label.
|
||||
//! @param template Menu item template.
|
||||
//! @param confirm Should the service call be confirmed to avoid accidental invocation?
|
||||
//! @param pin Should the service call be protected with a PIN for some low level of security?
|
||||
//! @param data Data to supply to the service call.
|
||||
//! @param options Menu item options to be passed on.
|
||||
//
|
||||
function initialize(
|
||||
label as Lang.String or Lang.Symbol,
|
||||
template as Lang.String,
|
||||
@ -58,6 +64,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
mTemplate = template;
|
||||
}
|
||||
|
||||
//! Set the state of a toggle menu item.
|
||||
//
|
||||
private function setUiToggle(state as Null or Lang.String) as Void {
|
||||
if (state != null) {
|
||||
if (state.equals("on") && !isEnabled()) {
|
||||
@ -68,13 +76,26 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
function buildTemplate() as Lang.String or Null {
|
||||
//! Return the menu item's template.
|
||||
//!
|
||||
//! @return A string with the menu item's template definition.
|
||||
//
|
||||
function getTemplate() as Lang.String or Null {
|
||||
return mTemplate;
|
||||
}
|
||||
function buildToggleTemplate() as Lang.String or Null {
|
||||
|
||||
//! Return a toggle menu item's state template.
|
||||
//!
|
||||
//! @return A string with the menu item's template definition.
|
||||
//
|
||||
function getToggleTemplate() as Lang.String or Null {
|
||||
return "{{states('" + mData.get("entity_id") + "')}}";
|
||||
}
|
||||
|
||||
//! Update the menu item's label from a recent GET request.
|
||||
//!
|
||||
//! @param data This should be a string, but the way the GET response is parsed, it can also be a number.
|
||||
//
|
||||
function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void {
|
||||
if (data == null) {
|
||||
setSubLabel(null);
|
||||
@ -100,6 +121,10 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
WatchUi.requestUpdate();
|
||||
}
|
||||
|
||||
//! Update the menu item's toggle state from a recent GET request.
|
||||
//!
|
||||
//! @param data This should be a string of either "on" or "off".
|
||||
//
|
||||
function updateToggleState(data as Lang.String or Lang.Dictionary or Null) as Void {
|
||||
if (data == null) {
|
||||
setUiToggle("off");
|
||||
@ -126,9 +151,15 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
WatchUi.requestUpdate();
|
||||
}
|
||||
|
||||
// Callback function after completing the POST request to set the status.
|
||||
//! Callback function after completing the POST request to set the status.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
//
|
||||
function onReturnSetState(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||
function onReturnSetState(
|
||||
responseCode as Lang.Number,
|
||||
data as Null or Lang.Dictionary or Lang.String
|
||||
) as Void {
|
||||
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: " + responseCode);
|
||||
// System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Data: " + data);
|
||||
|
||||
@ -183,6 +214,10 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
getApp().setApiStatus(status);
|
||||
}
|
||||
|
||||
//! Set the state of the toggle menu item.
|
||||
//!
|
||||
//! @param s Boolean indicating the desired state of the toggle switch.
|
||||
//
|
||||
function setState(s as Lang.Boolean) as Void {
|
||||
// Toggle the UI back, we'll wait for confirmation from the Home Assistant
|
||||
setEnabled(!isEnabled());
|
||||
@ -228,6 +263,8 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
//! Call a Home Assistant service only after checks have been done for confirmation or PIN entry.
|
||||
//
|
||||
function callService(b as Lang.Boolean) as Void {
|
||||
var hasTouchScreen = System.getDeviceSettings().isTouchScreen;
|
||||
if (mPin && hasTouchScreen) {
|
||||
@ -251,6 +288,10 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
|
||||
}
|
||||
}
|
||||
|
||||
//! Callback function to toggle state of this item after (optional) confirmation.
|
||||
//!
|
||||
//! @param b Desired toggle button state.
|
||||
//
|
||||
function onConfirm(b as Lang.Boolean) as Void {
|
||||
setState(b);
|
||||
}
|
||||
|
@ -11,11 +11,6 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Home Assistant menu construction.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Application;
|
||||
@ -24,8 +19,12 @@ using Toybox.Graphics;
|
||||
using Toybox.System;
|
||||
using Toybox.WatchUi;
|
||||
|
||||
//! Home Assistant menu construction.
|
||||
//
|
||||
class HomeAssistantView extends WatchUi.Menu2 {
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize(
|
||||
definition as Lang.Dictionary,
|
||||
options as {
|
||||
@ -75,7 +74,12 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
}
|
||||
}
|
||||
|
||||
// Lang.Array.addAll() fails structural type checking without including "Null" in the return type
|
||||
//! Return a list of items that need to be updated within this menu structure.
|
||||
//!
|
||||
//! MN. Lang.Array.addAll() fails structural type checking without including "Null" in the return type
|
||||
//!
|
||||
//! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state.
|
||||
//
|
||||
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem or Null> {
|
||||
var fullList = [];
|
||||
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
|
||||
@ -102,26 +106,35 @@ class HomeAssistantView extends WatchUi.Menu2 {
|
||||
return fullList;
|
||||
}
|
||||
|
||||
// Called when this View is brought to the foreground. Restore
|
||||
// the state of this View and prepare it to be shown. This includes
|
||||
// loading resources into memory.
|
||||
//! Called when this View is brought to the foreground. Restore
|
||||
//! the state of this View and prepare it to be shown. This includes
|
||||
//! loading resources into memory.
|
||||
function onShow() as Void {}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/
|
||||
//! Delegate for the HomeAssistantView.
|
||||
//!
|
||||
//! Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/
|
||||
//
|
||||
class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
||||
private var mIsRootMenuView as Lang.Boolean = false;
|
||||
private var mTimer as QuitTimer;
|
||||
|
||||
//! Class Constructor
|
||||
//!
|
||||
//! @param isRootMenuView As menus can be nested, this state marks the top level menu so that the
|
||||
//! back event can exit the application completely rather than just popping
|
||||
//! a menu view.
|
||||
//
|
||||
function initialize(isRootMenuView as Lang.Boolean) {
|
||||
Menu2InputDelegate.initialize();
|
||||
mIsRootMenuView = isRootMenuView;
|
||||
mTimer = getApp().getQuitTimer();
|
||||
}
|
||||
|
||||
//! Back button event
|
||||
//
|
||||
function onBack() {
|
||||
mTimer.reset();
|
||||
|
||||
@ -135,16 +148,22 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
||||
WatchUi.popView(WatchUi.SLIDE_RIGHT);
|
||||
}
|
||||
|
||||
// Only for CheckboxMenu
|
||||
//! Only for CheckboxMenu
|
||||
//
|
||||
function onDone() {
|
||||
mTimer.reset();
|
||||
}
|
||||
|
||||
// Only for CustomMenu
|
||||
//! Only for CustomMenu
|
||||
//
|
||||
function onFooter() {
|
||||
mTimer.reset();
|
||||
}
|
||||
|
||||
//! Select event
|
||||
//!
|
||||
//! @param item Selected menu item.
|
||||
//
|
||||
function onSelect(item as WatchUi.MenuItem) as Void {
|
||||
mTimer.reset();
|
||||
if (item instanceof HomeAssistantToggleMenuItem) {
|
||||
@ -164,7 +183,8 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// Only for CustomMenu
|
||||
//! Only for CustomMenu
|
||||
//
|
||||
function onTitle() {
|
||||
mTimer.reset();
|
||||
}
|
||||
|
@ -11,11 +11,6 @@
|
||||
//
|
||||
// J D Abbey & P A Abbey, 28 December 2022
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Quit the application after a period of inactivity in order to save the battery.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
@ -23,18 +18,27 @@ using Toybox.Timer;
|
||||
using Toybox.Application.Properties;
|
||||
using Toybox.WatchUi;
|
||||
|
||||
//! Quit the application after a period of inactivity in order to save the battery.
|
||||
//!
|
||||
class QuitTimer extends Timer.Timer {
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
Timer.Timer.initialize();
|
||||
}
|
||||
|
||||
//! Can't see how to make a method object from `System.exit()` without this layer of
|
||||
//! indirection. I assume this is because `System` is a static class.
|
||||
//
|
||||
function exitApp() as Void {
|
||||
// System.println("QuitTimer exitApp(): Exiting");
|
||||
// This will exit the system cleanly from any point within an app.
|
||||
System.exit();
|
||||
}
|
||||
|
||||
//! Kick off the quit timer.
|
||||
//
|
||||
function begin() {
|
||||
var api_timeout = Settings.getAppTimeout(); // ms
|
||||
if (api_timeout > 0) {
|
||||
@ -42,6 +46,8 @@ class QuitTimer extends Timer.Timer {
|
||||
}
|
||||
}
|
||||
|
||||
//! Reset the quit timer.
|
||||
//
|
||||
function reset() {
|
||||
// System.println("QuitTimer reset(): Restarted quit timer");
|
||||
stop();
|
||||
|
@ -11,35 +11,36 @@
|
||||
//
|
||||
// J D Abbey & P A Abbey, 28 December 2022
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// A view with added methods to scale from percentages of scrren size to pixels.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
using Toybox.WatchUi;
|
||||
using Toybox.Math;
|
||||
|
||||
//! A view that provides a common method 'pixelsForScreen' to make Views easier to layout on different
|
||||
//! sized watch screens.
|
||||
//
|
||||
class ScalableView extends WatchUi.View {
|
||||
//! Retain the local screen width for efficiency
|
||||
private var mScreenWidth;
|
||||
|
||||
//! Class Constructor
|
||||
//
|
||||
function initialize() {
|
||||
View.initialize();
|
||||
mScreenWidth = System.getDeviceSettings().screenWidth;
|
||||
}
|
||||
|
||||
// Convert a fraction expressed as a percentage (%) to a number of pixels for the
|
||||
// screen's dimensions.
|
||||
//
|
||||
// Parameters:
|
||||
// * dc - Device context
|
||||
// * pc - Percentage (%) expressed as a number in the range 0.0..100.0
|
||||
//
|
||||
// Uses screen width rather than screen height as rectangular screens tend to have
|
||||
// height > width.
|
||||
//
|
||||
//! Convert a fraction expressed as a percentage (%) to a number of pixels for the
|
||||
//! screen's dimensions.
|
||||
//!
|
||||
//! Uses screen width rather than screen height as rectangular screens tend to have
|
||||
//! height > width.
|
||||
//!
|
||||
//! @param pc Percentage (%) expressed as a number in the range 0.0..100.0
|
||||
//!
|
||||
//! @return Number of pixels for the screen's dimensions for a fraction expressed as a percentage (%).
|
||||
//!
|
||||
function pixelsForScreen(pc as Lang.Float) as Lang.Number {
|
||||
return Math.round(pc * mScreenWidth) / 100;
|
||||
}
|
||||
|
@ -11,16 +11,6 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey, SomeoneOnEarth & moesterheld, 23 November 2023
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Home Assistant settings.
|
||||
//
|
||||
// WARNING!
|
||||
//
|
||||
// Careful putting ErrorView.show() calls in here. They need to be guarded so that
|
||||
// they do not get called when only displaying the glance view.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
@ -31,6 +21,11 @@ using Toybox.System;
|
||||
using Toybox.Background;
|
||||
using Toybox.Time;
|
||||
|
||||
//! Home Assistant settings.
|
||||
//!
|
||||
//! <em>WARNING!</em> Careful putting ErrorView.show() calls in here. They need to be
|
||||
//! guarded so that they do not get called when only displaying the glance view.
|
||||
//
|
||||
(:glance, :background)
|
||||
class Settings {
|
||||
private static var mApiKey as Lang.String = "";
|
||||
@ -40,19 +35,24 @@ class Settings {
|
||||
private static var mCacheConfig as Lang.Boolean = false;
|
||||
private static var mClearCache as Lang.Boolean = false;
|
||||
private static var mVibrate as Lang.Boolean = false;
|
||||
private static var mAppTimeout as Lang.Number = 0; // seconds
|
||||
private static var mPollDelay as Lang.Number = 0; // seconds
|
||||
private static var mConfirmTimeout as Lang.Number = 3; // seconds
|
||||
//! seconds
|
||||
private static var mAppTimeout as Lang.Number = 0;
|
||||
//! seconds
|
||||
private static var mPollDelay as Lang.Number = 0;
|
||||
//! seconds
|
||||
private static var mConfirmTimeout as Lang.Number = 3;
|
||||
private static var mPin as Lang.String or Null = "0000";
|
||||
private static var mMenuAlignment as Lang.Number = WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_LEFT;
|
||||
private static var mIsSensorsLevelEnabled as Lang.Boolean = false;
|
||||
private static var mBatteryRefreshRate as Lang.Number = 15; // minutes
|
||||
//! minutes
|
||||
private static var mBatteryRefreshRate as Lang.Number = 15;
|
||||
private static var mIsApp as Lang.Boolean = false;
|
||||
private static var mHasService as Lang.Boolean = false;
|
||||
// Must keep the object so it doesn't get garbage collected.
|
||||
//! Must keep the object so it doesn't get garbage collected.
|
||||
private static var mWebhookManager as WebhookManager or Null;
|
||||
|
||||
// Called on application start and then whenever the settings are changed.
|
||||
//! Called on application start and then whenever the settings are changed.
|
||||
//
|
||||
static function update() {
|
||||
mIsApp = getApp().getIsApp();
|
||||
mApiKey = Properties.getValue("api_key");
|
||||
@ -71,6 +71,8 @@ class Settings {
|
||||
mBatteryRefreshRate = Properties.getValue("battery_level_refresh_rate");
|
||||
}
|
||||
|
||||
//! A webhook is required for non-privileged API calls.
|
||||
//
|
||||
static function webhook() {
|
||||
if (System has :ServiceDelegate) {
|
||||
mHasService = true;
|
||||
@ -116,65 +118,123 @@ class Settings {
|
||||
// }
|
||||
}
|
||||
|
||||
//! Get the API key supplied as part of the Settings.
|
||||
//!
|
||||
//! @return The API Key
|
||||
//
|
||||
static function getApiKey() as Lang.String {
|
||||
return mApiKey;
|
||||
}
|
||||
|
||||
//! Get the Webhook ID supplied as part of the Settings.
|
||||
//!
|
||||
//! @return The Webhook ID
|
||||
//
|
||||
static function getWebhookId() as Lang.String {
|
||||
return mWebhookId;
|
||||
}
|
||||
|
||||
//! Set the Webhook ID supplied as part of the Settings.
|
||||
//!
|
||||
//! @param webhookId The Webhook ID value to be saved.
|
||||
//
|
||||
static function setWebhookId(webhookId as Lang.String) {
|
||||
mWebhookId = webhookId;
|
||||
Properties.setValue("webhook_id", mWebhookId);
|
||||
}
|
||||
|
||||
//! Delete the Webhook ID saved as part of the Settings.
|
||||
//
|
||||
static function unsetWebhookId() {
|
||||
mWebhookId = "";
|
||||
Properties.setValue("webhook_id", mWebhookId);
|
||||
}
|
||||
|
||||
//! Get the API URL supplied as part of the Settings.
|
||||
//!
|
||||
//! @return The API URL
|
||||
//
|
||||
static function getApiUrl() as Lang.String {
|
||||
return mApiUrl;
|
||||
}
|
||||
|
||||
//! Get the menu configuration URL supplied as part of the Settings.
|
||||
//!
|
||||
//! @return The menu configuration URL
|
||||
//
|
||||
static function getConfigUrl() as Lang.String {
|
||||
return mConfigUrl;
|
||||
}
|
||||
|
||||
//! Get the menu cache Boolean option supplied as part of the Settings.
|
||||
//!
|
||||
//! @return Boolean for whether the menu should be cached to save application
|
||||
//! start up time.
|
||||
//
|
||||
static function getCacheConfig() as Lang.Boolean {
|
||||
return mCacheConfig;
|
||||
}
|
||||
|
||||
//! Get the clear cache Boolean option supplied as part of the Settings.
|
||||
//!
|
||||
//! @return Boolean for whether the cache should be cleared next time the
|
||||
//! application is started, forcing a menu refresh.
|
||||
//
|
||||
static function getClearCache() as Lang.Boolean {
|
||||
return mClearCache;
|
||||
}
|
||||
|
||||
//! Unset the clear cache Boolean option supplied as part of the Settings.
|
||||
//
|
||||
static function unsetClearCache() {
|
||||
mClearCache = false;
|
||||
Properties.setValue("clear_cache", mClearCache);
|
||||
}
|
||||
|
||||
//! Get the vibration Boolean option supplied as part of the Settings.
|
||||
//!
|
||||
//! @return Boolean for whether vibration is enabled.
|
||||
//
|
||||
static function getVibrate() as Lang.Boolean {
|
||||
return mVibrate;
|
||||
}
|
||||
|
||||
//! Get the application timeout value supplied as part of the Settings.
|
||||
//!
|
||||
//! @return The application timeout in milliseconds.
|
||||
//
|
||||
static function getAppTimeout() as Lang.Number {
|
||||
return mAppTimeout * 1000; // Convert to milliseconds
|
||||
}
|
||||
|
||||
//! Get the application API polling interval supplied as part of the Settings.
|
||||
//!
|
||||
//! @return The application API polling interval in milliseconds.
|
||||
//
|
||||
static function getPollDelay() as Lang.Number {
|
||||
return mPollDelay * 1000; // Convert to milliseconds
|
||||
}
|
||||
|
||||
//! Get the menu item confirmation delay supplied as part of the Settings.
|
||||
//!
|
||||
//! @return The menu item confirmation delay in milliseconds.
|
||||
//
|
||||
static function getConfirmTimeout() as Lang.Number {
|
||||
return mConfirmTimeout * 1000; // Convert to milliseconds
|
||||
}
|
||||
|
||||
//! Get the menu item security PIN supplied as part of the Settings.
|
||||
//!
|
||||
//! @return The menu item security PIN.
|
||||
//
|
||||
static function getPin() as Lang.String or Null {
|
||||
return mPin;
|
||||
}
|
||||
|
||||
//! Check the user selected PIN confirms to 4 digits as a string.
|
||||
//!
|
||||
//! @return The validated 4 digit string.
|
||||
//
|
||||
private static function validatePin() as Lang.String or Null {
|
||||
var pin = Properties.getValue("pin");
|
||||
if (pin.toNumber() == null || pin.length() != 4) {
|
||||
@ -183,14 +243,24 @@ class Settings {
|
||||
return pin;
|
||||
}
|
||||
|
||||
//! Get the menu item alignment as part of the Settings.
|
||||
//!
|
||||
//! @return The menu item alignment.
|
||||
//
|
||||
static function getMenuAlignment() as Lang.Number {
|
||||
return mMenuAlignment; // Either WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_RIGHT or WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_LEFT
|
||||
}
|
||||
|
||||
//! Is logging of the watch sensors enabled? E.g. battery, activity etc.
|
||||
//!
|
||||
//! @return Boolean for whether logging of the watch sensors is enabled.
|
||||
//
|
||||
static function isSensorsLevelEnabled() as Lang.Boolean {
|
||||
return mIsSensorsLevelEnabled;
|
||||
}
|
||||
|
||||
//! Disable logging of the watch's sensors.
|
||||
//
|
||||
static function unsetIsSensorsLevelEnabled() {
|
||||
mIsSensorsLevelEnabled = false;
|
||||
Properties.setValue("enable_battery_level", mIsSensorsLevelEnabled);
|
||||
|
123
source/WebLog.mc
123
source/WebLog.mc
@ -11,88 +11,99 @@
|
||||
//
|
||||
// J D Abbey & P A Abbey, 28 December 2022
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// WebLog provides a logging and hence debugging aid for when the application is
|
||||
// deployed to the watch. This is only used for development and use of it must not
|
||||
// persist into a deployed version. It uses a string buffer to group log entries into
|
||||
// larger submissions in order to prevent overflow of the blue tooth stack.
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
//
|
||||
// Usage:
|
||||
// wl = new WebLog();
|
||||
// wl.clear();
|
||||
// wl.println("Debug Message");
|
||||
// wl.flush();
|
||||
//
|
||||
// https://domain.name/path/log.php
|
||||
//
|
||||
// <?php
|
||||
// $myfile = fopen("log", "a");
|
||||
// $queries = array();
|
||||
// parse_str($_SERVER['QUERY_STRING'], $queries);
|
||||
// fwrite($myfile, $queries['log']);
|
||||
// print "Success";
|
||||
// ?>
|
||||
//
|
||||
// Logs published to: https://domain.name/path/log
|
||||
//
|
||||
// https://domain.name/path/log_clear.php
|
||||
//
|
||||
// <?php
|
||||
// $myfile = fopen("log", "w");
|
||||
// fwrite($myfile, "");
|
||||
// print "Success";
|
||||
// ?>
|
||||
|
||||
using Toybox.Communications;
|
||||
using Toybox.Lang;
|
||||
using Toybox.System;
|
||||
|
||||
//! WebLog provides a logging and hence debugging aid for when the application is
|
||||
//! deployed to the watch. This is only used for development and use of it must not
|
||||
//! persist into a deployed version. It uses a string buffer to group log entries into
|
||||
//! larger submissions in order to prevent overflow of the Bluetooth stack.
|
||||
//!
|
||||
//! Usage:
|
||||
//! <pre>
|
||||
//! wl = new WebLog();
|
||||
//! wl.clear();
|
||||
//! wl.println("Debug Message");
|
||||
//! wl.flush();
|
||||
//! </pre>
|
||||
//!
|
||||
//! File: https://domain.name/path/log.php
|
||||
//!
|
||||
//! <pre>
|
||||
//! <?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)
|
||||
class WebLog {
|
||||
private var callsbuffer = 4 as Lang.Number;
|
||||
private var callsBuffer = 4 as Lang.Number;
|
||||
private var numCalls = 0 as Lang.Number;
|
||||
private var buffer = "" as Lang.String;
|
||||
|
||||
// Set the number of calls to print() before sending the buffer to the online
|
||||
// logger.
|
||||
//! Set the number of calls to print() before sending the buffer to the online
|
||||
//! logger.
|
||||
//!
|
||||
//! @param l The number of log calls to buffer before writing to the online service.
|
||||
//
|
||||
function setCallsBuffer(l as Lang.Number) {
|
||||
callsbuffer = l;
|
||||
callsBuffer = l;
|
||||
}
|
||||
|
||||
// Get the number of calls to print() before sending the buffer to the online
|
||||
// logger.
|
||||
//! Get the number of calls to print() before sending the buffer to the online
|
||||
//! logger.
|
||||
//!
|
||||
//! @return The number of log calls to buffer before writing to the online service.
|
||||
//
|
||||
function getCallsBuffer() as Lang.Number {
|
||||
return callsbuffer;
|
||||
return callsBuffer;
|
||||
}
|
||||
|
||||
// Create a debug log over the Internet to keep track of the watch's runtime
|
||||
// execution.
|
||||
//! Create a debug log over the Internet to keep track of the watch's runtime
|
||||
//! execution.
|
||||
//!
|
||||
//! @param str The string to log.
|
||||
//
|
||||
function print(str as Lang.String) {
|
||||
var myTime = System.getClockTime();
|
||||
buffer += myTime.hour.format("%02d") + ":" + myTime.min.format("%02d") + ":" + myTime.sec.format("%02d") + " " + str;
|
||||
numCalls++;
|
||||
// System.println("WebLog print() str = " + str);
|
||||
if (numCalls >= callsbuffer) {
|
||||
if (numCalls >= callsBuffer) {
|
||||
doPrint();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a debug log over the Internet to keep track of the watch's runtime
|
||||
// execution. Add a new line character to the end.
|
||||
//! Create a debug log over the Internet to keep track of the watch's runtime
|
||||
//! execution. Add a new line character to the end.
|
||||
//!
|
||||
//! @param str The string to log.
|
||||
//
|
||||
function println(str as Lang.String) {
|
||||
print(str + "\n");
|
||||
}
|
||||
|
||||
// Flush the current buffer to the online logger even if it has not reach the
|
||||
// submission level set by 'callsbuffer'.
|
||||
//! Flush the current buffer to the online logger even if it has not reach the
|
||||
//! submission level set by 'callsBuffer'.
|
||||
//
|
||||
function flush() {
|
||||
// System.println("WebLog flush()");
|
||||
@ -101,7 +112,7 @@ class WebLog {
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the submission to the online logger.
|
||||
//! Perform the submission to the online logger.
|
||||
//
|
||||
function doPrint() {
|
||||
// System.println("WebLog doPrint()");
|
||||
@ -122,8 +133,8 @@ class WebLog {
|
||||
buffer = "";
|
||||
}
|
||||
|
||||
// Clear the debug log over the Internet to start a new track of the watch's runtime
|
||||
// execution.
|
||||
//! Clear the debug log over the Internet to start a new track of the watch's runtime
|
||||
//! execution.
|
||||
//
|
||||
function clear() {
|
||||
// System.println("WebLog clear()");
|
||||
@ -143,7 +154,10 @@ class WebLog {
|
||||
buffer = "";
|
||||
}
|
||||
|
||||
// Callback function to print the outcome of a doPrint() method.
|
||||
//! Callback function to print the outcome of a doPrint() method. Typically used for debugging this class.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
//
|
||||
function onLog(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||
// if (responseCode != 200) {
|
||||
@ -153,7 +167,10 @@ class WebLog {
|
||||
// }
|
||||
}
|
||||
|
||||
// Callback function to print the outcome of a clear() method.
|
||||
// Callback function to print the outcome of a clear() method. Typically used for debugging this class.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
//
|
||||
function onClear(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||
// if (responseCode != 200) {
|
||||
|
@ -11,14 +11,6 @@
|
||||
//
|
||||
// P A Abbey & J D Abbey, 10 January 2024
|
||||
//
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Home Assistant Webhook creation.
|
||||
//
|
||||
// Reference:
|
||||
// * https://developers.home-assistant.io/docs/api/native-app-integration
|
||||
//
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
using Toybox.Lang;
|
||||
@ -26,10 +18,24 @@ using Toybox.Communications;
|
||||
using Toybox.System;
|
||||
using Toybox.WatchUi;
|
||||
|
||||
// Can use push view so must never be run in a glance context
|
||||
//! Home Assistant Webhook creation.
|
||||
//!
|
||||
//! NB. Because we can use push view (E.g. `ErrorView.show()`) this class must never
|
||||
//! be run in a glance context.
|
||||
//!
|
||||
//! Reference: https://developers.home-assistant.io/docs/api/native-app-integration
|
||||
//
|
||||
class WebhookManager {
|
||||
|
||||
function onReturnRequestWebhookId(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
|
||||
//! Callback for requesting a Webhook ID.
|
||||
//!
|
||||
//! @param responseCode Response code
|
||||
//! @param data Return data
|
||||
//
|
||||
function onReturnRequestWebhookId(
|
||||
responseCode as Lang.Number,
|
||||
data as Null or Lang.Dictionary or Lang.String
|
||||
) as Void {
|
||||
switch (responseCode) {
|
||||
case Communications.BLE_HOST_TIMEOUT:
|
||||
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
||||
@ -84,6 +90,8 @@ class WebhookManager {
|
||||
}
|
||||
}
|
||||
|
||||
//! Request a Webhook ID from Home Assistant for use in this application.
|
||||
//
|
||||
function requestWebhookId() {
|
||||
var deviceSettings = System.getDeviceSettings();
|
||||
// System.println("WebhookManager requestWebhookId(): Requesting webhook id for device = " + deviceSettings.uniqueIdentifier);
|
||||
@ -115,7 +123,18 @@ class WebhookManager {
|
||||
);
|
||||
}
|
||||
|
||||
function onReturnRegisterWebhookSensor(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String, sensors as Lang.Array<Lang.Object>) as Void {
|
||||
//! Callback function for the POST request to register the watch's sensors on the Home Assistant instance.
|
||||
//!
|
||||
//! @param responseCode Response code.
|
||||
//! @param data Response data.
|
||||
//! @param sensors The remaining sensors to be processed. The list of sensors is iterated through
|
||||
//! until empty. Each POST request creating one sensor on the local Home Assistant.
|
||||
//
|
||||
function onReturnRegisterWebhookSensor(
|
||||
responseCode as Lang.Number,
|
||||
data as Null or Lang.Dictionary or Lang.String,
|
||||
sensors as Lang.Array<Lang.Object>
|
||||
) as Void {
|
||||
switch (responseCode) {
|
||||
case Communications.BLE_HOST_TIMEOUT:
|
||||
case Communications.BLE_CONNECTION_UNAVAILABLE:
|
||||
@ -194,7 +213,11 @@ class WebhookManager {
|
||||
}
|
||||
}
|
||||
|
||||
function registerWebhookSensor(sensors as Lang.Array<Lang.Object>) {
|
||||
//! Local method to send the POST request to register a number of sensors.
|
||||
//!
|
||||
//! @param sensors An array of sensors, e.g. As created by `registerWebhookSensors()`.
|
||||
//
|
||||
private function registerWebhookSensor(sensors as Lang.Array<Lang.Object>) {
|
||||
var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId();
|
||||
// System.println("WebhookManager registerWebhookSensor(): Registering webhook sensor: " + sensor.toString());
|
||||
// System.println("WebhookManager registerWebhookSensor(): URL=" + url);
|
||||
@ -217,6 +240,8 @@ class WebhookManager {
|
||||
);
|
||||
}
|
||||
|
||||
//! Request the creation of all the supported watch sensors on the Home Assistant instance.
|
||||
//
|
||||
function registerWebhookSensors() {
|
||||
var heartRate = Activity.getActivityInfo().currentHeartRate;
|
||||
|
||||
|
Reference in New Issue
Block a user