Compare commits

...

7 Commits
v2.19 ... v2.20

Author SHA1 Message Date
2e7216b6b2 v2.20 documentation update 2024-08-30 21:18:56 +01:00
57a44d8946 Update WebhookManager.mc (#179)
Speculative fix to handle the callback data from webhook generation
perhaps not being Lang.Dict.
2024-08-30 19:48:47 +01:00
4432c7b9a0 Update WebhookManager.mc
Speculative fix to handle the callback data from webhook generation perhaps not being Lang.Dict.
2024-08-30 15:49:06 +01:00
15e2b19193 Deprecate template type (#177) 2024-08-30 14:32:22 +01:00
1b40231360 Fix errors 2024-08-30 13:49:09 +01:00
446c579660 Merge branch 'main' into 176-deprecate-template-items 2024-08-30 13:26:31 +01:00
1c182dd615 Deprecate template type 2024-08-30 13:25:16 +01:00
9 changed files with 104 additions and 204 deletions

View File

@ -32,3 +32,4 @@
| 2.17 | Bug fix for reporting activity metrics that are not found on some devices. |
| 2.18 | Bug fix for reporting activity metrics that might be `null` sometimes. This is unsimulatable situation, so this version is a change based on an informed guess. |
| 2.19 | 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. All updates are performed in a single HTTP GET request for efficiency. Bug fix for negative heading values. Vibration now (optionally) confirms toggle menu items being tapped. |
| 2.20 | Simplified the code base now that templates have been requested in all menu items. This means the `template` menu item became a superset of `tap`. Therefore the `tap` code has been has been upgraded to include `template` and the latter deprecated. JSON menu definitions continue to support `template` items by instantiating a `tap` menu item, but the schema marks them as deprecated and users should migrate their menu definitions now. Use the [web editor](https://house-of-abbey.github.io/GarminHomeAssistant/web/) for assistance with changes. |

View File

@ -310,3 +310,5 @@ Stack:
```
The only useful information we can glean from this log is the first line, `Error: Unexpected Type Error`. There is no useful mapping to a line of code unless someone can explain to us how to use the `pc` line. Being able to send us the error type does serve as a clue.
More on [debugging Monkey C applications](https://developer.garmin.com/connect-iq/core-topics/debugging/#appcrashes). The filenames and line numbers must only be present for deployment of code instrumented for debug.

View File

@ -64,7 +64,10 @@
},
"type": {
"$ref": "#/$defs/type",
"const": "template"
"const": "template",
"deprecated": true,
"title": "Schema change:",
"description": "Use 'tap' instead."
}
},
"required": ["name", "content", "type"],
@ -84,7 +87,10 @@
},
"type": {
"$ref": "#/$defs/type",
"const": "template"
"const": "template",
"deprecated": true,
"title": "Schema change:",
"description": "Use 'tap' instead."
},
"tap_action": {
"$ref": "#/$defs/tap_action"
@ -108,6 +114,10 @@
"$ref": "#/$defs/type",
"const": "tap"
},
"content": {
"$ref": "#/$defs/content",
"description": "Optional in a tap."
},
"service": {
"$ref": "#/$defs/entity",
"deprecated": true,
@ -118,14 +128,7 @@
"$ref": "#/$defs/tap_action"
}
},
"oneOf": [
{
"required": ["name", "type", "service"]
},
{
"required": ["name", "type", "tap_action"]
}
],
"required": ["name", "type"],
"additionalProperties": false
},
"group": {

View File

@ -34,7 +34,7 @@ class HomeAssistantApp extends Application.AppBase {
private var mGlanceTimer as Timer.Timer or Null;
private var mUpdateTimer as Timer.Timer or Null;
// Array initialised by onReturnFetchMenuConfig()
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTemplateMenuItem> or Null;
private var mItemsToUpdate as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem> or Null;
private var mIsGlance as Lang.Boolean = false;
private var mIsApp as Lang.Boolean = false; // Or Widget
private var mUpdating as Lang.Boolean = false; // Don't start a second chain of updates

View File

@ -79,7 +79,7 @@ class HomeAssistantMenuItemFactory {
);
}
function template_tap(
function tap(
label as Lang.String or Lang.Symbol,
entity as Lang.String or Null,
template as Lang.String or Null,
@ -94,57 +94,29 @@ class HomeAssistantMenuItemFactory {
data.put("entity_id", entity);
}
}
return new HomeAssistantTemplateMenuItem(
label,
template,
service,
confirm,
data,
mTapTypeIcon,
mMenuItemOptions,
mHomeAssistantService
);
}
function template_notap(
label as Lang.String or Lang.Symbol,
template as Lang.String or Null
) as WatchUi.MenuItem {
return new HomeAssistantTemplateMenuItem(
label,
template,
null,
false,
null,
mInfoTypeIcon,
mMenuItemOptions,
mHomeAssistantService
);
}
function tap(
label as Lang.String or Lang.Symbol,
entity as Lang.String or Null,
service as Lang.String or Null,
confirm as Lang.Boolean,
data as Lang.Dictionary or Null
) as WatchUi.MenuItem {
if (entity != null) {
if (data == null) {
data = { "entity_id" => entity };
} else {
data.put("entity_id", entity);
}
if (service != null) {
return new HomeAssistantTapMenuItem(
label,
template,
service,
confirm,
data,
mTapTypeIcon,
mMenuItemOptions,
mHomeAssistantService
);
} else {
return new HomeAssistantTapMenuItem(
label,
template,
service,
confirm,
data,
mInfoTypeIcon,
mMenuItemOptions,
mHomeAssistantService
);
}
return new HomeAssistantTapMenuItem(
label,
service,
confirm,
data,
mTapTypeIcon,
mMenuItemOptions,
mHomeAssistantService
);
}
function group(

View File

@ -24,20 +24,22 @@ using Toybox.Graphics;
class HomeAssistantTapMenuItem extends WatchUi.IconMenuItem {
private var mHomeAssistantService as HomeAssistantService;
private var mService as Lang.String;
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,
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(
label,
@ -48,16 +50,37 @@ class HomeAssistantTapMenuItem extends WatchUi.IconMenuItem {
);
mHomeAssistantService = haService;
mTemplate = template;
mService = service;
mConfirm = confirm;
mData = data;
}
function buildTemplate() as Lang.String or Null {
return null;
function hasTemplate() as Lang.Boolean {
return mTemplate != null;
}
function updateState(data as Lang.String or Null) as Void {
function buildTemplate() as Lang.String or Null {
return mTemplate;
}
function updateState(data as Lang.String or Lang.Dictionary or Null) as Void {
if (data == null) {
setSubLabel($.Rez.Strings.Empty);
} else if(data instanceof Lang.String) {
setSubLabel(data);
} else if(data instanceof Lang.Dictionary) {
// System.println("HomeAsistantTemplateMenuItem updateState() data = " + data);
if (data.get("error") != null) {
setSubLabel($.Rez.Strings.TemplateError);
} else {
setSubLabel($.Rez.Strings.PotentialError);
}
} else {
// The template must return a Lang.String, 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);
}
WatchUi.requestUpdate();
}
function callService() as Void {
@ -68,13 +91,15 @@ class HomeAssistantTapMenuItem extends WatchUi.IconMenuItem {
WatchUi.SLIDE_IMMEDIATE
);
} else {
mHomeAssistantService.call(mService, mData);
onConfirm(false);
}
}
// NB. Parameter 'b' is ignored
function onConfirm(b as Lang.Boolean) as Void {
mHomeAssistantService.call(mService, mData);
if (mService != null) {
mHomeAssistantService.call(mService, mData);
}
}
}

View File

@ -1,105 +0,0 @@
//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant.
//
// P A Abbey & J D Abbey, 12 January 2024
//
//
// Description:
//
// Menu button that renders a Home Assistant Template, and optionally triggers a service.
//
// 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 HomeAssistantTemplateMenuItem extends WatchUi.IconMenuItem {
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 {
:alignment as WatchUi.MenuItem.Alignment
} or Null,
haService as HomeAssistantService
) {
WatchUi.IconMenuItem.initialize(
label,
null,
null,
icon,
options
);
mHomeAssistantService = haService;
mTemplate = template;
mService = service;
mConfirm = confirm;
mData = data;
}
function buildTemplate() as Lang.String or Null {
return mTemplate;
}
function updateState(data as Lang.String or Lang.Dictionary or Null) as Void {
if (data == null) {
setSubLabel($.Rez.Strings.Empty);
} else if(data instanceof Lang.String) {
setSubLabel(data);
} else if(data instanceof Lang.Dictionary) {
// System.println("HomeAsistantTemplateMenuItem updateState() data = " + data);
if (data.get("error") != null) {
setSubLabel($.Rez.Strings.TemplateError);
} else {
setSubLabel($.Rez.Strings.PotentialError);
}
} else {
// The template must return a Lang.String, 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);
}
WatchUi.requestUpdate();
}
function callService() as Void {
if (mConfirm) {
WatchUi.pushView(
new HomeAssistantConfirmation(),
new HomeAssistantConfirmationDelegate(method(:onConfirm), false),
WatchUi.SLIDE_IMMEDIATE
);
} else {
onConfirm(false);
}
}
// NB. Parameter 'b' is ignored
function onConfirm(b as Lang.Boolean) as Void {
if (mService != null) {
mHomeAssistantService.call(mService, mData);
}
}
}

View File

@ -63,15 +63,8 @@ class HomeAssistantView extends WatchUi.Menu2 {
if (type != null && name != null) {
if (type.equals("toggle") && entity != null) {
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));
} else {
addItem(HomeAssistantMenuItemFactory.create().template_tap(name, entity, content, service, confirm, data));
}
} else if (type.equals("tap") && service != null) {
addItem(HomeAssistantMenuItemFactory.create().tap(name, entity, service, confirm, data));
} else if ((type.equals("tap") && service != null) || (type.equals("template") && content != null)) {
addItem(HomeAssistantMenuItemFactory.create().tap(name, entity, content, service, confirm, data));
} else if (type.equals("group")) {
addItem(HomeAssistantMenuItemFactory.create().group(items[i], content));
}
@ -80,7 +73,7 @@ class HomeAssistantView extends WatchUi.Menu2 {
}
}
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTemplateMenuItem> {
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem or HomeAssistantTapMenuItem or HomeAssistantGroupMenuItem> {
var fullList = [];
var lmi = mItems as Lang.Array<WatchUi.MenuItem>;
@ -95,8 +88,11 @@ class HomeAssistantView extends WatchUi.Menu2 {
fullList.addAll(item.getMenuView().getItemsToUpdate());
} else if (item instanceof HomeAssistantToggleMenuItem) {
fullList.add(item);
} else if (item instanceof HomeAssistantTemplateMenuItem) {
fullList.add(item);
} else if (item instanceof HomeAssistantTapMenuItem) {
var tmi = item as HomeAssistantTapMenuItem;
if (tmi.hasTemplate()) {
fullList.add(item);
}
}
}
@ -156,10 +152,6 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
var haItem = item as HomeAssistantTapMenuItem;
// System.println(haItem.getLabel() + " " + haItem.getId());
haItem.callService();
} else if (item instanceof HomeAssistantTemplateMenuItem) {
var haItem = item as HomeAssistantTemplateMenuItem;
// System.println(haItem.getLabel() + " " + haItem.getId());
haItem.callService();
} else if (item instanceof HomeAssistantGroupMenuItem) {
var haMenuItem = item as HomeAssistantGroupMenuItem;
// System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());

View File

@ -160,18 +160,28 @@ class WebhookManager {
case 200:
case 201:
var d = data as Lang.Dictionary;
if ((d.get("success") as Lang.Boolean or Null) != false) {
if (sensors.size() == 0) {
getApp().startUpdates();
if (data instanceof Lang.Dictionary) {
var d = data as Lang.Dictionary;
var b = d.get("success") as Lang.Boolean or Null;
if (b != null and b != false) {
if (sensors.size() == 0) {
getApp().startUpdates();
} else {
registerWebhookSensor(sensors);
}
} else {
registerWebhookSensor(sensors);
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure, no 'success'.");
Settings.unsetWebhookId();
Settings.unsetIsSensorsLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String);
}
} else {
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure");
// !! Speculative code for an application crash !!
// System.println("WebhookManager onReturnRegisterWebhookSensor(): Failure, not a Lang.Dict");
// Webhook ID might have been deleted on Home Assistant server and a Lang.String is trying to tell us an error message
Settings.unsetWebhookId();
Settings.unsetIsSensorsLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + data.toString());
}
break;
@ -179,7 +189,7 @@ class WebhookManager {
// System.println("WebhookManager onReturnRequestWebhookId(): Unhandled HTTP response code = " + responseCode);
Settings.unsetWebhookId();
Settings.unsetIsSensorsLevelEnabled();
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + responseCode);
ErrorView.show(WatchUi.loadResource($.Rez.Strings.WebhookFailed) as Lang.String + "\n" + WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr) as Lang.String + " " + responseCode);
}
}