Site Overlay

Home automation 7 – watch control

The most common thing I find myself doing with my current home automation setup is turning my fan and light on and off, but it's still not as easy as it could be. To turn off my fan, I open the site on my PC and toggle it there. For my light, I have a hardware dimmer switch near my bed for turning it off at night.

I currently use a Fossil Gen 5 smartwatch, which has Google's seemingly abandoned Wear OS installed. I use this watch and OS mainly because of Google Assistant support, which is pretty good and automatically integrates into your Google account, which is quite nice.

A lot of people don't recommend Wear OS over others (like Samsung's Galaxy Watch), but I've tried this same experiment on the Galaxy Watch Active2, and I can tell you, coding anything on that watch is an absolute nightmare. At least on Wear OS you can use familiar Android tools!

I think the main problem with Wear OS is it's lack of useful software. All of it's great features are very hidden, so when a user first gets their Wear OS watch they just end up looking at the app on their phone, with like 5 buttons and a handful of watch faces, and that's it. Meanwhile, you actually need to look through the Play Store to look for watch faces and apps, and can even get them directly on the watch through the Play Store installed there. After using both Samsung's Galaxy Watch and a Wear OS watch, they really do all the same things (although Samsung's integration with their own apps is much better, like dismissing an alarm ringing on your phone by snoozing it on your watch, etc)…

But that's not the point here. The point is, I want to be able to control my room light and my fan with my watch! So, let's make it happen!

Getting the address

openHAB has a convenient REST API, and when used with the myopenhab Cloud Connector, that same API is exposed over the internet behind a secure address, so that's all we need. We need two APIs for each device: one to query the on/off status of the fan or light, and the other to set it's status.

After looking through the also very convenient API documentation generator and a bit of googling, this is the full API for those two things:

GET or PUT
https://my%40email.com:password@home.myopenhab.org/rest/items/ITEM_NAME/state

The GET call returns the text ON, OFF, or NULL for the fan, since it uses a Switch type item (null if it hasn't been set, so we can consider that as OFF). For the light it uses a Dimmer type item, so it returns a number from 0 to 100 instead.

To modify the items, we just use PUT instead of GET, and the body is the value we want it to be (ON/OFF for the fan, 0-100 for the light).

Create the watch app

The Fossil Gen 5 smartwatch has two user-programmable buttons. I have one of them mapped to Keep Notes, which is amazingly useful. The other one opens Spotify, but since I don't use that much I'm going to use that second button to launch my home control app. This means if I want to toggle my fan or light, I just have to press the bottom button on my watch and then tap the icon on the screen for watch or fan. Simple!

First, I need to create the watch app though. Easily done through Android Studio. I'll use the Blank Activity template. I'll leave "Pair with Empty Phone app" off, since I have no idea what that means.

creating the project

Connect the watch

First I had to enable developer mode by tapping the Build number, enable ADB debugging over WiFi, and connect to the watch. This is amazingly similar to Android development.

After that, the watch appeared in Android Studio, I could hit the Run button, and the app appeared on the watch. That was easy.

easy

Setup the UI layout

Ok, so now the next part is to create the UI layout. Android Studio has a visual drag-n-drop editor, so this part is simple. We just need two buttons, which are TOGGLE FAN and TOGGLE LIGHT.

two buttons, right in the center

Add the code

Now we just need to call our APIs that we discovered above in response to tapping on the buttons. To make things easier, I'm going to add one of my favorite Kotlin libraries for promises (Kovenant) as a dependency.

class MainActivity : WearableActivity() {

    // Username
    val username = "email@me.com"
    val password = "myopenhab_password"

    // Called on startup
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Enables Always-on
        setAmbientEnabled()
    }

    fun onLightPress(view : View) {
        toggleItem("Josh_Bedside_Lamp", "0", "100")
    }

    fun onFanPress(view : View) {
        toggleItem("Josh_Fan", "ON", "OFF")
    }

    // Do an API call to openHAB
    fun doCall(method : String, endpoint : String, body : String = "") = task {

        // Open connection
        Log.i("Network", "$method $endpoint")
        val conn = URL("https://home.myopenhab.org$endpoint").openConnection() as HttpURLConnection
        conn.requestMethod = method

        // Add auth header
        val authHeader = "Basic " + String(Base64.getEncoder().encode("$username:$password".toByteArray()), Charset.forName("UTF-8"))
        conn.setRequestProperty("Authorization", authHeader)

        // Add body if necessary
        if (body.isNotEmpty()) {
            conn.setRequestProperty("Content-Type", "text/plain")
            conn.doOutput = true
            conn.outputStream.write(body.toByteArray())
        }

        // Check response code
        if (conn.responseCode == 401)
            throw Exception("Username or password is incorrect.")
        else if (conn.responseCode < 200 || conn.responseCode >= 300)
            throw Exception("Returned error code ${conn.responseCode}")

        // Read response
        val buffer = ByteArray(1024)
        val len = conn.inputStream.read(buffer)
        return@task String(buffer, 0, len)

    }

    // Toggle an item
    fun toggleItem(itemName : String, offState : String, onState : String) = task {} bind {

        // Get current state
        doCall("GET", "/rest/items/$itemName/state")

    } bind {

        // Toggle state
        val newState = if (it == onState) offState else onState
        Log.i("Home", "item=$itemName, oldState=$it, newState=$newState")
        doCall("PUT", "/rest/items/$itemName/state", newState)

    } success {

        // Done
        Log.i("Home", "Item toggled for $itemName")

    } fail {

        // Failed!
        Log.w("Home", "Failed to toggle state for $itemName: ${it.localizedMessage}")
        it.printStackTrace()

    }

}

Done! And now I can just bind the app to the bottom button on my watch, and that's it!

< Part 6            First            Part 8 >