mirror of
				https://github.com/house-of-abbey/GarminHomeAssistant.git
				synced 2025-10-31 15:48:13 +00:00 
			
		
		
		
	Merge pull request #4 from house-of-abbey/Picker-formatter
Suggested code changes from philipabbey
This commit is contained in:
		| @@ -283,50 +283,14 @@ | |||||||
|             "exit": { |             "exit": { | ||||||
|               "$ref": "#/$defs/exit" |               "$ref": "#/$defs/exit" | ||||||
|             }, |             }, | ||||||
|             "min": { |  | ||||||
|               "type": "number", |  | ||||||
|               "title": "Minimum Value" |  | ||||||
|             }, |  | ||||||
|             "max": { |  | ||||||
|               "type": "number", |  | ||||||
|               "title": "Maximum Value" |  | ||||||
|             }, |  | ||||||
|             "step": { |  | ||||||
|               "type": "number", |  | ||||||
|               "title": "Step Size" |  | ||||||
|             }, |  | ||||||
|             "display_format": { |  | ||||||
|               "type": "string", |  | ||||||
|               "title": "Display Format", |  | ||||||
|               "description": "A C-Style format string for displaying the value in the UI. https://developer.garmin.com/connect-iq/api-docs/Toybox/Lang/Number.html#format-instance_function", |  | ||||||
|               "default": "%.1f" |  | ||||||
|             }, |  | ||||||
|             "entity": { |             "entity": { | ||||||
|               "$ref": "#/$defs/entity" |               "$ref": "#/$defs/entity" | ||||||
|             }, |  | ||||||
|             "attribute": { |  | ||||||
|               "type": "string", |  | ||||||
|               "title": "Attribute on the entity", |  | ||||||
|               "description": "Attribute on the entity with the current numeric value. To use the state of the entity, do not specify." |  | ||||||
|             }, |  | ||||||
|             "service": { |  | ||||||
|               "$ref": "#/$defs/service" |  | ||||||
|             }, |  | ||||||
|             "data_attribute": { |  | ||||||
|               "type": "string", |  | ||||||
|               "title": "Attribute on the service data", |  | ||||||
|               "description": "Attribute on the service data for the value to set." |  | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           "required": [ |           "required": [ | ||||||
|             "name", |             "name", | ||||||
|             "type", |             "type", | ||||||
|             "min", |             "entity" | ||||||
|             "max", |  | ||||||
|             "step", |  | ||||||
|             "entity", |  | ||||||
|             "service", |  | ||||||
|             "data_attribute" |  | ||||||
|           ], |           ], | ||||||
|           "additionalProperties": false |           "additionalProperties": false | ||||||
|         }, |         }, | ||||||
| @@ -816,6 +780,9 @@ | |||||||
|       "title": "Action", |       "title": "Action", | ||||||
|       "description": "'confirm' field is optional.", |       "description": "'confirm' field is optional.", | ||||||
|       "properties": { |       "properties": { | ||||||
|  |         "picker": { | ||||||
|  |           "$ref": "#/$defs/picker" | ||||||
|  |         }, | ||||||
|         "confirm": { |         "confirm": { | ||||||
|           "$ref": "#/$defs/confirm" |           "$ref": "#/$defs/confirm" | ||||||
|         }, |         }, | ||||||
| @@ -824,6 +791,41 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "picker": { | ||||||
|  |       "type": "object", | ||||||
|  |       "title": "Number picker configuration", | ||||||
|  |       "description": "'attribute' field is optional.", | ||||||
|  |       "properties": { | ||||||
|  |         "min": { | ||||||
|  |           "type": "number", | ||||||
|  |           "title": "Minimum Value" | ||||||
|  |         }, | ||||||
|  |         "max": { | ||||||
|  |           "type": "number", | ||||||
|  |           "title": "Maximum Value" | ||||||
|  |         }, | ||||||
|  |         "step": { | ||||||
|  |           "type": "number", | ||||||
|  |           "title": "Step Size" | ||||||
|  |         }, | ||||||
|  |         "attribute": { | ||||||
|  |           "type": "string", | ||||||
|  |           "title": "Attribute on the entity", | ||||||
|  |           "description": "Attribute on the entity with the current numeric value. To use the state of the entity, do not specify." | ||||||
|  |         }, | ||||||
|  |         "data_attribute": { | ||||||
|  |           "type": "string", | ||||||
|  |           "title": "Attribute on the service data", | ||||||
|  |           "description": "Attribute on the service data for the value to set." | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "required": [ | ||||||
|  |         "min", | ||||||
|  |         "max", | ||||||
|  |         "step", | ||||||
|  |         "data_attribute" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|     "content": { |     "content": { | ||||||
|       "title": "Home Assistant Template", |       "title": "Home Assistant Template", | ||||||
|       "description": "Jinja2 template defining the text to display. Must be included in an 'info'. Optional in a 'toggle', 'tap' and 'group'. Special characters may not render in the glance context.", |       "description": "Jinja2 template defining the text to display. Must be included in an 'info'. Optional in a 'toggle', 'tap' and 'group'. Special characters may not render in the glance context.", | ||||||
|   | |||||||
| @@ -630,10 +630,10 @@ class HomeAssistantApp extends Application.AppBase { | |||||||
|                                 (item as HomeAssistantToggleMenuItem).updateToggleState(data[i.toString() + "t"]); |                                 (item as HomeAssistantToggleMenuItem).updateToggleState(data[i.toString() + "t"]); | ||||||
|                             } |                             } | ||||||
|                             if (item instanceof HomeAssistantNumericMenuItem) { |                             if (item instanceof HomeAssistantNumericMenuItem) { | ||||||
|                                 if (data[i.toString() + "n"] != null) |                                var s = data[i.toString() + "n"]; | ||||||
|                                 { |                                if ((s instanceof Lang.Number) or (s instanceof Lang.Float)) { | ||||||
|                                     (item as HomeAssistantNumericMenuItem).updateNumericState(data[i.toString() + "n"].toString()); |                                    (item as HomeAssistantNumericMenuItem).setValue(s); | ||||||
|                                 } |                                } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                         if (Settings.getMenuCheck() && Settings.getCacheConfig() && !mIsCacheChecked) { |                         if (Settings.getMenuCheck() && Settings.getCacheConfig() && !mIsCacheChecked) { | ||||||
| @@ -833,7 +833,7 @@ class HomeAssistantApp extends Application.AppBase { | |||||||
|         var phoneConnected = System.getDeviceSettings().phoneConnected; |         var phoneConnected = System.getDeviceSettings().phoneConnected; | ||||||
|         var connectionAvailable = System.getDeviceSettings().connectionAvailable; |         var connectionAvailable = System.getDeviceSettings().connectionAvailable; | ||||||
|  |  | ||||||
|         // System.println("API URL = " + Settings.getApiUrl()); |         // System.println("HomeAssistantApp fetchApiStatus(): API URL = " + Settings.getApiUrl()); | ||||||
|         if (Settings.getApiUrl().equals("")) { |         if (Settings.getApiUrl().equals("")) { | ||||||
|             mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String; |             mApiStatus = WatchUi.loadResource($.Rez.Strings.Unconfigured) as Lang.String; | ||||||
|             WatchUi.requestUpdate(); |             WatchUi.requestUpdate(); | ||||||
|   | |||||||
| @@ -158,7 +158,7 @@ class HomeAssistantMenuItemFactory { | |||||||
|         entity_id as Lang.String?, |         entity_id as Lang.String?, | ||||||
|         template  as Lang.String?, |         template  as Lang.String?, | ||||||
|         service   as Lang.String?, |         service   as Lang.String?, | ||||||
|         data      as Lang.Dictionary?, |         picker    as Lang.Dictionary, | ||||||
|         options   as { |         options   as { | ||||||
|             :exit    as Lang.Boolean, |             :exit    as Lang.Boolean, | ||||||
|             :confirm as Lang.Boolean, |             :confirm as Lang.Boolean, | ||||||
| @@ -166,25 +166,21 @@ class HomeAssistantMenuItemFactory { | |||||||
|             :icon    as WatchUi.Bitmap |             :icon    as WatchUi.Bitmap | ||||||
|         } |         } | ||||||
|     ) as WatchUi.MenuItem { |     ) as WatchUi.MenuItem { | ||||||
|  |         var data = null; | ||||||
|         if (entity_id != null) { |         if (entity_id != null) { | ||||||
|             if (data == null) { |             data = { "entity_id" => entity_id }; | ||||||
|                 data = { "entity_id" => entity_id }; |  | ||||||
|                  |  | ||||||
|             } else { |  | ||||||
|                 data.put("entity_id", entity_id); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         var keys = mMenuItemOptions.keys(); |         var keys = mMenuItemOptions.keys(); | ||||||
|         for (var i = 0; i < keys.size(); i++) { |         for (var i = 0; i < keys.size(); i++) { | ||||||
|             options.put(keys[i], mMenuItemOptions.get(keys[i])); |             options.put(keys[i], mMenuItemOptions.get(keys[i])); | ||||||
|         } |         } | ||||||
|         options.put(:icon, mTapTypeIcon); |         options.put(:icon, mTapTypeIcon); | ||||||
|          |  | ||||||
|         return new HomeAssistantNumericMenuItem( |         return new HomeAssistantNumericMenuItem( | ||||||
|             label, |             label, | ||||||
|             template, |             template, | ||||||
|             service, |             service, | ||||||
|             data, |             data, | ||||||
|  |             picker, | ||||||
|             options, |             options, | ||||||
|             mHomeAssistantService |             mHomeAssistantService | ||||||
|         ); |         ); | ||||||
|   | |||||||
| @@ -9,84 +9,92 @@ | |||||||
| // tested on a Venu 2 device. The source code is provided at: | // tested on a Venu 2 device. The source code is provided at: | ||||||
| //            https://github.com/house-of-abbey/GarminHomeAssistant. | //            https://github.com/house-of-abbey/GarminHomeAssistant. | ||||||
| // | // | ||||||
| // P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 | // P A Abbey & J D Abbey & @thmichel, 13 October 2025 | ||||||
| // | // | ||||||
| //------------------------------------------------------------ | //------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| import Toybox.Graphics; | using Toybox.Graphics; | ||||||
| import Toybox.Lang; | using Toybox.Lang; | ||||||
| import Toybox.WatchUi; | using Toybox.WatchUi; | ||||||
| 
 | 
 | ||||||
| //! Factory that controls which numbers can be picked | //! Factory that controls which numbers can be picked | ||||||
| class HomeAssistantNumericFactory extends WatchUi.PickerFactory { | class HomeAssistantNumericFactory extends WatchUi.PickerFactory { | ||||||
|     // define default values in case not contained in data |     // define default values in case not contained in data | ||||||
|     private var mStart as Lang.Float = 0.0; |     private var mStart        as Lang.Float  = 0.0; | ||||||
|     private var mStop as Lang.Float = 100.0; |     private var mStop         as Lang.Float  = 100.0; | ||||||
|     private var mStep as Lang.Float = 1.0; |     private var mStep         as Lang.Float  = 1.0; | ||||||
|     private var mFormatString as Lang.String = "%.2f"; |     private var mFormatString as Lang.String = "%d"; | ||||||
| 
 | 
 | ||||||
|     //! Class Constructor |     //! Class Constructor | ||||||
|     //! |     // | ||||||
|     public function initialize(data as Lang.Dictionary) { |     public function initialize(picker as Lang.Dictionary) { | ||||||
|         PickerFactory.initialize(); |         PickerFactory.initialize(); | ||||||
| 
 | 
 | ||||||
|         // Get values from data |         // Get values from data | ||||||
| 
 |         var val = picker["min"]; | ||||||
|         var val = data.get("min"); |  | ||||||
|         if (val != null) { |         if (val != null) { | ||||||
|             mStart = val.toString().toFloat(); |             mStart = val.toString().toFloat(); | ||||||
|         } |         } | ||||||
|         val = data.get("max"); |         val = picker["max"]; | ||||||
|         if (val != null) { |         if (val != null) { | ||||||
|             mStop = val.toString().toFloat(); |             mStop = val.toString().toFloat(); | ||||||
|         }  |         }  | ||||||
|         val = data.get("step"); |         val = picker["step"]; | ||||||
|         if (val != null) { |         if (val != null) { | ||||||
|             mStep = val.toString().toFloat(); |             mStep = val.toString().toFloat(); | ||||||
|         }  |         }  | ||||||
|         if (mStep < 0.01) { |         if (mStep > 0.0) { | ||||||
|             mFormatString="%.3f"; |             var s = mStep; | ||||||
|         } else if (mStep < 0.1) { |             var dp = 0; | ||||||
|             mFormatString="%.2f"; |             while (s < 1.0) { | ||||||
|         } else if (mStep < 1) { |                 s *= 10; | ||||||
|             mFormatString="%.1f"; |                 dp++; | ||||||
|  |                 // Assigned inside the loop and in each iteration to avoid clobbering the default '%d'. | ||||||
|  |                 mFormatString = "%." + dp.toString() + "f"; | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|             mFormatString="%d"; |             // The JSON menu definition defined a step size of 0, revert to the default. | ||||||
|  |             mStep = 1.0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     //! 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 |     //! Generate a Drawable instance for an item | ||||||
|  |     //! | ||||||
|     //! @param index The item index |     //! @param index The item index | ||||||
|     //! @param selected true if the current item is selected, false otherwise |     //! @param selected true if the current item is selected, false otherwise | ||||||
|     //! @return Drawable for the item |     //! @return Drawable for the item | ||||||
|     public function getDrawable(index as Number, selected as Boolean) as Drawable? { |     // | ||||||
|  |     public function getDrawable( | ||||||
|  |         index    as Lang.Number, | ||||||
|  |         selected as Lang.Boolean | ||||||
|  |     ) as WatchUi.Drawable? { | ||||||
|         var value = getValue(index); |         var value = getValue(index); | ||||||
|         var text = "No item"; |         var text = "No item"; | ||||||
|         if (value instanceof Lang.Float) { |         if (value instanceof Lang.Float) { | ||||||
|             text = value.format(mFormatString); |             text = value.format(mFormatString); | ||||||
|         } |         } | ||||||
|         return new WatchUi.Text({:text=>text, :color=>Graphics.COLOR_WHITE,  |         return new WatchUi.Text({ | ||||||
|             :locX=>WatchUi.LAYOUT_HALIGN_CENTER, :locY=>WatchUi.LAYOUT_VALIGN_CENTER}); |             :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 |     //! Get the value of the item at the given index | ||||||
|  |     //! | ||||||
|     //! @param index Index of the item to get the value of |     //! @param index Index of the item to get the value of | ||||||
|     //! @return Value of the item |     //! @return Value of the item | ||||||
|     public function getValue(index as Number) as Object? { |     // | ||||||
|  |     public function getValue(index as Lang.Number) as Lang.Object? { | ||||||
|         return mStart + (index * mStep); |         return mStart + (index * mStep); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     //! Get the number of picker items |     //! Get the number of picker items | ||||||
|  |     //! | ||||||
|     //! @return Number of items |     //! @return Number of items | ||||||
|     public function getSize() as Number { |     // | ||||||
|  |     public function getSize() as Lang.Number { | ||||||
|         return ((mStop - mStart) / mStep).toNumber() + 1; |         return ((mStop - mStart) / mStep).toNumber() + 1; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| } | } | ||||||
| @@ -9,7 +9,7 @@ | |||||||
| // tested on a Venu 2 device. The source code is provided at: | // tested on a Venu 2 device. The source code is provided at: | ||||||
| //            https://github.com/house-of-abbey/GarminHomeAssistant. | //            https://github.com/house-of-abbey/GarminHomeAssistant. | ||||||
| // | // | ||||||
| // P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 | // P A Abbey & J D Abbey & @thmichel, 13 October 2025 | ||||||
| // | // | ||||||
| //----------------------------------------------------------------------------------- | //----------------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| @@ -17,7 +17,6 @@ using Toybox.Lang; | |||||||
| using Toybox.WatchUi; | using Toybox.WatchUi; | ||||||
| using Toybox.Graphics; | using Toybox.Graphics; | ||||||
|  |  | ||||||
|  |  | ||||||
| //! Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders | //! Menu button with an icon that opens a sub-menu, i.e. group, and optionally renders | ||||||
| //! a Home Assistant Template. | //! a Home Assistant Template. | ||||||
| // | // | ||||||
| @@ -28,9 +27,9 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { | |||||||
|     private var mExit                 as Lang.Boolean; |     private var mExit                 as Lang.Boolean; | ||||||
|     private var mPin                  as Lang.Boolean; |     private var mPin                  as Lang.Boolean; | ||||||
|     private var mData                 as Lang.Dictionary?; |     private var mData                 as Lang.Dictionary?; | ||||||
|     private var mValue                as Lang.String?;   |     private var mPicker               as Lang.Dictionary?; | ||||||
|     private var mFormatString         as Lang.String="%.1f"; |     private var mValue                as Lang.Number or Lang.Float = 0; | ||||||
|  |     private var mFormatString         as Lang.String = "%d"; | ||||||
|  |  | ||||||
|     //! Class Constructor |     //! Class Constructor | ||||||
|     //! |     //! | ||||||
| @@ -51,6 +50,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { | |||||||
|         template  as Lang.String, |         template  as Lang.String, | ||||||
|         service   as Lang.String?, |         service   as Lang.String?, | ||||||
|         data      as Lang.Dictionary?, |         data      as Lang.Dictionary?, | ||||||
|  |         picker    as Lang.Dictionary, | ||||||
|         options   as { |         options   as { | ||||||
|             :alignment as WatchUi.MenuItem.Alignment, |             :alignment as WatchUi.MenuItem.Alignment, | ||||||
|             :icon      as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol, |             :icon      as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol, | ||||||
| @@ -62,6 +62,7 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { | |||||||
|     ) { |     ) { | ||||||
|         mService              = service; |         mService              = service; | ||||||
|         mData                 = data; |         mData                 = data; | ||||||
|  |         mPicker               = picker; | ||||||
|         mExit                 = options[:exit]; |         mExit                 = options[:exit]; | ||||||
|         mConfirm              = options[:confirm]; |         mConfirm              = options[:confirm]; | ||||||
|         mPin                  = options[:pin]; |         mPin                  = options[:pin]; | ||||||
| @@ -76,10 +77,22 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { | |||||||
|                 :icon      => options[:icon] |                 :icon      => options[:icon] | ||||||
|             } |             } | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|  |         if (picker != null) { | ||||||
|  |             var s = picker["step"]; | ||||||
|  |             if (s != null) { | ||||||
|  |                 var step = s.toFloat() as Lang.Float; | ||||||
|  |                 var dp = 0; | ||||||
|  |                 while (step < 1.0) { | ||||||
|  |                     step *= 10; | ||||||
|  |                     dp++; | ||||||
|  |                     // Assigned inside the loop and in each iteration to avoid clobbering the default '%d'. | ||||||
|  |                     mFormatString = "%." + dp.toString() + "f"; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     function callService() as Void { |     function callService() as Void { | ||||||
|         var hasTouchScreen = System.getDeviceSettings().isTouchScreen; |         var hasTouchScreen = System.getDeviceSettings().isTouchScreen; | ||||||
|         if (mPin && hasTouchScreen) { |         if (mPin && hasTouchScreen) { | ||||||
| @@ -89,10 +102,10 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { | |||||||
|                 WatchUi.pushView( |                 WatchUi.pushView( | ||||||
|                     pinConfirmationView, |                     pinConfirmationView, | ||||||
|                     new HomeAssistantPinConfirmationDelegate({ |                     new HomeAssistantPinConfirmationDelegate({ | ||||||
|                         :callback    => method(:onConfirm), |                         :callback => method(:onConfirm), | ||||||
|                         :pin         => pin,  |                         :pin      => pin, | ||||||
|                         :state       => false, |                         :state    => false, | ||||||
|                         :view        => pinConfirmationView, |                         :view     => pinConfirmationView, | ||||||
|                     }), |                     }), | ||||||
|                     WatchUi.SLIDE_IMMEDIATE |                     WatchUi.SLIDE_IMMEDIATE | ||||||
|                 ); |                 ); | ||||||
| @@ -130,65 +143,60 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     |  | ||||||
|  |  | ||||||
|     //! Callback function after the menu items selection has been (optionally) confirmed. |     //! 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. |     //! @param b Ignored. It is included in order to match the expected function prototype of the callback method. | ||||||
|     // |     // | ||||||
|     function onConfirm(b as Lang.Boolean) as Void { |     function onConfirm(b as Lang.Boolean) as Void { | ||||||
|         //mHomeAssistantService.call(mService, {"entity_id"  => mData.get("entity_id").toString(),mData.get("valueLabel").toString() => mValue}, mExit); |         var dataAttribute = mPicker["data_attribute"]; | ||||||
|         var dataAttribute = mData.get("data_attribute"); |  | ||||||
|         if (dataAttribute == null) { |         if (dataAttribute == null) { | ||||||
|             //return without call service if no data attribute is set to avoid crash |             //return without call service if no data attribute is set to avoid crash | ||||||
|             WatchUi.popView(WatchUi.SLIDE_RIGHT); |             WatchUi.popView(WatchUi.SLIDE_RIGHT); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         var entity_id = mData.get("entity_id"); |         var entity_id = mData["entity_id"]; | ||||||
|         if (entity_id == null) { |         if (entity_id == null) { | ||||||
|             //return without call service if no entity_id is set to avoid crash |             //return without call service if no entity_id is set to avoid crash | ||||||
|             WatchUi.popView(WatchUi.SLIDE_RIGHT); |             WatchUi.popView(WatchUi.SLIDE_RIGHT); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         mHomeAssistantService.call(mService, {"entity_id"  => entity_id.toString(),dataAttribute.toString() => mValue}, mExit); |         mHomeAssistantService.call( | ||||||
|  |             mService, | ||||||
|  |             { | ||||||
|  |                 "entity_id"              => entity_id.toString(), | ||||||
|  |                 dataAttribute.toString() => mValue | ||||||
|  |             }, | ||||||
|  |             mExit | ||||||
|  |         ); | ||||||
|         WatchUi.popView(WatchUi.SLIDE_RIGHT); |         WatchUi.popView(WatchUi.SLIDE_RIGHT); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     //! Return a toggle menu item's state template. |     //! Return a numeric menu item's fetch state template. | ||||||
|     //! |     //! | ||||||
|     //! @return A string with the menu item's template definition (or null). |     //! @return A string with the menu item's template definition (or null). | ||||||
|     // |     // | ||||||
|     function getNumericTemplate() as Lang.String? { |     function getNumericTemplate() as Lang.String? { | ||||||
|         var entity_id = mData.get("entity_id"); |         var entity_id = mData["entity_id"]; | ||||||
|         if (entity_id != null) { |         var attribute = mPicker["attribute"] as Lang.String; | ||||||
|             if (mData.get("attribute")!=null) |         if (entity_id == null) { | ||||||
|             { |             return null; | ||||||
|                 return "{{state_attr('" + entity_id.toString() + "','" + mData.get("attribute").toString() +"')}}"; |  | ||||||
|             } |  | ||||||
|             return ""; |  | ||||||
|        } |  | ||||||
|         return ""; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     function updateNumericState(data as Lang.String or Lang.Dictionary or Null) as Void { |  | ||||||
|         if (data == null) { |  | ||||||
|             mValue="0"; |  | ||||||
|             return; |  | ||||||
|         } else if(data instanceof Lang.String) { |  | ||||||
|             mValue=data; |  | ||||||
|         } else { |         } else { | ||||||
|             // Catch possible error |             if (attribute == null) { | ||||||
|             mValue="0"; |                 // Compiler says: "Statement is not reachable." | ||||||
|  |                 // This is wrong because a break point on the following line proves it is executed! | ||||||
|  |                 return "{{states('" + entity_id.toString() + "')}}"; | ||||||
|  |             } else { | ||||||
|  |                 return "{{state_attr('" + entity_id.toString() + "','" + attribute + "')}}"; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   |  | ||||||
|     //! Update the menu item's sub label to display the template rendered by Home Assistant. |     //! 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 |     //! @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. |     //!             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 { |     public function updateState(data as Lang.String or Lang.Dictionary or Lang.Number or Lang.Float or Null) as Void { | ||||||
|         if (data == null) { |         if (data == null) { | ||||||
|             setSubLabel($.Rez.Strings.Empty); |             setSubLabel($.Rez.Strings.Empty); | ||||||
|         } else if(data instanceof Lang.Float) { |         } else if(data instanceof Lang.Float) { | ||||||
| @@ -197,31 +205,45 @@ class HomeAssistantNumericMenuItem extends HomeAssistantMenuItem { | |||||||
|         } else if(data instanceof Lang.Number) { |         } else if(data instanceof Lang.Number) { | ||||||
|             var f = data.toFloat() as Lang.Float; |             var f = data.toFloat() as Lang.Float; | ||||||
|             setSubLabel(f.format(mFormatString)); |             setSubLabel(f.format(mFormatString)); | ||||||
|         } else if (data instanceof Lang.String){ |         } else if (data instanceof Lang.String) { | ||||||
|             // This should not happen |             // This should not happen | ||||||
|             setSubLabel(data); |             setSubLabel(data); | ||||||
|         }   |         } else { | ||||||
|         else { |  | ||||||
|             // The template must return a Float on Numeric value, or the item cannot be formatted locally without error. |             // The template must return a Float on Numeric value, or the item cannot be formatted locally without error. | ||||||
|             setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); |             setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); | ||||||
|         } |         } | ||||||
|         WatchUi.requestUpdate(); |         WatchUi.requestUpdate(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     //! Set the mValue value. |     //! Set the Picker's value. Needed to set new value via the Service call | ||||||
|     //! |     //! | ||||||
|     //! Needed to set new value via the Service call |     //! @param value New value to set. | ||||||
|     // |     // | ||||||
|     function setValue(value as Lang.String) as Void { |     public function setValue(value as Lang.Number or Lang.Float) as Void { | ||||||
|         mValue = value; |         mValue = value; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getValue() as Lang.String { |     //! Get the Picker's value. | ||||||
|  |     //! | ||||||
|  |     //! Needed to set new value via the Service call | ||||||
|  |     // | ||||||
|  |     public function getValue() as Lang.Number or Lang.Float { | ||||||
|         return mValue; |         return mValue; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getData() as Lang.Dictionary { |     //! Get the original 'data' field supplied by the JSON menu. | ||||||
|  |     //! | ||||||
|  |     //! @return Dictionary containing the 'data' field. | ||||||
|  |     // | ||||||
|  |     public function getData() as Lang.Dictionary { | ||||||
|         return mData; |         return mData; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Get the original 'picker' field supplied by the JSON menu. | ||||||
|  |     //! | ||||||
|  |     //! @return Dictionary containing the 'picker' field. | ||||||
|  |     // | ||||||
|  |     public function getPicker() as Lang.Dictionary { | ||||||
|  |         return mPicker; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| // tested on a Venu 2 device. The source code is provided at: | // tested on a Venu 2 device. The source code is provided at: | ||||||
| //            https://github.com/house-of-abbey/GarminHomeAssistant. | //            https://github.com/house-of-abbey/GarminHomeAssistant. | ||||||
| // | // | ||||||
| // P A Abbey & J D Abbey & Someone0nEarth, 31 October 2023 | // P A Abbey & J D Abbey & @thmichel, 13 October 2025 | ||||||
| // | // | ||||||
| //------------------------------------------------------------ | //------------------------------------------------------------ | ||||||
| 
 | 
 | ||||||
| @@ -20,88 +20,79 @@ using Toybox.System; | |||||||
| using Toybox.WatchUi; | using Toybox.WatchUi; | ||||||
| 
 | 
 | ||||||
| //! Picker that allows the user to choose a float value | //! Picker that allows the user to choose a float value | ||||||
|  | // | ||||||
| class HomeAssistantNumericPicker extends WatchUi.Picker { | class HomeAssistantNumericPicker extends WatchUi.Picker { | ||||||
| 
 |  | ||||||
|     private var mFactory as HomeAssistantNumericFactory; |  | ||||||
|     private var mItem as HomeAssistantNumericMenuItem; |     private var mItem as HomeAssistantNumericMenuItem; | ||||||
| 
 | 
 | ||||||
|     //! Constructor |     //! Constructor | ||||||
|     public function initialize(factory as HomeAssistantNumericFactory, haItem as HomeAssistantNumericMenuItem) { |     // | ||||||
|  |     public function initialize( | ||||||
|  |         factory as HomeAssistantNumericFactory, | ||||||
|  |         haItem  as HomeAssistantNumericMenuItem | ||||||
|  |     ) { | ||||||
|  |         mItem      = haItem; | ||||||
|  |         var picker = mItem.getPicker(); | ||||||
|  |         var min    = (picker.get("min") as Lang.String).toFloat(); | ||||||
|  |         var step   = (picker.get("step") as Lang.String).toFloat(); | ||||||
|  |         var val    = haItem.getValue(); | ||||||
| 
 | 
 | ||||||
|         mFactory = factory; |         if (min == null) { | ||||||
| 
 |             min = 0.0; | ||||||
| 
 |  | ||||||
|         var pickerOptions = {:pattern=>[mFactory]}; |  | ||||||
|         mItem=haItem; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         var data = mItem.getData(); |  | ||||||
| 
 |  | ||||||
|         var min = 0.0; |  | ||||||
|         var val = data.get("min"); |  | ||||||
|         if (val != null) { |  | ||||||
|             min = val.toString().toFloat(); |  | ||||||
|         } |         } | ||||||
|         var step = 1.0; |         if (step == null) { | ||||||
|         val = data.get("step"); |             step = 1.0; | ||||||
|         if (val != null) { |  | ||||||
|             step = val.toString().toFloat(); |  | ||||||
|         } |         } | ||||||
|         val = haItem.getValue(); |  | ||||||
|         if (val != null) { |  | ||||||
|             val = val.toString().toFloat(); |  | ||||||
|         } else { |  | ||||||
|             // catch missing state to avoid crash |  | ||||||
|             val = min; |  | ||||||
|         } |  | ||||||
|         var index = ((val -min) / 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); |  | ||||||
| 
 | 
 | ||||||
|  |         WatchUi.Picker.initialize({ | ||||||
|  |             :title    => new WatchUi.Text({ | ||||||
|  |                 :text => haItem.getLabel(), | ||||||
|  |                 :locX => WatchUi.LAYOUT_HALIGN_CENTER, | ||||||
|  |                 :locY => WatchUi.LAYOUT_VALIGN_BOTTOM | ||||||
|  |             }), | ||||||
|  |             :pattern  => [factory], | ||||||
|  |             :defaults => [((val - min) / step).toNumber()] | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     //! Called when the user has completed picking. | ||||||
|     //! Get whether the user is done picking |     //! | ||||||
|     //! @param value Value user selected |     //! @param value Value user selected | ||||||
|     //! @return true if user is done, false otherwise |     //! @return true if user is done, false otherwise | ||||||
|     public function onConfirm(value as Lang.String) as Void { |     // | ||||||
|  |     public function onConfirm(value as Lang.Number or Lang.Float) as Void { | ||||||
|         mItem.setValue(value); |         mItem.setValue(value); | ||||||
|         mItem.callService(); |         mItem.callService(); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|      |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //! Responds to a numeric  picker selection or cancellation | //! Responds to a numeric picker selection or cancellation. | ||||||
|  | // | ||||||
| class HomeAssistantNumericPickerDelegate extends WatchUi.PickerDelegate { | class HomeAssistantNumericPickerDelegate extends WatchUi.PickerDelegate { | ||||||
|     private var mPicker as HomeAssistantNumericPicker; |     private var mPicker as HomeAssistantNumericPicker; | ||||||
| 
 | 
 | ||||||
|     //! Constructor |     //! Constructor | ||||||
|  |     // | ||||||
|     public function initialize(picker as HomeAssistantNumericPicker) { |     public function initialize(picker as HomeAssistantNumericPicker) { | ||||||
|         PickerDelegate.initialize(); |         PickerDelegate.initialize(); | ||||||
|         mPicker = picker; |         mPicker = picker; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     //! Handle a cancel event from the picker |     //! Handle a cancel event from the picker | ||||||
|  |     //! | ||||||
|     //! @return true if handled, false otherwise |     //! @return true if handled, false otherwise | ||||||
|  |     // | ||||||
|     public function onCancel() as Lang.Boolean { |     public function onCancel() as Lang.Boolean { | ||||||
|         WatchUi.popView(WatchUi.SLIDE_RIGHT); |         WatchUi.popView(WatchUi.SLIDE_RIGHT); | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     //! Handle a confirm event from the picker |     //! Handle a confirm event from the picker | ||||||
|  |     //! | ||||||
|     //! @param values The values chosen in the picker |     //! @param values The values chosen in the picker | ||||||
|     //! @return true if handled, false otherwise |     //! @return true if handled, false otherwise | ||||||
|  |     // | ||||||
|     public function onAccept(values as Lang.Array) as Lang.Boolean { |     public function onAccept(values as Lang.Array) as Lang.Boolean { | ||||||
|         var chosenValue = values[0].toString(); |         mPicker.onConfirm(values[0]); | ||||||
|         mPicker.onConfirm(chosenValue); |  | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -128,7 +128,7 @@ class HomeAssistantTapMenuItem extends HomeAssistantMenuItem { | |||||||
|     //! |     //! | ||||||
|     //! @param b Ignored. It is included in order to match the expected function prototype of the callback method. |     //! @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 { |     public function onConfirm(b as Lang.Boolean) as Void { | ||||||
|         if (mService != null) { |         if (mService != null) { | ||||||
|             mHomeAssistantService.call(mService, mData, mExit); |             mHomeAssistantService.call(mService, mData, mExit); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -47,7 +47,6 @@ class HomeAssistantView extends WatchUi.Menu2 { | |||||||
|                 var content    = items[i].get("content")    as Lang.String?; |                 var content    = items[i].get("content")    as Lang.String?; | ||||||
|                 var entity     = items[i].get("entity")     as Lang.String?; |                 var entity     = items[i].get("entity")     as Lang.String?; | ||||||
|                 var tap_action = items[i].get("tap_action") as Lang.Dictionary?; |                 var tap_action = items[i].get("tap_action") as Lang.Dictionary?; | ||||||
|                 var picker     = items[i].get("picker")     as Lang.Dictionary?; // optional for numeric items |  | ||||||
|                 var service    = items[i].get("service")    as Lang.String?; // Deprecated schema |                 var service    = items[i].get("service")    as Lang.String?; // Deprecated schema | ||||||
|                 var confirm    = false                      as Lang.Boolean?; |                 var confirm    = false                      as Lang.Boolean?; | ||||||
|                 var pin        = false                      as Lang.Boolean?; |                 var pin        = false                      as Lang.Boolean?; | ||||||
| @@ -128,18 +127,23 @@ class HomeAssistantView extends WatchUi.Menu2 { | |||||||
|                             )); |                             )); | ||||||
|                         } |                         } | ||||||
|                     } else if (type.equals("numeric") && service != null) { |                     } else if (type.equals("numeric") && service != null) { | ||||||
|                         addItem(HomeAssistantMenuItemFactory.create().numeric( |                         if (tap_action != null) { | ||||||
|                             name, |                             var picker = tap_action.get("picker") as Lang.Dictionary?; | ||||||
|                             entity, |                             if (picker != null) { | ||||||
|                             content, |                                 addItem(HomeAssistantMenuItemFactory.create().numeric( | ||||||
|                             service, |                                     name, | ||||||
|                             picker, |                                     entity, | ||||||
|                             { |                                     content, | ||||||
|                                 :exit    => exit, |                                     service, | ||||||
|                                 :confirm => confirm, |                                     picker, | ||||||
|                                 :pin     => pin |                                     { | ||||||
|  |                                         :exit    => exit, | ||||||
|  |                                         :confirm => confirm, | ||||||
|  |                                         :pin     => pin | ||||||
|  |                                     } | ||||||
|  |                                 )); | ||||||
|                             } |                             } | ||||||
|                         ));    |                         } | ||||||
|                     } else if (type.equals("info") && content != null) { |                     } else if (type.equals("info") && content != null) { | ||||||
|                         // Cannot exit from a non-actionable information only menu item. |                         // Cannot exit from a non-actionable information only menu item. | ||||||
|                         addItem(HomeAssistantMenuItemFactory.create().tap( |                         addItem(HomeAssistantMenuItemFactory.create().tap( | ||||||
| @@ -168,7 +172,6 @@ class HomeAssistantView extends WatchUi.Menu2 { | |||||||
|     //! |     //! | ||||||
|     //! @return An array of menu items that need to be updated periodically to reflect the latest Home Assistant state. |     //! @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 HomeAssistantNumericMenuItem or Null> { |     function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem  or HomeAssistantNumericMenuItem or Null> { | ||||||
|         var fullList = []; |         var fullList = []; | ||||||
|         var lmi = mItems as Lang.Array<WatchUi.MenuItem>; |         var lmi = mItems as Lang.Array<WatchUi.MenuItem>; | ||||||
| @@ -204,8 +207,8 @@ class HomeAssistantView extends WatchUi.Menu2 { | |||||||
|     //! Called when this View is brought to the foreground. Restore |     //! Called when this View is brought to the foreground. Restore | ||||||
|     //! the state of this View and prepare it to be shown. This includes |     //! the state of this View and prepare it to be shown. This includes | ||||||
|     //! loading resources into memory. |     //! loading resources into memory. | ||||||
|  |     // | ||||||
|     function onShow() as Void {} |     function onShow() as Void {} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| //! Delegate for the HomeAssistantView. | //! Delegate for the HomeAssistantView. | ||||||
| @@ -273,18 +276,13 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate { | |||||||
|             var haItem = item as HomeAssistantNumericMenuItem; |             var haItem = item as HomeAssistantNumericMenuItem; | ||||||
|             // System.println(haItem.getLabel() + " " + haItem.getId()); |             // System.println(haItem.getLabel() + " " + haItem.getId()); | ||||||
|             // create new view to select new value |             // create new view to select new value | ||||||
|             |             var mPickerFactory  = new HomeAssistantNumericFactory((haItem as HomeAssistantNumericMenuItem).getPicker()); | ||||||
|             var mPickerFactory = new HomeAssistantNumericFactory(haItem.getData()); |             var mPicker         = new HomeAssistantNumericPicker(mPickerFactory,haItem); | ||||||
|             |  | ||||||
|             var mPicker = new HomeAssistantNumericPicker(mPickerFactory,haItem); |  | ||||||
|             var mPickerDelegate = new HomeAssistantNumericPickerDelegate(mPicker); |             var mPickerDelegate = new HomeAssistantNumericPickerDelegate(mPicker); | ||||||
|             WatchUi.pushView(mPicker,mPickerDelegate,WatchUi.SLIDE_LEFT); |             WatchUi.pushView(mPicker,mPickerDelegate,WatchUi.SLIDE_LEFT); | ||||||
|         } else if (item instanceof HomeAssistantGroupMenuItem) { |         } else if (item instanceof HomeAssistantGroupMenuItem) { | ||||||
|             var haMenuItem = item as HomeAssistantGroupMenuItem; |             var haMenuItem = item as HomeAssistantGroupMenuItem; | ||||||
|             // System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId()); |  | ||||||
|             WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT); |             WatchUi.pushView(haMenuItem.getMenuView(), new HomeAssistantViewDelegate(false), WatchUi.SLIDE_LEFT); | ||||||
|         // } else { |  | ||||||
|         //     System.println(item.getLabel() + " " + item.getId()); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user