From ea32d71a2b1cf52648c3704a210ba78b884f5a69 Mon Sep 17 00:00:00 2001 From: Philip Abbey Date: Sat, 24 Aug 2024 18:31:47 +0100 Subject: [PATCH 1/8] Added templates to group items --- HISTORY.md | 1 + config.schema.json | 53 ++++--- examples/Templates.md | 3 + source/HomeAssistantApp.mc | 9 +- source/HomeAssistantGroupMenuItem.mc | 14 +- source/HomeAssistantMenuItemFactory.mc | 12 +- source/HomeAssistantTemplateMenuItem.mc | 137 ++---------------- source/HomeAssistantView.mc | 9 +- source/TemplateMenuItem.mc | 176 ++++++++++++++++++++++++ 9 files changed, 251 insertions(+), 163 deletions(-) create mode 100644 source/TemplateMenuItem.mc diff --git a/HISTORY.md b/HISTORY.md index f77a024..8d9a77c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -30,3 +30,4 @@ | 2.15 | Better support for templates by isolating erroneous returns and marking the menu item. | | 2.16 | Bug fix for lack of phone connection when starting the application. Includes new activity reporting features from [KPWhiver](https://github.com/KPWhiver) covering steps, heart rate, floors climbed and descended, and respiration rate. | | 2.17 | Bug fix for reporting activity metrics that are not found on some devices. | +| 2.18 | A template to evaluate is now optionally allowed on a group menu item. Only toggle items cannot include a template at this time. | diff --git a/config.schema.json b/config.schema.json index ae78b37..02e58dd 100644 --- a/config.schema.json +++ b/config.schema.json @@ -22,14 +22,16 @@ "$ref": "#/$defs/entity" }, "name": { - "title": "Your familiar name", - "type": "string" + "$ref": "#/$defs/name" }, "type": { - "title": "Menu item type", - "description": "One of 'tap', 'template', 'toggle' or 'group'.", + "$ref": "#/$defs/type", "const": "toggle" }, + "content": { + "$ref": "#/$defs/content", + "description": "Optional in a toggle." + }, "tap_action": { "type": "object", "properties": { @@ -55,16 +57,13 @@ "description": "Use 'tap_action' instead to mirror Home Assistant." }, "name": { - "title": "Your familiar name", - "type": "string" + "$ref": "#/$defs/name" }, "content": { - "title": "What to display (template)", - "type": "string" + "$ref": "#/$defs/content" }, "type": { - "title": "Menu item type", - "description": "One of 'tap', 'template', 'toggle' or 'group'.", + "$ref": "#/$defs/type", "const": "template" } }, @@ -78,16 +77,13 @@ "$ref": "#/$defs/entity" }, "name": { - "title": "Your familiar name", - "type": "string" + "$ref": "#/$defs/name" }, "content": { - "title": "What to display (template)", - "type": "string" + "$ref": "#/$defs/content" }, "type": { - "title": "Menu item type", - "description": "One of 'tap', 'template', 'toggle' or 'group'.", + "$ref": "#/$defs/type", "const": "template" }, "tap_action": { @@ -106,12 +102,10 @@ "$ref": "#/$defs/entity" }, "name": { - "title": "Your familiar name", - "type": "string" + "$ref": "#/$defs/name" }, "type": { - "title": "Menu item type", - "description": "One of 'tap', 'template', 'toggle' or 'group'.", + "$ref": "#/$defs/type", "const": "tap" }, "service": { @@ -153,10 +147,13 @@ "type": "string" }, "type": { - "title": "Menu item type", - "description": "One of 'tap', 'template', 'toggle' or 'group'.", + "$ref": "#/$defs/type", "const": "group" }, + "content": { + "$ref": "#/$defs/content", + "description": "Optional in a group." + }, "items": { "$ref": "#/$defs/items" } @@ -164,6 +161,10 @@ "required": ["name", "title", "type", "items"], "additionalProperties": false }, + "type": { + "title": "Menu item type", + "description": "One of 'tap', 'template', 'toggle' or 'group'." + }, "items": { "type": "array", "maxItems": 16, @@ -184,6 +185,10 @@ ] } }, + "name": { + "title": "Your familiar name", + "type": "string" + }, "entity": { "type": "string", "title": "Home Assistant entity name", @@ -213,6 +218,10 @@ }, "required": ["service"] }, + "content": { + "title": "Jinja2 template defining the text to display.", + "type": "string" + }, "confirm": { "type": "boolean", "default": false, diff --git a/examples/Templates.md b/examples/Templates.md index 97641dd..c1ff923 100644 --- a/examples/Templates.md +++ b/examples/Templates.md @@ -10,6 +10,9 @@ In order to provide the most functionality possible the content of the menu item - `{{` ... `}}` for Expressions to print to the template output - `{#` ... `#}` for Comments not included in theĀ templateĀ output +> [!IMPORTANT] +> In order to avoid "Template Error" being displayed as the return value, make sure your Jinja2 template returns a `string`, not a number of some variety. _All numbers must be formatted to strings_ so the application does not need to distinguish an `integer` from a `float`. + ## States In this example we get the battery level of the device and add the percent sign. *Very simple* diff --git a/source/HomeAssistantApp.mc b/source/HomeAssistantApp.mc index de7a700..18d17e3 100644 --- a/source/HomeAssistantApp.mc +++ b/source/HomeAssistantApp.mc @@ -429,12 +429,11 @@ class HomeAssistantApp extends Application.AppBase { // We need to spread out the API calls so as not to overload the results queue and cause Communications.BLE_QUEUE_FULL // (-101) error. This function is called by a timer every Globals.menuItemUpdateInterval ms. function updateNextMenuItemInternal() as Void { - var itu = mItemsToUpdate as Lang.Array; - if (itu != null) { + if (mItemsToUpdate != null) { // System.println("HomeAssistantApp updateNextMenuItemInternal(): Doing update for item " + mNextItemToUpdate + ", mIsInitUpdateCompl=" + mIsInitUpdateCompl); - itu[mNextItemToUpdate].getState(); - // mNextItemToUpdate = (mNextItemToUpdate + 1) % itu.size() - But with roll-over detection - if (mNextItemToUpdate == itu.size()-1) { + mItemsToUpdate[mNextItemToUpdate].getState(); + // mNextItemToUpdate = (mNextItemToUpdate + 1) % mItemsToUpdate.size() - But with roll-over detection + if (mNextItemToUpdate == mItemsToUpdate.size()-1) { // Last item completed return to the start of the list mNextItemToUpdate = 0; mIsInitUpdateCompl = true; diff --git a/source/HomeAssistantGroupMenuItem.mc b/source/HomeAssistantGroupMenuItem.mc index bf52204..b8860fe 100644 --- a/source/HomeAssistantGroupMenuItem.mc +++ b/source/HomeAssistantGroupMenuItem.mc @@ -14,27 +14,29 @@ // // Description: // -// Menu button with an icon that opens a sub-menu, i.e. group. +// 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; -class HomeAssistantGroupMenuItem extends WatchUi.IconMenuItem { +class HomeAssistantGroupMenuItem extends TemplateMenuItem { private var mMenu as HomeAssistantView; function initialize( definition as Lang.Dictionary, + template as Lang.String, icon as WatchUi.Drawable, options as { :alignment as WatchUi.MenuItem.Alignment - } or Null) { + } or Null + ) { - WatchUi.IconMenuItem.initialize( + TemplateMenuItem.initialize( definition.get("name") as Lang.String, - null, - null, + template, icon, options ); diff --git a/source/HomeAssistantMenuItemFactory.mc b/source/HomeAssistantMenuItemFactory.mc index 67003ec..a9be246 100644 --- a/source/HomeAssistantMenuItemFactory.mc +++ b/source/HomeAssistantMenuItemFactory.mc @@ -145,7 +145,15 @@ class HomeAssistantMenuItemFactory { ); } - function group(definition as Lang.Dictionary) as WatchUi.MenuItem { - return new HomeAssistantGroupMenuItem(definition, mGroupTypeIcon, mMenuItemOptions); + function group( + definition as Lang.Dictionary, + template as Lang.String or Null + ) as WatchUi.MenuItem { + return new HomeAssistantGroupMenuItem( + definition, + template, + mGroupTypeIcon, + mMenuItemOptions + ); } } diff --git a/source/HomeAssistantTemplateMenuItem.mc b/source/HomeAssistantTemplateMenuItem.mc index be667f1..6e68840 100644 --- a/source/HomeAssistantTemplateMenuItem.mc +++ b/source/HomeAssistantTemplateMenuItem.mc @@ -26,35 +26,32 @@ using Toybox.Lang; using Toybox.WatchUi; using Toybox.Graphics; -class HomeAssistantTemplateMenuItem extends WatchUi.IconMenuItem { +class HomeAssistantTemplateMenuItem extends TemplateMenuItem { private var mHomeAssistantService as HomeAssistantService; - private var mTemplate as Lang.String; private var mService as Lang.String or Null; private var mConfirm as Lang.Boolean; private var mData as Lang.Dictionary or Null; function initialize( - label as Lang.String or Lang.Symbol, - template as Lang.String, - service as Lang.String or Null, - confirm as Lang.Boolean, - data as Lang.Dictionary or Null, - icon as Graphics.BitmapType or WatchUi.Drawable, - options as { + label as Lang.String or Lang.Symbol, + template as Lang.String, + service as Lang.String or Null, + confirm as Lang.Boolean, + data as Lang.Dictionary or Null, + icon as Graphics.BitmapType or WatchUi.Drawable, + options as { :alignment as WatchUi.MenuItem.Alignment } or Null, - haService as HomeAssistantService + haService as HomeAssistantService ) { - WatchUi.IconMenuItem.initialize( + TemplateMenuItem.initialize( label, - null, - null, + template, icon, options ); mHomeAssistantService = haService; - mTemplate = template; mService = service; mConfirm = confirm; mData = data; @@ -79,116 +76,4 @@ class HomeAssistantTemplateMenuItem extends WatchUi.IconMenuItem { } } - // Callback function after completing the GET request to fetch the status. - // Terminate updating the toggle menu items via the chain of calls for a permanent network - // error. The ErrorView cancellation will resume the call chain. - // - function onReturnGetState(responseCode as Lang.Number, data as Null or Lang.Dictionary) as Void { - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: " + responseCode); - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Data: " + data); - - var status = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; - switch (responseCode) { - case Communications.BLE_HOST_TIMEOUT: - case Communications.BLE_CONNECTION_UNAVAILABLE: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); - break; - - case Communications.BLE_QUEUE_FULL: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String); - break; - - case Communications.NETWORK_REQUEST_TIMED_OUT: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String); - break; - - case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); - break; - - case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?"); - var myTimer = new Timer.Timer(); - // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. - myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false); - // Revert status - status = getApp().getApiStatus(); - break; - - case 404: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String); - break; - - case 400: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 400, bad request. Template error."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); - break; - - case 200: - status = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String; - var label = data.get("request"); - if (label == null) { - setSubLabel($.Rez.Strings.Empty); - } else if(label instanceof Lang.String) { - setSubLabel(label); - } else if(label instanceof Lang.Dictionary) { - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() label = " + label); - if (label.get("error") != null) { - setSubLabel($.Rez.Strings.TemplateError); - } else { - setSubLabel($.Rez.Strings.PotentialError); - } - } - requestUpdate(); - // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. - getApp().updateNextMenuItem(); - break; - - default: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode); - } - getApp().setApiStatus(status); - } - - function getState() as Void { - if (! System.getDeviceSettings().phoneConnected) { - // System.println("HomeAssistantTemplateMenuItem getState(): No Phone connection, skipping API call."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); - getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); - } else if (! System.getDeviceSettings().connectionAvailable) { - // System.println("HomeAssistantTemplateMenuItem getState(): No Internet connection, skipping API call."); - ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); - getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); - } else { - // https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates - var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(); - // System.println("HomeAssistantTemplateMenuItem getState() URL=" + url + ", Template='" + mTemplate + "'"); - Communications.makeWebRequest( - url, - { - "type" => "render_template", - "data" => { - "request" => { - "template" => mTemplate - } - } - }, - { - :method => Communications.HTTP_REQUEST_METHOD_POST, - :headers => { - "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON - }, - :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON - }, - method(:onReturnGetState) - ); - } - } - } diff --git a/source/HomeAssistantView.mc b/source/HomeAssistantView.mc index ac93663..6a82046 100644 --- a/source/HomeAssistantView.mc +++ b/source/HomeAssistantView.mc @@ -73,7 +73,7 @@ class HomeAssistantView extends WatchUi.Menu2 { } else if (type.equals("tap") && service != null) { addItem(HomeAssistantMenuItemFactory.create().tap(name, entity, service, confirm, data)); } else if (type.equals("group")) { - addItem(HomeAssistantMenuItemFactory.create().group(items[i])); + addItem(HomeAssistantMenuItemFactory.create().group(items[i], content)); } } } @@ -82,11 +82,16 @@ class HomeAssistantView extends WatchUi.Menu2 { 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); diff --git a/source/TemplateMenuItem.mc b/source/TemplateMenuItem.mc new file mode 100644 index 0000000..927618b --- /dev/null +++ b/source/TemplateMenuItem.mc @@ -0,0 +1,176 @@ +//----------------------------------------------------------------------------------- +// +// 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, 12 January 2024 +// +// +// Description: +// +// Menu button that renders a Home Assistant Template. +// +// Reference: +// * https://developers.home-assistant.io/docs/api/rest/ +// * https://www.home-assistant.io/docs/configuration/templating +// +//----------------------------------------------------------------------------------- + +using Toybox.Lang; +using Toybox.WatchUi; +using Toybox.Graphics; + +class TemplateMenuItem extends WatchUi.IconMenuItem { + private var mTemplate as Lang.String; + + function initialize( + label as Lang.String or Lang.Symbol, + template as Lang.String, + icon as Graphics.BitmapType or WatchUi.Drawable, + options as { + :alignment as WatchUi.MenuItem.Alignment + } or Null + ) { + WatchUi.IconMenuItem.initialize( + label, + null, + null, + icon, + options + ); + + mTemplate = template; + } + + // Callback function after completing the GET request to fetch the status. + // Terminate updating the toggle menu items via the chain of calls for a permanent network + // error. The ErrorView cancellation will resume the call chain. + // + function onReturnGetState(responseCode as Lang.Number, data as Null or Lang.Dictionary) as Void { + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: " + responseCode); + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Data: " + data); + + var status = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; + switch (responseCode) { + case Communications.BLE_HOST_TIMEOUT: + case Communications.BLE_CONNECTION_UNAVAILABLE: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + break; + + case Communications.BLE_QUEUE_FULL: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String); + break; + + case Communications.NETWORK_REQUEST_TIMED_OUT: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String); + break; + + case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); + break; + + case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?"); + var myTimer = new Timer.Timer(); + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false); + // Revert status + status = getApp().getApiStatus(); + break; + + case 404: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String); + break; + + case 400: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 400, bad request. Template error."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); + break; + + case 200: + status = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String; + var label = data.get("request"); + if (label == null) { + setSubLabel($.Rez.Strings.Empty); + } else if(label instanceof Lang.String) { + setSubLabel(label); + } else if(label instanceof Lang.Dictionary) { + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() label = " + label); + if (label.get("error") != null) { + setSubLabel($.Rez.Strings.TemplateError); + } else { + setSubLabel($.Rez.Strings.PotentialError); + } + } else { + // The template must return a Lang.String, a number can be either integer or float and hence cannot be formatted locally without error. + setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); + } + requestUpdate(); + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + getApp().updateNextMenuItem(); + break; + + default: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode); + } + getApp().setApiStatus(status); + } + + function getState() as Void { + if (mTemplate == null) { + // Nothing to do here. + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + getApp().updateNextMenuItem(); + } else { + if (! System.getDeviceSettings().phoneConnected) { + // System.println("HomeAssistantTemplateMenuItem getState(): No Phone connection, skipping API call."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); + } else if (! System.getDeviceSettings().connectionAvailable) { + // System.println("HomeAssistantTemplateMenuItem getState(): No Internet connection, skipping API call."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); + getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); + } else { + // https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates + var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(); + // System.println("HomeAssistantTemplateMenuItem getState() URL=" + url + ", Template='" + mTemplate + "'"); + Communications.makeWebRequest( + url, + { + "type" => "render_template", + "data" => { + "request" => { + "template" => mTemplate + } + } + }, + { + :method => Communications.HTTP_REQUEST_METHOD_POST, + :headers => { + "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON + }, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }, + method(:onReturnGetState) + ); + } + } + } + + function hasTemplate() as Lang.Boolean { + return (mTemplate != null); + } + +} \ No newline at end of file From 1dc95eeac7f27187c987c3bc0aeb6007dce3801a Mon Sep 17 00:00:00 2001 From: Philip Abbey Date: Sun, 25 Aug 2024 18:53:32 +0100 Subject: [PATCH 2/8] Added callback function to TemplateMenuItem --- source/HomeAssistantGroupMenuItem.mc | 2 ++ source/HomeAssistantTemplateMenuItem.mc | 2 ++ source/TemplateMenuItem.mc | 16 +++++++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/source/HomeAssistantGroupMenuItem.mc b/source/HomeAssistantGroupMenuItem.mc index b8860fe..2239f19 100644 --- a/source/HomeAssistantGroupMenuItem.mc +++ b/source/HomeAssistantGroupMenuItem.mc @@ -37,6 +37,8 @@ class HomeAssistantGroupMenuItem extends TemplateMenuItem { TemplateMenuItem.initialize( definition.get("name") as Lang.String, template, + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + getApp().method(:updateNextMenuItem), icon, options ); diff --git a/source/HomeAssistantTemplateMenuItem.mc b/source/HomeAssistantTemplateMenuItem.mc index 6e68840..fd8e13a 100644 --- a/source/HomeAssistantTemplateMenuItem.mc +++ b/source/HomeAssistantTemplateMenuItem.mc @@ -47,6 +47,8 @@ class HomeAssistantTemplateMenuItem extends TemplateMenuItem { TemplateMenuItem.initialize( label, template, + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + getApp().method(:updateNextMenuItem), icon, options ); diff --git a/source/TemplateMenuItem.mc b/source/TemplateMenuItem.mc index 927618b..4078572 100644 --- a/source/TemplateMenuItem.mc +++ b/source/TemplateMenuItem.mc @@ -9,7 +9,7 @@ // 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, 12 January 2024 +// P A Abbey & J D Abbey, 24 August 2024 // // // Description: @@ -28,10 +28,13 @@ using Toybox.Graphics; class TemplateMenuItem extends WatchUi.IconMenuItem { private var mTemplate as Lang.String; + private var mCallback as Method() as Void; function initialize( label as Lang.String or Lang.Symbol, template as Lang.String, + // Do not use Lang.Method as it does not compile! + callback as Method() as Void, icon as Graphics.BitmapType or WatchUi.Drawable, options as { :alignment as WatchUi.MenuItem.Alignment @@ -46,6 +49,7 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { ); mTemplate = template; + mCallback = callback; } // Callback function after completing the GET request to fetch the status. @@ -117,8 +121,9 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); } requestUpdate(); - // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. - getApp().updateNextMenuItem(); + if (mCallback != null) { + mCallback.invoke(); + } break; default: @@ -131,8 +136,9 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { function getState() as Void { if (mTemplate == null) { // Nothing to do here. - // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. - getApp().updateNextMenuItem(); + if (mCallback != null) { + mCallback.invoke(); + } } else { if (! System.getDeviceSettings().phoneConnected) { // System.println("HomeAssistantTemplateMenuItem getState(): No Phone connection, skipping API call."); From 72e825566cc51c7aa1b7651dfba9f050802f5273 Mon Sep 17 00:00:00 2001 From: Philip Abbey Date: Sun, 25 Aug 2024 19:33:33 +0100 Subject: [PATCH 3/8] Update TemplateMenuItem.mc Fixed System.println() calls. --- source/TemplateMenuItem.mc | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/source/TemplateMenuItem.mc b/source/TemplateMenuItem.mc index 4078572..b98d805 100644 --- a/source/TemplateMenuItem.mc +++ b/source/TemplateMenuItem.mc @@ -57,34 +57,34 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { // error. The ErrorView cancellation will resume the call chain. // function onReturnGetState(responseCode as Lang.Number, data as Null or Lang.Dictionary) as Void { - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: " + responseCode); - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Data: " + data); + // System.println("TemplateMenuItem onReturnGetState() Response Code: " + responseCode); + // System.println("TemplateMenuItem onReturnGetState() Response Data: " + data); var status = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; switch (responseCode) { case Communications.BLE_HOST_TIMEOUT: case Communications.BLE_CONNECTION_UNAVAILABLE: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); + // System.println("TemplateMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); break; case Communications.BLE_QUEUE_FULL: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + // System.println("TemplateMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String); break; case Communications.NETWORK_REQUEST_TIMED_OUT: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); + // System.println("TemplateMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String); break; case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); + // System.println("TemplateMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); break; case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?"); + // System.println("TemplateMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?"); var myTimer = new Timer.Timer(); // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false); @@ -93,12 +93,12 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { break; case 404: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting."); + // System.println("TemplateMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting."); ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String); break; case 400: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 400, bad request. Template error."); + // System.println("TemplateMenuItem onReturnGetState() Response Code: 400, bad request. Template error."); ErrorView.show(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); break; @@ -110,7 +110,7 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { } else if(label instanceof Lang.String) { setSubLabel(label); } else if(label instanceof Lang.Dictionary) { - // System.println("HomeAssistantTemplateMenuItem onReturnGetState() label = " + label); + // System.println("TemplateMenuItem onReturnGetState() label = " + label); if (label.get("error") != null) { setSubLabel($.Rez.Strings.TemplateError); } else { @@ -127,7 +127,7 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { break; default: - // System.println("HomeAssistantTemplateMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode); + // System.println("TemplateMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode); ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode); } getApp().setApiStatus(status); @@ -141,17 +141,17 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { } } else { if (! System.getDeviceSettings().phoneConnected) { - // System.println("HomeAssistantTemplateMenuItem getState(): No Phone connection, skipping API call."); + // System.println("TemplateMenuItem getState(): No Phone connection, skipping API call."); ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); } else if (! System.getDeviceSettings().connectionAvailable) { - // System.println("HomeAssistantTemplateMenuItem getState(): No Internet connection, skipping API call."); + // System.println("TemplateMenuItem getState(): No Internet connection, skipping API call."); ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); } else { // https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(); - // System.println("HomeAssistantTemplateMenuItem getState() URL=" + url + ", Template='" + mTemplate + "'"); + // System.println("TemplateMenuItem getState() URL=" + url + ", Template='" + mTemplate + "'"); Communications.makeWebRequest( url, { From 685cda7924d912c68e3dc93d9e906ae0dfd13917 Mon Sep 17 00:00:00 2001 From: Philip Abbey Date: Sun, 25 Aug 2024 19:34:29 +0100 Subject: [PATCH 4/8] Added template option to toggle menu items --- source/HomeAssistantMenuItemFactory.mc | 2 + source/HomeAssistantToggleMenuItem.mc | 152 +++++++++++++++++++++++-- source/HomeAssistantView.mc | 2 +- 3 files changed, 145 insertions(+), 11 deletions(-) diff --git a/source/HomeAssistantMenuItemFactory.mc b/source/HomeAssistantMenuItemFactory.mc index a9be246..d19cd34 100644 --- a/source/HomeAssistantMenuItemFactory.mc +++ b/source/HomeAssistantMenuItemFactory.mc @@ -67,10 +67,12 @@ class HomeAssistantMenuItemFactory { function toggle( label as Lang.String or Lang.Symbol, entity_id as Lang.String or Null, + template as Lang.String or Null, confirm as Lang.Boolean ) as WatchUi.MenuItem { return new HomeAssistantToggleMenuItem( label, + template, confirm, { "entity_id" => entity_id }, mMenuItemOptions diff --git a/source/HomeAssistantToggleMenuItem.mc b/source/HomeAssistantToggleMenuItem.mc index 3a49d2f..ac6d8c9 100644 --- a/source/HomeAssistantToggleMenuItem.mc +++ b/source/HomeAssistantToggleMenuItem.mc @@ -25,21 +25,24 @@ using Toybox.Application.Properties; using Toybox.Timer; class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { - private var mConfirm as Lang.Boolean; - private var mData as Lang.Dictionary; + private var mConfirm as Lang.Boolean; + private var mData as Lang.Dictionary; + private var mTemplate as Lang.String; function initialize( - label as Lang.String or Lang.Symbol, - confirm as Lang.Boolean, - data as Lang.Dictionary or Null, - options as { + label as Lang.String or Lang.Symbol, + template as Lang.String, + confirm as Lang.Boolean, + data as Lang.Dictionary or Null, + options as { :alignment as WatchUi.MenuItem.Alignment, :icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol } or Null ) { WatchUi.ToggleMenuItem.initialize(label, null, null, false, options); - mConfirm = confirm; - mData = data; + mConfirm = confirm; + mData = data; + mTemplate = template; } private function setUiToggle(state as Null or Lang.String) as Void { @@ -88,6 +91,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY: // System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?"); var myTimer = new Timer.Timer(); + // Abandon the update to this menu item, and any template, and move on to the next with a back-off delay. // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false); // Revert status @@ -123,8 +127,13 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String); } setUiToggle(state); - // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. - getApp().updateNextMenuItem(); + if (mTemplate == null) { + // Nothing more to do + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + getApp().updateNextMenuItem(); + } else { + updateTemplate(); + } break; default: @@ -272,4 +281,127 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { setState(b); } + // Callback function after completing the GET request to fetch the status. + // Terminate updating the toggle menu items via the chain of calls for a permanent network + // error. The ErrorView cancellation will resume the call chain. + // + function onReturnUpdateTemplate(responseCode as Lang.Number, data as Null or Lang.Dictionary) as Void { + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: " + responseCode); + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Data: " + data); + + var status = WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String; + switch (responseCode) { + case Communications.BLE_HOST_TIMEOUT: + case Communications.BLE_CONNECTION_UNAVAILABLE: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + break; + + case Communications.BLE_QUEUE_FULL: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiFlood) as Lang.String); + break; + + case Communications.NETWORK_REQUEST_TIMED_OUT: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoResponse) as Lang.String); + break; + + case Communications.INVALID_HTTP_BODY_IN_NETWORK_RESPONSE: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: INVALID_HTTP_BODY_IN_NETWORK_RESPONSE, check JSON is returned."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoJson) as Lang.String); + break; + + case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?"); + var myTimer = new Timer.Timer(); + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false); + // Revert status + status = getApp().getApiStatus(); + break; + + case 404: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound) as Lang.String); + break; + + case 400: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() Response Code: 400, bad request. Template error."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); + break; + + case 200: + status = WatchUi.loadResource($.Rez.Strings.Available) as Lang.String; + var label = data.get("request"); + if (label == null) { + setSubLabel($.Rez.Strings.Empty); + } else if(label instanceof Lang.String) { + setSubLabel(label); + } else if(label instanceof Lang.Dictionary) { + // System.println("HomeAssistantTemplateMenuItem onReturnGetState() label = " + label); + if (label.get("error") != null) { + setSubLabel($.Rez.Strings.TemplateError); + } else { + setSubLabel($.Rez.Strings.PotentialError); + } + } else { + // The template must return a Lang.String, a number can be either integer or float and hence cannot be formatted locally without error. + setSubLabel(WatchUi.loadResource($.Rez.Strings.TemplateError) as Lang.String); + } + requestUpdate(); + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + getApp().updateNextMenuItem(); + break; + + default: + // System.println("HomeAssistantTemplateMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode); + } + getApp().setApiStatus(status); + } + + // Massive code duplication from TemplateMenuItem, but cannot inherit from two classes. + // + function updateTemplate() as Void { + if (mTemplate == null) { + // Nothing to do here. + // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer. + getApp().updateNextMenuItem(); + } else { + if (! System.getDeviceSettings().phoneConnected) { + // System.println("HomeAssistantTemplateMenuItem getState(): No Phone connection, skipping API call."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoPhone) as Lang.String + "."); + getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); + } else if (! System.getDeviceSettings().connectionAvailable) { + // System.println("HomeAssistantTemplateMenuItem getState(): No Internet connection, skipping API call."); + ErrorView.show(WatchUi.loadResource($.Rez.Strings.NoInternet) as Lang.String + "."); + getApp().setApiStatus(WatchUi.loadResource($.Rez.Strings.Unavailable) as Lang.String); + } else { + // https://developers.home-assistant.io/docs/api/native-app-integration/sending-data/#render-templates + var url = Settings.getApiUrl() + "/webhook/" + Settings.getWebhookId(); + // System.println("HomeAssistantTemplateMenuItem getState() URL=" + url + ", Template='" + mTemplate + "'"); + Communications.makeWebRequest( + url, + { + "type" => "render_template", + "data" => { + "request" => { + "template" => mTemplate + } + } + }, + { + :method => Communications.HTTP_REQUEST_METHOD_POST, + :headers => { + "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON + }, + :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON + }, + method(:onReturnUpdateTemplate) + ); + } + } + } + } diff --git a/source/HomeAssistantView.mc b/source/HomeAssistantView.mc index 6a82046..1bb431e 100644 --- a/source/HomeAssistantView.mc +++ b/source/HomeAssistantView.mc @@ -62,7 +62,7 @@ class HomeAssistantView extends WatchUi.Menu2 { } if (type != null && name != null) { if (type.equals("toggle") && entity != null) { - addItem(HomeAssistantMenuItemFactory.create().toggle(name, entity, confirm)); + addItem(HomeAssistantMenuItemFactory.create().toggle(name, entity, content, confirm)); } else if (type.equals("template") && content != null) { if (service == null) { addItem(HomeAssistantMenuItemFactory.create().template_notap(name, content)); From 1846d682f7311a9107d4a41a83c2e04be795ecdd Mon Sep 17 00:00:00 2001 From: Philip Abbey Date: Sun, 25 Aug 2024 19:39:27 +0100 Subject: [PATCH 5/8] Update HISTORY.md v2.18 text line amended. --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 8d9a77c..30cff67 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -30,4 +30,4 @@ | 2.15 | Better support for templates by isolating erroneous returns and marking the menu item. | | 2.16 | Bug fix for lack of phone connection when starting the application. Includes new activity reporting features from [KPWhiver](https://github.com/KPWhiver) covering steps, heart rate, floors climbed and descended, and respiration rate. | | 2.17 | Bug fix for reporting activity metrics that are not found on some devices. | -| 2.18 | A template to evaluate is now optionally allowed on a group menu item. Only toggle items cannot include a template at this time. | +| 2.18 | A template to evaluate is now optionally allowed on both `group` and `toggle` menu items. The template to evaluate is non-optional on a `template` menu item. | From 342457602722f3c7cdc471cafd1fdc05a1a60f16 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 25 Aug 2024 18:42:32 +0000 Subject: [PATCH 6/8] Restyled by jq --- config.schema.json | 47 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/config.schema.json b/config.schema.json index 02e58dd..ccddd2f 100644 --- a/config.schema.json +++ b/config.schema.json @@ -12,7 +12,10 @@ "type": "string" } }, - "required": ["title", "items"], + "required": [ + "title", + "items" + ], "additionalProperties": false, "$defs": { "toggle": { @@ -42,7 +45,11 @@ "additionalProperties": false } }, - "required": ["entity", "name", "type"], + "required": [ + "entity", + "name", + "type" + ], "additionalProperties": false }, "template": { @@ -67,7 +74,11 @@ "const": "template" } }, - "required": ["name", "content", "type"], + "required": [ + "name", + "content", + "type" + ], "additionalProperties": false }, { @@ -90,7 +101,12 @@ "$ref": "#/$defs/tap_action" } }, - "required": ["name", "content", "type", "tap_action"], + "required": [ + "name", + "content", + "type", + "tap_action" + ], "additionalProperties": false } ] @@ -120,10 +136,18 @@ }, "oneOf": [ { - "required": ["name", "type", "service"] + "required": [ + "name", + "type", + "service" + ] }, { - "required": ["name", "type", "tap_action"] + "required": [ + "name", + "type", + "tap_action" + ] } ], "additionalProperties": false @@ -158,7 +182,12 @@ "$ref": "#/$defs/items" } }, - "required": ["name", "title", "type", "items"], + "required": [ + "name", + "title", + "type", + "items" + ], "additionalProperties": false }, "type": { @@ -216,7 +245,9 @@ "description": "The object containing the parameters and their values to be passed to the entity. No schema checking can be done here, you are on your own! On application crash, remove the parameters." } }, - "required": ["service"] + "required": [ + "service" + ] }, "content": { "title": "Jinja2 template defining the text to display.", From 4707f1ea9e0c318c3bfb23f438ff001a594840ef Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 25 Aug 2024 18:42:33 +0000 Subject: [PATCH 7/8] Restyled by prettier-json --- config.schema.json | 47 ++++++++-------------------------------------- 1 file changed, 8 insertions(+), 39 deletions(-) diff --git a/config.schema.json b/config.schema.json index ccddd2f..02e58dd 100644 --- a/config.schema.json +++ b/config.schema.json @@ -12,10 +12,7 @@ "type": "string" } }, - "required": [ - "title", - "items" - ], + "required": ["title", "items"], "additionalProperties": false, "$defs": { "toggle": { @@ -45,11 +42,7 @@ "additionalProperties": false } }, - "required": [ - "entity", - "name", - "type" - ], + "required": ["entity", "name", "type"], "additionalProperties": false }, "template": { @@ -74,11 +67,7 @@ "const": "template" } }, - "required": [ - "name", - "content", - "type" - ], + "required": ["name", "content", "type"], "additionalProperties": false }, { @@ -101,12 +90,7 @@ "$ref": "#/$defs/tap_action" } }, - "required": [ - "name", - "content", - "type", - "tap_action" - ], + "required": ["name", "content", "type", "tap_action"], "additionalProperties": false } ] @@ -136,18 +120,10 @@ }, "oneOf": [ { - "required": [ - "name", - "type", - "service" - ] + "required": ["name", "type", "service"] }, { - "required": [ - "name", - "type", - "tap_action" - ] + "required": ["name", "type", "tap_action"] } ], "additionalProperties": false @@ -182,12 +158,7 @@ "$ref": "#/$defs/items" } }, - "required": [ - "name", - "title", - "type", - "items" - ], + "required": ["name", "title", "type", "items"], "additionalProperties": false }, "type": { @@ -245,9 +216,7 @@ "description": "The object containing the parameters and their values to be passed to the entity. No schema checking can be done here, you are on your own! On application crash, remove the parameters." } }, - "required": [ - "service" - ] + "required": ["service"] }, "content": { "title": "Jinja2 template defining the text to display.", From a35798f9d3dd9764367182e9545d0635ff2e2a3b Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 25 Aug 2024 18:42:34 +0000 Subject: [PATCH 8/8] Restyled by whitespace --- source/TemplateMenuItem.mc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/TemplateMenuItem.mc b/source/TemplateMenuItem.mc index b98d805..89e0431 100644 --- a/source/TemplateMenuItem.mc +++ b/source/TemplateMenuItem.mc @@ -179,4 +179,4 @@ class TemplateMenuItem extends WatchUi.IconMenuItem { return (mTemplate != null); } -} \ No newline at end of file +}