diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d0d27f8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+bin/
+export/
+**/Thumbs.db
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..070367e
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,31 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "monkeyc",
+ "request": "launch",
+ "name": "Run App",
+ "stopAtLaunch": false,
+ "device": "${command:GetTargetDevice}"
+ },
+ {
+ "type": "monkeyc",
+ "request": "launch",
+ "name": "Run Tests",
+ "runTests": true,
+ "device": "${command:GetTargetDevice}"
+ },
+ {
+ "type": "monkeyc",
+ "request": "launch",
+ "name": "Run Complication Apps",
+ "stopAtLaunch": false,
+ "complicationSubscriberFolder": "${command:GetComplicationSubscriberFolder}",
+ "complicationPublisherFolder": "${command:GetComplicationPublisherFolder}",
+ "device": "${command:GetTargetDevice}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/manifest.xml b/manifest.xml
new file mode 100644
index 0000000..9028ea1
--- /dev/null
+++ b/manifest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ eng
+
+
+
+
+
\ No newline at end of file
diff --git a/monkey.jungle b/monkey.jungle
new file mode 100644
index 0000000..87796c7
--- /dev/null
+++ b/monkey.jungle
@@ -0,0 +1 @@
+project.manifest = manifest.xml
diff --git a/resources/drawables/drawables.xml b/resources/drawables/drawables.xml
new file mode 100644
index 0000000..a22c33c
--- /dev/null
+++ b/resources/drawables/drawables.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/resources/drawables/launcher_icon.png b/resources/drawables/launcher_icon.png
new file mode 100644
index 0000000..8e302cc
Binary files /dev/null and b/resources/drawables/launcher_icon.png differ
diff --git a/resources/layouts/layout.xml b/resources/layouts/layout.xml
new file mode 100644
index 0000000..e0bb1b2
--- /dev/null
+++ b/resources/layouts/layout.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/resources/menus/menu.xml b/resources/menus/menu.xml
new file mode 100644
index 0000000..6537eba
--- /dev/null
+++ b/resources/menus/menu.xml
@@ -0,0 +1,4 @@
+
diff --git a/resources/settings/properties.xml b/resources/settings/properties.xml
new file mode 100644
index 0000000..5d2bccb
--- /dev/null
+++ b/resources/settings/properties.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/resources/settings/settings.xml b/resources/settings/settings.xml
new file mode 100644
index 0000000..664d902
--- /dev/null
+++ b/resources/settings/settings.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/resources/strings/strings.xml b/resources/strings/strings.xml
new file mode 100644
index 0000000..f425222
--- /dev/null
+++ b/resources/strings/strings.xml
@@ -0,0 +1,8 @@
+
+ HomeAssistant
+
+ Click the menu button
+
+
+
+
diff --git a/source/Globals.mc b/source/Globals.mc
new file mode 100644
index 0000000..75adb8f
--- /dev/null
+++ b/source/Globals.mc
@@ -0,0 +1,15 @@
+using Toybox.Lang;
+
+class Globals {
+ // Enable printing of messages to the debug console (don't make this a Property
+ // as the messages can't be read from a watch!)
+ static const debug = false;
+ static const updateInterval = 5; // seconds
+// static hidden const apiUrl = "https://homeassistant.local/api";
+ static hidden const apiUrl = "https://home.abbey1.org.uk/api";
+
+ static function getApiUrl() {
+ return apiUrl;
+ }
+
+}
diff --git a/source/HomeAssistantApp.mc b/source/HomeAssistantApp.mc
new file mode 100644
index 0000000..3aad550
--- /dev/null
+++ b/source/HomeAssistantApp.mc
@@ -0,0 +1,28 @@
+import Toybox.Application;
+import Toybox.Lang;
+import Toybox.WatchUi;
+
+class HomeAssistantApp extends Application.AppBase {
+
+ function initialize() {
+ AppBase.initialize();
+ }
+
+ // onStart() is called on application start up
+ function onStart(state as Dictionary?) as Void {
+ }
+
+ // onStop() is called when your application is exiting
+ function onStop(state as Dictionary?) as Void {
+ }
+
+ // Return the initial view of your application here
+ function getInitialView() as Array? {
+ return [ new HomeAssistantView(), new HomeAssistantViewDelegate() ] as Array;
+ }
+
+}
+
+function getApp() as HomeAssistantApp {
+ return Application.getApp() as HomeAssistantApp;
+}
diff --git a/source/HomeAssistantMenuItem.mc b/source/HomeAssistantMenuItem.mc
new file mode 100644
index 0000000..2f3b484
--- /dev/null
+++ b/source/HomeAssistantMenuItem.mc
@@ -0,0 +1,72 @@
+import Toybox.Lang;
+import Toybox.WatchUi;
+import Toybox.Graphics;
+using Toybox.Application.Properties;
+
+class HomeAssistantMenuItem extends WatchUi.MenuItem {
+ hidden var api_key = Properties.getValue("api_key");
+
+ function initialize(
+ label as Lang.String or Lang.Symbol,
+ subLabel as Lang.String or Lang.Symbol or Null,
+ identifier as Lang.Object or Null,
+ options as {
+ :alignment as MenuItem.Alignment,
+ :icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
+ } or Null
+ ) {
+ WatchUi.MenuItem.initialize(
+ label,
+ subLabel,
+ identifier,
+ options
+ );
+ }
+
+ // Callback function after completing the POST request to call a script.
+ //
+ function onReturnExecScript(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
+ if (Globals.debug) {
+ System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: " + responseCode);
+ System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Data: " + data);
+ }
+ if (responseCode == 200) {
+ var d = data as Lang.Array;
+ for(var i = 0; i < d.size(); i++) {
+ if ((d[i].get("entity_id") as Lang.String).equals(mIdentifier)) {
+ if (Globals.debug) {
+ System.println("HomeAssistantMenuItem Note - onReturnExecScript(): Correct script executed.");
+ }
+ }
+ }
+ }
+ }
+
+ function execScript() as Void {
+ var options = {
+ :method => Communications.HTTP_REQUEST_METHOD_POST,
+ :headers => {
+ "Authorization" => "Bearer " + api_key
+ },
+ :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
+ };
+ if (System.getDeviceSettings().phoneConnected && System.getDeviceSettings().connectionAvailable) {
+ var url = Globals.getApiUrl() + "/services/" + mIdentifier.substring(0, mIdentifier.find(".")) + "/" + mIdentifier.substring(mIdentifier.find(".")+1, null);
+ if (Globals.debug) {
+ System.println("URL=" + url);
+ System.println("mIdentifier=" + mIdentifier);
+ }
+ Communications.makeWebRequest(
+ url,
+ null,
+ options,
+ method(:onReturnExecScript)
+ );
+ } else {
+ if (Globals.debug) {
+ System.println("HomeAssistantMenuItem Note - executeScript(): No Internet connection, skipping API call.");
+ }
+ }
+ }
+
+}
diff --git a/source/HomeAssistantToggleMenuItem.mc b/source/HomeAssistantToggleMenuItem.mc
new file mode 100644
index 0000000..342a9e1
--- /dev/null
+++ b/source/HomeAssistantToggleMenuItem.mc
@@ -0,0 +1,142 @@
+import Toybox.Lang;
+import Toybox.WatchUi;
+import Toybox.Graphics;
+using Toybox.Application.Properties;
+
+class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
+ hidden var api_key = Properties.getValue("api_key");
+ hidden var menu;
+
+ function initialize(
+ menu as HomeAssistantView,
+ label as Lang.String or Lang.Symbol,
+ subLabel as Lang.String or Lang.Symbol or {
+ :enabled as Lang.String or Lang.Symbol or Null,
+ :disabled as Lang.String or Lang.Symbol or Null
+ } or Null,
+ identifier,
+ enabled as Lang.Boolean,
+ options as {
+ :alignment as MenuItem.Alignment,
+ :icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
+ } or Null
+ ) {
+ WatchUi.ToggleMenuItem.initialize(label, subLabel, identifier, enabled, options);
+ api_key = Properties.getValue("api_key");
+ self.menu = menu;
+ }
+
+ private function setUiToggle(state as Null or Lang.String) as Void {
+ if (state != null) {
+ if (state.equals("on") && !isEnabled()) {
+ setEnabled(true);
+ } else if (state.equals("off") && isEnabled()) {
+ setEnabled(false);
+ }
+ WatchUi.requestUpdate();
+ }
+ }
+
+ // Callback function after completing the GET request to fetch the status.
+ //
+ function onReturnGetState(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
+ if (Globals.debug) {
+ System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: " + responseCode);
+ System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Data: " + data);
+ }
+ if (responseCode == 200) {
+ var state = data.get("state") as Lang.String;
+ if (Globals.debug) {
+ System.println((data.get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state);
+ }
+ if (getLabel().equals("...")) {
+ setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String);
+ }
+ setUiToggle(state);
+ }
+ }
+
+ function getState() as Void {
+ var options = {
+ :method => Communications.HTTP_REQUEST_METHOD_GET,
+ :headers => {
+ "Authorization" => "Bearer " + api_key
+ },
+ :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
+ };
+ if (System.getDeviceSettings().phoneConnected && System.getDeviceSettings().connectionAvailable) {
+ var url = Globals.getApiUrl() + "/states/" + mIdentifier;
+ if (Globals.debug) {
+ System.println("URL=" + url);
+ }
+ Communications.makeWebRequest(
+ url,
+ null,
+ options,
+ method(:onReturnGetState)
+ );
+ } else {
+ if (Globals.debug) {
+ System.println("HomeAssistantToggleMenuItem Note - getState(): No Internet connection, skipping API call.");
+ }
+ }
+ }
+
+ // Callback function after completing the POST request to set the status.
+ //
+ function onReturnSetState(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
+ if (Globals.debug) {
+ System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: " + responseCode);
+ System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Data: " + data);
+ }
+ if (responseCode == 200) {
+ var state;
+ var d = data as Lang.Array;
+ for(var i = 0; i < d.size(); i++) {
+ if ((d[i].get("entity_id") as Lang.String).equals(mIdentifier)) {
+ state = d[i].get("state") as Lang.String;
+ if (Globals.debug) {
+ System.println((d[i].get("attributes") as Lang.Dictionary).get("friendly_name") + " State=" + state);
+ }
+ setUiToggle(state);
+ }
+ }
+ }
+ }
+
+ function setState(s as Lang.Boolean) as Void {
+ var options = {
+ :method => Communications.HTTP_REQUEST_METHOD_POST,
+ :headers => {
+ "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON,
+ "Authorization" => "Bearer " + api_key
+ },
+ :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
+ };
+ if (System.getDeviceSettings().phoneConnected && System.getDeviceSettings().connectionAvailable) {
+ var url;
+ if (s) {
+ url = Globals.getApiUrl() + "/services/" + mIdentifier.substring(0, mIdentifier.find(".")) + "/turn_on";
+ } else {
+ url = Globals.getApiUrl() + "/services/" + mIdentifier.substring(0, mIdentifier.find(".")) + "/turn_off";
+ }
+ if (Globals.debug) {
+ System.println("URL=" + url);
+ System.println("mIdentifier=" + mIdentifier);
+ }
+ Communications.makeWebRequest(
+ url,
+ {
+ "entity_id" => mIdentifier
+ },
+ options,
+ method(:onReturnSetState)
+ );
+ } else {
+ if (Globals.debug) {
+ System.println("HomeAssistantToggleMenuItem Note - setState(): No Internet connection, skipping API call.");
+ }
+ }
+ }
+
+}
diff --git a/source/HomeAssistantView.mc b/source/HomeAssistantView.mc
new file mode 100644
index 0000000..6de07a4
--- /dev/null
+++ b/source/HomeAssistantView.mc
@@ -0,0 +1,154 @@
+import Toybox.Lang;
+import Toybox.Graphics;
+import Toybox.WatchUi;
+
+class HomeAssistantView extends WatchUi.Menu2 {
+ hidden var timer;
+
+ function initialize() {
+ timer = new Timer.Timer();
+
+ var toggle_obj = {
+ :enabled => "On",
+ :disabled => "Off"
+ };
+
+ WatchUi.Menu2.initialize({
+ :title => "Entities"
+ });
+ addItem(
+ new HomeAssistantToggleMenuItem(
+ self,
+ "Bedroom Light",
+ toggle_obj,
+ "light.philip_s_bedside_light_switch",
+ false,
+ null
+ )
+ );
+ addItem(
+ new HomeAssistantToggleMenuItem(
+ self,
+ "Lounge Lights",
+ toggle_obj,
+ "light.living_room_ambient_lights_all",
+ false,
+ null
+ )
+ );
+ addItem(
+ new HomeAssistantMenuItem(
+ "Food is Ready!",
+ null,
+ "script.food_is_ready",
+ null
+ )
+ );
+ // addItem(
+ // new HomeAssistantMenuItem(
+ // "Test Script",
+ // null,
+ // "script.test",
+ // null
+ // )
+ // );
+ addItem(
+ new HomeAssistantToggleMenuItem(
+ self,
+ "Bookcase USBs",
+ toggle_obj,
+ "switch.bookcase_usbs",
+ false,
+ null
+ )
+ );
+ addItem(
+ new HomeAssistantToggleMenuItem(
+ self,
+ "Corner Table USBs",
+ toggle_obj,
+ "switch.corner_table_usbs",
+ false,
+ null
+ )
+ );
+ }
+
+ // Load your resources here
+ function onLayout(dc as Dc) as Void {
+ setLayout(Rez.Layouts.MainLayout(dc));
+ }
+
+ // Called when this View is brought to the foreground. Restore
+ // the state of this View and prepare it to be shown. This includes
+ // loading resources into memory.
+ function onShow() as Void {
+ timer.start(
+ method(:timerUpdate),
+ Globals.updateInterval * 1000,
+ true
+ );
+ for(var i = 0; i < mItems.size(); i++) {
+ if (mItems[i] instanceof HomeAssistantToggleMenuItem) {
+ var toggleItem = mItems[i] as HomeAssistantToggleMenuItem;
+ toggleItem.getState();
+ if (Globals.debug) {
+ System.println("HomeAssistantView Note: " + toggleItem.getLabel() + " ID=" + toggleItem.getId() + " Enabled=" + toggleItem.isEnabled());
+ }
+ }
+ }
+ }
+
+ // Update the view
+ function onUpdate(dc as Dc) as Void {
+ View.onUpdate(dc);
+ }
+
+ // Called when this View is removed from the screen. Save the
+ // state of this View here. This includes freeing resources from
+ // memory.
+ function onHide() as Void {
+ timer.stop();
+ }
+
+ function timerUpdate() as Void {
+ for(var i = 0; i < mItems.size(); i++) {
+ if (mItems[i] instanceof HomeAssistantToggleMenuItem) {
+ var toggleItem = mItems[i] as HomeAssistantToggleMenuItem;
+ toggleItem.getState();
+ if (Globals.debug) {
+ System.println("HomeAssistantView Note: " + toggleItem.getLabel() + " ID=" + toggleItem.getId() + " Enabled=" + toggleItem.isEnabled());
+ }
+ }
+ }
+ }
+
+}
+
+class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
+
+ function initialize() {
+ Menu2InputDelegate.initialize();
+ }
+
+ function onSelect(item as WatchUi.MenuItem) as Void {
+ if (item instanceof HomeAssistantToggleMenuItem) {
+ var haToggleItem = item as HomeAssistantToggleMenuItem;
+ if (Globals.debug) {
+ System.println(haToggleItem.getLabel() + " " + haToggleItem.getId() + " " + haToggleItem.isEnabled());
+ }
+ haToggleItem.setState(haToggleItem.isEnabled());
+ } else if (item instanceof HomeAssistantMenuItem) {
+ var haItem = item as HomeAssistantMenuItem;
+ if (Globals.debug) {
+ System.println(haItem.getLabel() + " " + haItem.getId());
+ }
+ haItem.execScript();
+ } else {
+ if (Globals.debug) {
+ System.println(item.getLabel() + " " + item.getId());
+ }
+ }
+ }
+
+}
\ No newline at end of file