simbaja / gehome

Python SDK for GE smart appliances
MIT License
45 stars 31 forks source link

gehomesdk

Python SDK for GE WiFi-enabled (SmartHQ) appliances. The primary goal is to use this to power integrations for Home Assistant, though that will probably need to wait on some new entity types.

Forked from Andrew Mark's repository.

Installation

pip install gehomesdk

Changelog

Please click here for change information.

Usage

gehome-appliance-data

As of 0.5.0, after installation, you should now be able to use the gehome-appliance-data application:

gehome-appliance-data [-h] -u USERNAME -p PASSWORD [-r {US,EU}]

The parameters are as follows:

This application will set up a client, iterate over all appliances, and will update the state every minute. It will also capture all state changes as they happen (useful for figuring out which values correspond to which function). You can exit at any time by keyboard interrupt ctrl^C

Credentials

Create a credentials.py and place in the examples directory. It should contain the following:

USERNAME = "your@email.com"
PASSWORD = "supersecret"
REGION = "US or EU"

You will need to replace the username/password values with your credentials. After that, you can run the websocket_example.py (preferred) or xmpp_example.py sample files. These will generate information about your appliances and are useful if you'd like to help implement more functionality.

Simple example

Here we're going to run the client in a pre-existing event loop. We're also going to register some event callbacks to update appliances every five minutes and to turn on our oven the first time we see it. Because that is safe!

import aiohttp
import asyncio
import logging
from gehomesdk.secrets import USERNAME, PASSWORD
from gehomesdk import GeWebsocketClient

_LOGGER = logging.getLogger(__name__)

if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s')

    loop = asyncio.get_event_loop()
    client = GeWebsocketClient(loop, USERNAME, PASSWORD, REGION)

    session = aiohttp.ClientSession()
    asyncio.ensure_future(client.async_get_credentials_and_run(session), loop=loop)
    loop.run_until_complete(asyncio.sleep(60))

    for appliance in client.appliances:
        print(appliance)

Authentication

The authentication process has a few steps. First, for both the websocket and XMPP APIs, we use Oauth2 to authenticate to the HTTPS API. From there, we can either get a websocket endpoint with access_token or proceed with the XMPP login flow. For XMPP, we get a mobile device token, which in turn be used to get a new Bearer token, which, finally, is used to get XMPP credentials to authenticate to the Jabber server. In gehomesdk, going from username/password to XMPP credentials is handled by do_full_xmpp_flow(username, password).

Useful functions

do_full_xmpp_flow(username, password)

Function to authenticate to the web API and get XMPP credentials. Returns a dict of XMPP credentials

do_full_wss_flow(username, password)

Function to authenticate to the web API and get websocket credentials. Returns a dict of WSS credentials

Objects

GeWebsocketClient(event_loop=None, username=None, password=None)

Main Websocket client

GeXmppClient(xmpp_credentials, event_loop=None, **kwargs)

Main XMPP client, and a subclass of slixmpp.ClientXMPP.

GeAppliance(mac_addr, client)

Representation of a single appliance

Useful Enum types

Other types

API Overview

The GE SmartHQ app communicates with devices through (at least) three different APIs: XMPP, HTTP REST, and what they seem to call MQTT (though that's not really accurate). All of them are based around sending (pseudo-)HTTP requests back and forth. Device properties are represented by hex codes (represented by ErdCode objects in gehomesdk), and values are sent as hexadecimal strings without leading "0x", then json encoded as a dictionary. One thing that is important to note is that not all appliances support every API.

For further documentation of appliance commands, please see the GEMaker Github repositories at: https://github.com/GEMakers. Not every appliance has documentation there, but many do, and it is useful for decoding values that you may see coming from the API. There is also another Github repository here that has more information about reverse engineering the GE protocols.

  1. REST - We can access or set most device properties via HTTP REST. Unfortunately, relying on this means we need to result to constantly polling the devices, which is less than desirable, especially, e.g., for ovens that where we want to know exactly when a timer finishes. This API is not directly supported.
  2. Websocket "MQTT" - The WSS "MQTT" API is basically a wrapper around the REST API with the ability to subscribe to a device, meaning that we can treat it as (in Home Assistant lingo) IoT Cloud Push instead of IoT Cloud Polling. In gehomesdk, support for the websocket API is provided by the GeWebsocketClient class.
  3. XMPP - As far as I can tell, there seems to be little, if any, benefit to the XMPP API except that it will notify the client if a new device becomes available. I suspect that this can be achieved with websocket API as well via subscriptions, but have not yet tested. Support for the XMPP API is provided by the GeXmppClient class, based on slixmpp, which it requires as an optional dependency.

XMPP API

The device informs the client of a state change by sending a PUBLISH message like this, informing us that the value of property 0x5205 (ErdCode.LOWER_OVEN_KITCHEN_TIMER in gehomesdk) is now "002d" (45 minutes):

<body>
    <publish>
        <method>PUBLISH</method>
        <uri>/UUID/erd/0x5205</uri>
        <json>{"0x5205":"002d"}</json>
    </publish>
</body>

Similarly, we can set the timer to 45 minutes by POSTing to the same "endpoint":

<body>
    <request>
        <method>POST</method>
        <uri>/UUID/erd/0x5205</uri>
        <json>{"0x5205":"002d"}</json>
    </request>
</body>

In gehomesdk, that would handled by the GeAppliance.set_erd_value method:

appliance.async_set_erd_value(ErdCode.LOWER_OVEN_KITCHEN_TIMER, timedelta(minutes=45))

We can also get a specific property, or, more commonly, request a full cache refresh by GETing the /UUID/cache endpoint:

<body>
    <request>
        <id>0</id>
        <method>GET</method>
        <uri>/UUID/cache</uri>
    </request>
</body>

The device will then respond to the GET with a response having a json payload:

<body>
    <response>
        <id>0</id>
        <method>GET</method>
        <uri>/UUID/cache</uri>
        <json>{
            "0x0006":"00",
            "0x0007":"00",
            "0x0008":"07",
            "0x0009":"00",
            "0x000a":"03",
            "0x0089":"",
            ...
        }</json>
    </response>
</body>