diff --git a/source/HomeAssistantMenuItemFactory.mc b/source/HomeAssistantMenuItemFactory.mc index 46e5f7b..1136d23 100644 --- a/source/HomeAssistantMenuItemFactory.mc +++ b/source/HomeAssistantMenuItemFactory.mc @@ -173,7 +173,7 @@ class HomeAssistantMenuItemFactory { } else { data.put("entity_id", entity_id); } - } + } var keys = mMenuItemOptions.keys(); for (var i = 0; i < keys.size(); i++) { options.put(keys[i], mMenuItemOptions.get(keys[i])); @@ -182,7 +182,6 @@ class HomeAssistantMenuItemFactory { return new HomeAssistantNumericMenuItem( label, - entity_id, template, service, data, diff --git a/source/HomeAssistantNumericMenuItem.mc b/source/HomeAssistantNumericMenuItem.mc index 2533228..e04fdd7 100644 --- a/source/HomeAssistantNumericMenuItem.mc +++ b/source/HomeAssistantNumericMenuItem.mc @@ -28,10 +28,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { private var mExit as Lang.Boolean; private var mPin as Lang.Boolean; private var mData as Lang.Dictionary?; - private var mStep as Lang.Float=1.0; - private var mValueChanged as Lang.Boolean = false; - private var mValue as Lang.Float?; - private var mEntity as Lang.String?; + private var mValue as Lang.String?; private var mFormatString as Lang.String="%.1f"; @@ -51,7 +48,6 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { // function initialize( label as Lang.String or Lang.Symbol, - entity as Lang.String, template as Lang.String, service as Lang.String?, data as Lang.Dictionary?, @@ -71,7 +67,6 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { mPin = options[:pin]; mLabel = label; mHomeAssistantService = haService; - mEntity = entity; HomeAssistantMenuItem.initialize( label, @@ -81,22 +76,11 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { :icon => options[:icon] } ); - - - - if (mData.get("step") != null) { - mStep = mData.get("step").toString().toFloat(); - } - - if (mData.get("formatString") != null) { - mFormatString=mData.get("formatString").toString(); - } - } + function callService() as Void { - if (!mValueChanged) { return; } var hasTouchScreen = System.getDeviceSettings().isTouchScreen; if (mPin && hasTouchScreen) { var pin = Settings.getPin(); @@ -145,87 +129,48 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { onConfirm(false); } } - + + //! 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, {"entity_id" => mEntity,mData.get("valueLabel").toString() => mValue}, mExit); - } + mHomeAssistantService.call(mService, {"entity_id" => mData.get("entity_id").toString(),mData.get("valueLabel").toString() => mValue}, mExit); + } - ///! Increase value when Up button is pressed or touch screen swipe down - - function increaseValue() as Void { - if (mValueChanged) - { - mValue += mStep; - } - else { - mValue= getSubLabel().toFloat() + mStep; - mValueChanged=true; - } - setSubLabel(mValue.format(mFormatString)); - } - - ///! Decrease value when Down button is pressed or touch screen swipe up - function decreaseValue() as Void { - if (mValueChanged) - { - mValue -= mStep; - } - else { - mValue= getSubLabel().toFloat() - mStep; - mValueChanged=true; - } - setSubLabel(mValue.format(mFormatString)); - } - + //! 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 vlue has changed, don't use value from HomeAssitant but display target value - if (mValueChanged) { - setSubLabel(mValue.format(mFormatString)); - WatchUi.requestUpdate(); - return; - } if (data == null) { setSubLabel($.Rez.Strings.Empty); - } else if(data instanceof Lang.String) { - setSubLabel(data); - } else if(data instanceof Lang.Number) { - var d = data as Lang.Number; - setSubLabel(d.format("%d")); } else if(data instanceof Lang.Float) { var f = data as Lang.Float; setSubLabel(f.format(mFormatString)); - } else if(data instanceof Lang.Dictionary) { - // System.println("HomeAssistantMenuItem updateState() data = " + data); - if (data.get("error") != null) { - setSubLabel($.Rez.Strings.TemplateError); - } else { - setSubLabel($.Rez.Strings.PotentialError); - } - } else { - // The template must return a Lang.String, Number or Float, or the item cannot be formatted locally without error. + } else { + // The template must return a Float, or the item cannot be formatted locally without error. setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); } WatchUi.requestUpdate(); } - //! Set the mValuChanged value. + //! Set the mValue value. //! - //! Can be used to reenable update of subLabel + //! Needed to set new value via the Service call // - function setValueChanged(b as Lang.Boolean) as Void { - mValueChanged = b; + function setValue(value as Lang.String) as Void { + mValue = value; } + + function getData() as Lang.Dictionary { + return mData; + } + } diff --git a/source/HomeAssistantNumericView.mc b/source/HomeAssistantNumericView.mc deleted file mode 100644 index 2d874dc..0000000 --- a/source/HomeAssistantNumericView.mc +++ /dev/null @@ -1,212 +0,0 @@ -//----------------------------------------------------------------------------------- -// -// Distributed under MIT Licence -// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE. -// -//----------------------------------------------------------------------------------- -// -// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely -// tested on a Venu 2 device. The source code is provided at: -// https://github.com/house-of-abbey/GarminHomeAssistant. -// -// P A Abbey & J D Abbey & Someone0nEarth & moesterheld, 31 October 2023 -// -//----------------------------------------------------------------------------------- - -using Toybox.Application; -using Toybox.Lang; -using Toybox.Graphics; -using Toybox.System; -using Toybox.WatchUi; - - -using Toybox.Application.Properties; -using Toybox.Timer; - - -//! Home Assistant menu construction. -// -class HomeAssistantNumericView extends WatchUi.Menu2 { - - private var mMenuItem as HomeAssistantNumericMenuItem; - - - //! Class Constructor - // - function initialize( - menuItem as HomeAssistantNumericMenuItem - - ) { - mMenuItem = menuItem; - - WatchUi.Menu2.initialize({:title => mMenuItem.getLabel()}); - - addItem(mMenuItem); - - //updateState(mData); - - } - - //! Return the menu item - //! - //! @return A HomeAssitantTapMenuItem (or null). - // - function getMenuItem() as HomeAssistantNumericMenuItem? { - return mMenuItem; - } - - //! 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) { - mMenuItem.setSubLabel($.Rez.Strings.Empty); - } else if(data instanceof Lang.String) { - mMenuItem.setSubLabel(data); - } else if(data instanceof Lang.Number) { - var d = data as Lang.Number; - mMenuItem.setSubLabel(d.format("%d")); - } else if(data instanceof Lang.Float) { - var f = data as Lang.Float; - mMenuItem.setSubLabel(f.format("%f")); - } else if(data instanceof Lang.Dictionary) { - // System.println("HomeAssistantMenuItem updateState() data = " + data); - if (data.get("error") != null) { - mMenuItem.setSubLabel($.Rez.Strings.TemplateError); - } else { - mMenuItem.setSubLabel($.Rez.Strings.PotentialError); - } - } else { - // The template must return a Lang.String, Number or Float, or the item cannot be formatted locally without error. - mMenuItem.setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); - } - WatchUi.requestUpdate(); - } - - - //! 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 { - var fullList = []; - var lmi = mItems as Lang.Array; - - for(var i = 0; i < mItems.size(); i++) { - var item = lmi[i]; - if (item instanceof HomeAssistantGroupMenuItem) { - // Group menu items can now have an optional template to evaluate - var gmi = item as HomeAssistantGroupMenuItem; - if (gmi.hasTemplate()) { - fullList.add(item); - } - fullList.addAll(item.getMenuView().getItemsToUpdate()); - } else if (item instanceof HomeAssistantToggleMenuItem) { - fullList.add(item); - } else if (item instanceof HomeAssistantTapMenuItem) { - var tmi = item as HomeAssistantTapMenuItem; - if (tmi.hasTemplate()) { - fullList.add(item); - } - } - } - - 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. - function onShow() as Void {} -} - - -//! Delegate for the HomeAssistantView. -//! -//! Reference: https://developer.garmin.com/connect-iq/core-topics/input-handling/ -// -class HomeAssistantNumericViewDelegate extends WatchUi.Menu2InputDelegate { - private var mIsRootMenuView as Lang.Boolean = false; - private var mTimer as QuitTimer; - private var mItem as HomeAssistantNumericMenuItem; - - //! 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. - //tap - function initialize(isRootMenuView as Lang.Boolean, item as HomeAssistantNumericMenuItem) { - Menu2InputDelegate.initialize(); - mIsRootMenuView = isRootMenuView; - mTimer = getApp().getQuitTimer(); - mItem = item; - } - - //! Handle the back button (ESC) - // - function onBack() { - mTimer.reset(); - - mItem.setValueChanged(false); - - if (mIsRootMenuView) { - // If its started from glance or as an activity, directly exit the widget/app - // (on widgets without glance, this exit() won't do anything, - // so the base view will be shown instead, through the popView below this "if body") - System.exit(); - } - - WatchUi.popView(WatchUi.SLIDE_RIGHT); - } - - //! Only for CheckboxMenu - // - function onDone() { - mTimer.reset(); - } - - //! Only for CustomMenu - // - function onFooter() { - mTimer.reset(); - } - - // Decrease Value - function onNextPage() as Lang.Boolean { - mItem.decreaseValue(); - return true; - } - //Increase Value - function onPreviousPage() as Lang.Boolean { - mItem.increaseValue(); - return true; - } - - - //! Select event - //! - //! @param item Selected menu item. - // - function onSelect(item as WatchUi.MenuItem) as Void { - mTimer.reset(); - mItem.callService(); - WatchUi.popView(WatchUi.SLIDE_RIGHT); - return; - } - - //! Only for CustomMenu - // - function onTitle() { - mTimer.reset(); - } - - -} - - diff --git a/source/HomeAssistantView.mc b/source/HomeAssistantView.mc index ce028b0..a9f4d9a 100644 --- a/source/HomeAssistantView.mc +++ b/source/HomeAssistantView.mc @@ -271,12 +271,18 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate { } else if (item instanceof HomeAssistantNumericMenuItem) { var haItem = item as HomeAssistantNumericMenuItem; // System.println(haItem.getLabel() + " " + haItem.getId()); - // create new view to select new valu - var numView = new HomeAssistantNumericView(haItem); - WatchUi.pushView(numView, new HomeAssistantNumericViewDelegate(false,haItem), WatchUi.SLIDE_LEFT); + // create new view to select new value + + var mPickerFactory = new HomeAssistantNumericFactory(haItem.getData()); + + var mPicker = new HomeAssistantNumericPicker(mPickerFactory,haItem);//{:pattern => [mPickerFactory}); + var mPickerDelegate = new HomeAssistantNumericPickerDelegate(mPicker); + WatchUi.pushView(mPicker,mPickerDelegate,WatchUi.SLIDE_LEFT); + //WatchUi.pushView(numView, new HomeAssistantNumericViewDelegate(false,haItem), WatchUi.SLIDE_LEFT); } else if (item instanceof HomeAssistantGroupMenuItem) { var haMenuItem = item as HomeAssistantGroupMenuItem; // System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId()); + WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT); // } else { // System.println(item.getLabel() + " " + item.getId()); diff --git a/source/factory/HomeAssistantNumericFactory.mc b/source/factory/HomeAssistantNumericFactory.mc new file mode 100644 index 0000000..8f16b6f --- /dev/null +++ b/source/factory/HomeAssistantNumericFactory.mc @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------------- +// +// Distributed under MIT Licence +// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE. +// +//----------------------------------------------------------------------------------- +// +// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely +// tested on a Venu 2 device. The source code is provided at: +// https://github.com/house-of-abbey/GarminHomeAssistant. +// +// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 +// +//------------------------------------------------------------ + +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +//! Factory that controls which numbers can be picked +class HomeAssistantNumericFactory extends WatchUi.PickerFactory { + // define default values in case not contained in data + private var mStart as Lang.Float = 0.0; + private var mStop as Lang.Float = 100.0; + private var mStep as Lang.Float = 1.0; + private var mFormatString as Lang.String = "%.2f"; + + //! Class Constructor + //! + public function initialize(data as Lang.Dictionary) { + PickerFactory.initialize(); + + // Get values from data + + var val = data.get("start"); + if (val != null) { + mStart = val.toString().toFloat(); + } + val = data.get("stop"); + if (val != null) { + mStop = val.toString().toFloat(); + } + val = data.get("step"); + if (val != null) { + mStep = val.toString().toFloat(); + } + val = data.get("formatString"); + if (val != null) { + mFormatString = val.toString(); + } + + } + + //! Get the index of a number item + //! @param value The number to get the index of + //! @return The index of the number + public function getIndex(value as Float) as Number { + return ((value / mStep) - mStart).toNumber(); + } + + //! Generate a Drawable instance for an item + //! @param index The item index + //! @param selected true if the current item is selected, false otherwise + //! @return Drawable for the item + public function getDrawable(index as Number, selected as Boolean) as Drawable? { + var value = getValue(index); + var text = "No item"; + if (value instanceof Float) { + text = value.format(mFormatString); + } + return new WatchUi.Text({:text=>text, :color=>Graphics.COLOR_WHITE, + :locX=>WatchUi.LAYOUT_HALIGN_CENTER, :locY=>WatchUi.LAYOUT_VALIGN_CENTER}); + } + + //! Get the value of the item at the given index + //! @param index Index of the item to get the value of + //! @return Value of the item + public function getValue(index as Number) as Object? { + return mStart + (index * mStep); + } + + //! Get the number of picker items + //! @return Number of items + public function getSize() as Number { + return ((mStop - mStart) / mStep).toNumber() + 1; + } + +} diff --git a/source/picker/HomeAssistantNumericPicker.mc b/source/picker/HomeAssistantNumericPicker.mc new file mode 100644 index 0000000..a8d7317 --- /dev/null +++ b/source/picker/HomeAssistantNumericPicker.mc @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------------- +// +// Distributed under MIT Licence +// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE. +// +//----------------------------------------------------------------------------------- +// +// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely +// tested on a Venu 2 device. The source code is provided at: +// https://github.com/house-of-abbey/GarminHomeAssistant. +// +// P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 +// +//------------------------------------------------------------ + +using Toybox.Application; +using Toybox.Lang; +using Toybox.Graphics; +using Toybox.System; +using Toybox.WatchUi; + +//! Picker that allows the user to choose a float value +class HomeAssistantNumericPicker extends WatchUi.Picker { + + private var mFactory as HomeAssistantNumericFactory; + private var mItem as HomeAssistantNumericMenuItem; + + //! Constructor + public function initialize(factory as HomeAssistantNumericFactory, haItem as HomeAssistantNumericMenuItem) { + + mFactory = factory; + + + var pickerOptions = {:pattern=>[mFactory]}; + mItem=haItem; + + + var data = mItem.getData(); + + var start = 0.0; + var val = data.get("start"); + if (val != null) { + start = val.toString().toFloat(); + } + var step = 1.0; + val = data.get("step"); + if (val != null) { + step = val.toString().toFloat(); + } + val = haItem.getSubLabel().toFloat(); + var index = ((val -start) / step).toNumber(); + + pickerOptions[:defaults] =[index]; + + var title = new WatchUi.Text({:text=>haItem.getLabel(), :locX=>WatchUi.LAYOUT_HALIGN_CENTER, + :locY=>WatchUi.LAYOUT_VALIGN_BOTTOM}); + pickerOptions[:title] = title; + + + Picker.initialize(pickerOptions); + + } + + + //! Get whether the user is done picking + //! @param value Value user selected + //! @return true if user is done, false otherwise + public function onConfirm(value as Lang.String) as Void { + mItem.setValue(value); + mItem.callService(); + } + + +} + +//! Responds to a numeric picker selection or cancellation +class HomeAssistantNumericPickerDelegate extends WatchUi.PickerDelegate { + private var mPicker as HomeAssistantNumericPicker; + + //! Constructor + public function initialize(picker as HomeAssistantNumericPicker) { + PickerDelegate.initialize(); + mPicker = picker; + } + + //! Handle a cancel event from the picker + //! @return true if handled, false otherwise + public function onCancel() as Lang.Boolean { + WatchUi.popView(WatchUi.SLIDE_RIGHT); + return true; + } + + //! Handle a confirm event from the picker + //! @param values The values chosen in the picker + //! @return true if handled, false otherwise + public function onAccept(values as Lang.Array) as Lang.Boolean { + var chosenValue = values[0].toString(); + mPicker.onConfirm(chosenValue); + WatchUi.popView(WatchUi.SLIDE_RIGHT); + return true; + } +}