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.
pip install gehomesdk
Please click here for change information.
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
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.
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)
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)
.
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
Main Websocket client
event_loop: asyncio.AbstractEventLoop
Optional event loop. If None
, the client will use asyncio.get_event_loop()
username
/password
Optional strings to use when authenticating
async_get_credentials(session, username=None, password=None)
Get new WSS credentials using either the specified
username
and password
or ones already set in the constructor. get_credentials(username=None, password=None)
Blocking version of the aboveadd_event_handler(event, callback)
Add an event handlerdisconnect()
Disconnect the clientasync_run_client()
Run the clientasync_get_credentials_and_run(sessions, username=None, password=None)
Authenticate and run the client
appliances
A Dict[str, GeAppliance]
of all known appliances keyed on the appliances' JIDs.
EVENT_ADD_APPLIANCE
- Triggered immediately after a new appliance is added, before the initial update request has
even been sent. The GeAppliance
object is passed to the callback.EVENT_APPLIANCE_INITIAL_UPDATE
- Triggered when an appliance's type changes, at which point we know at least a
little about the appliance. The GeAppliance
object is passed to the callback.EVENT_APPLIANCE_STATE_CHANGE
- Triggered when an appliance message with a new state, different from the existing, cached
state is received. A tuple (appliance, state_changes)
is passed to the callback, where appliance
is the
GeAppliance
object with the updated state and state_changes
is a dictionary {erd_key: new_value}
of the changed
state.EVENT_APPLIANCE_UPDATE_RECEIVED
- Triggered after processing an ERD update message whether or not the state changedEVENT_CONNECTED
- Triggered when the API connects, after adding basic subscriptionsEVENT_DISCONNECTED
- Triggered when the API disconnectsEVENT_GOT_APPLIANCE_LIST
- Triggered when we get the list of appliancesMain XMPP client, and a subclass of slixmpp.ClientXMPP
.
xmpp_credentials: dict
A dictionary of XMPP credentials, usually obtained from either do_full_login_flow
or, in a
more manual process, get_xmpp_credentials
event_loop: asyncio.AbstractEventLoop
Optional event loop. If None
, the client will use asyncio.get_event_loop()
**kwargs
Passed to slixmpp.ClientXMPP
connect()
Connect to the XMPP serverprocess_in_running_loop(timeout: Optional[int] = None)
Run in an existing event loop. If timeout
is given, stop
running after timeout
secondsadd_event_handler(name: str, func: Callable)
Add an event handler. In addition to the events supported by
slixmpp.ClientXMPP
, we've added some more event types detailed below.
appliances
A Dict[str, GeAppliance]
of all known appliances keyed on the appliances' JIDs.
In addition to the standard slixmpp
events, the GeClient
object has support for the following:
EVENT_ADD_APPLIANCE
- Triggered immediately after a new appliance is added, before the initial update request has
even been sent. The GeAppliance
object is passed to the callback.EVENT_APPLIANCE_INITIAL_UPDATE
- Triggered when an appliance's type changes, at which point we know at least a
little about the appliance. The GeAppliance
object is passed to the callback.EVENT_APPLIANCE_STATE_CHANGE
- Triggered when an appliance message with a new state, different from the existing, cached
state is received. A tuple (appliance, state_changes)
is passed to the callback, where appliance
is the
GeAppliance
object with the updated state and state_changes
is a dictionary {erd_key: new_value}
of the changed
state.Representation of a single appliance
mac_addr: Union[str, slixmpp.JID]
The appliance's MAC address, which is what GE uses as unique identifiers client: GeBaseClient
The client used to communicate with the device
decode_erd_value(erd_code: ErdCodeType, erd_value: str)
Decode a raw ERD property value.encode_erd_value(erd_code: ErdCodeType, erd_value: str)
Decode a raw ERD property value.get_erd_value(erd_code: ErdCodeType)
Get the cached value of ERD code erd_code
. If erd_code
is a string, this
function will attempt to convert it to an ErdCode
object first.async_request_update()
Request the appliance send an update of all propertiesset_available()
Mark the appliance as availableasync_set_erd_value(erd_code: ErdType, value)
Tell the device to set the property represented by erd_code
to value
set_unavailable()
Mark the appliance as unavailableupdate_erd_value(erd_code: ErdType, value)
Update the local property cache value for erd_code
to value
, where
value is the not yet decoded hex string sent from the API. Returns True
if that is a change in state, False
otherwise.update_erd_values(self, erd_values: Dict[ErdCodeType, str])
Update multiple values in the local property cache.
Returns a dictionary of changed states or an empty dict
if nothing actually changed.
appliance_type: Optional[ErdApplianceType]
The type of appliance, None
if unknownavailable: bool
True
if the appliance is available, otherwise False
mac_addr
The appliance's MAC address (used as the appliance ID)Enum
typesErdCode
Enum
of known ERD property codesErdApplianceType
Values for ErdCode.APPLIANCE_TYPE
ErdMeasurementUnits
Values for ErdCode.TEMPERATURE_UNIT
ErdOvenCookMode
Possible oven cook modes, used for OvenCookSetting
among other thingsErdOvenState
Values for ErdCode.LOWER_OVEN_CURRENT_STATE
and ErdCode.UPPER_OVEN_CURRENT_STATE
OvenCookSetting
A namedtuple
of an ErdOvenCookMode
and an int
temperatureOvenConfiguration
A namedtuple
of boolean properties representing an oven's physical configurationThe 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.
gehomesdk
, support for the websocket API is provided by the GeWebsocketClient
class. GeXmppClient
class, based on
slixmpp
, which it requires as an optional dependency. 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 POST
ing 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 GET
ing 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>