Added template option to toggle menu items

This commit is contained in:
Philip Abbey
2024-08-25 19:34:29 +01:00
parent 72e825566c
commit 685cda7924
3 changed files with 145 additions and 11 deletions

View File

@ -67,10 +67,12 @@ class HomeAssistantMenuItemFactory {
function toggle( function toggle(
label as Lang.String or Lang.Symbol, label as Lang.String or Lang.Symbol,
entity_id as Lang.String or Null, entity_id as Lang.String or Null,
template as Lang.String or Null,
confirm as Lang.Boolean confirm as Lang.Boolean
) as WatchUi.MenuItem { ) as WatchUi.MenuItem {
return new HomeAssistantToggleMenuItem( return new HomeAssistantToggleMenuItem(
label, label,
template,
confirm, confirm,
{ "entity_id" => entity_id }, { "entity_id" => entity_id },
mMenuItemOptions mMenuItemOptions

View File

@ -27,9 +27,11 @@ using Toybox.Timer;
class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem { class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
private var mConfirm as Lang.Boolean; private var mConfirm as Lang.Boolean;
private var mData as Lang.Dictionary; private var mData as Lang.Dictionary;
private var mTemplate as Lang.String;
function initialize( function initialize(
label as Lang.String or Lang.Symbol, label as Lang.String or Lang.Symbol,
template as Lang.String,
confirm as Lang.Boolean, confirm as Lang.Boolean,
data as Lang.Dictionary or Null, data as Lang.Dictionary or Null,
options as { options as {
@ -40,6 +42,7 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
WatchUi.ToggleMenuItem.initialize(label, null, null, false, options); WatchUi.ToggleMenuItem.initialize(label, null, null, false, options);
mConfirm = confirm; mConfirm = confirm;
mData = data; mData = data;
mTemplate = template;
} }
private function setUiToggle(state as Null or Lang.String) as Void { 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: case Communications.NETWORK_RESPONSE_OUT_OF_MEMORY:
// System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?"); // System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_RESPONSE_OUT_OF_MEMORY, are we going too fast?");
var myTimer = new Timer.Timer(); 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. // 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); myTimer.start(getApp().method(:updateNextMenuItem), Globals.scApiBackoff, false);
// Revert status // Revert status
@ -123,8 +127,13 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String); setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String);
} }
setUiToggle(state); setUiToggle(state);
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. // Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
getApp().updateNextMenuItem(); getApp().updateNextMenuItem();
} else {
updateTemplate();
}
break; break;
default: default:
@ -272,4 +281,127 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
setState(b); 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)
);
}
}
}
} }

View File

@ -62,7 +62,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
} }
if (type != null && name != null) { if (type != null && name != null) {
if (type.equals("toggle") && entity != 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) { } else if (type.equals("template") && content != null) {
if (service == null) { if (service == null) {
addItem(HomeAssistantMenuItemFactory.create().template_notap(name, content)); addItem(HomeAssistantMenuItemFactory.create().template_notap(name, content));