Package: gekitchen
gekitchenPython SDK for GE WiFi-enabled kitchen appliances.
The primary goal is to use this to power integrations forHome Assistant, though that
will probably need to wait on some new entity types.Installationpip install gekitchenUsageSimple exampleHere 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!importaiohttpimportasyncioimportloggingfromgekitchen.secretsimportUSERNAME,PASSWORDfromgekitchenimportGeWebsocketClient_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)session=aiohttp.ClientSession()asyncio.ensure_future(client.async_get_credentials_and_run(session),loop=loop)loop.run_until_complete(asyncio.sleep(60))forapplianceinclient.appliances:print(appliance)AuthenticationThe 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 withaccess_tokenor proceed with the XMPP login
flow.  For XMPP, we get a mobile device token, which in turn be used to get a newBearertoken, which, finally,
is used to get XMPP credentials to authenticate to the Jabber server.  Ingekitchen, going from username/password
to XMPP credentials is handled bydo_full_xmpp_flow(username, password).Useful functionsdo_full_xmpp_flow(username, password)Function to authenticate to the web API and get XMPP credentials.  Returns adictof XMPP credentialsdo_full_wss_flow(username, password)Function to authenticate to the web API and get websocket credentials.  Returns adictof WSS credentialsObjectsGeWebsocketClient(event_loop=None, username=None, password=None)Main Websocket clientevent_loop: asyncio.AbstractEventLoopOptional event loop.  IfNone, the client will useasyncio.get_event_loop()username/passwordOptional strings to use when authenticatingUseful Methodsasync_get_credentials(session, username=None, password=None)Get new WSS credentials using either the specifiedusernameandpasswordor 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 clientPropertiesappliancesADict[str, GeAppliance]of all known appliances keyed on the appliances' JIDs.EventsEVENT_ADD_APPLIANCE- Triggered immediately after a new appliance is added, before the initial update request has
even been sent. TheGeApplianceobject 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. TheGeApplianceobject 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, whereapplianceis theGeApplianceobject with the updated state andstate_changesis 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 appliancesGeXmppClient(xmpp_credentials, event_loop=None, **kwargs)Main XMPP client, and a subclass ofslixmpp.ClientXMPP.xmpp_credentials: dictA dictionary of XMPP credentials, usually obtained from eitherdo_full_login_flowor, in a
more manual process,get_xmpp_credentialsevent_loop: asyncio.AbstractEventLoopOptional event loop.  IfNone, the client will useasyncio.get_event_loop()**kwargsPassed toslixmpp.ClientXMPPUseful Methodsconnect()Connect to the XMPP serverprocess_in_running_loop(timeout: Optional[int] = None)Run in an existing event loop.  Iftimeoutis given, stop
running aftertimeoutsecondsadd_event_handler(name: str, func: Callable)Add an event handler.  In addition to the events supported byslixmpp.ClientXMPP, we've added some more event types detailed below.PropertiesappliancesADict[str, GeAppliance]of all known appliances keyed on the appliances' JIDs.EventsIn addition to the standardslixmppevents, theGeClientobject 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. TheGeApplianceobject 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. TheGeApplianceobject 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, whereapplianceis theGeApplianceobject with the updated state andstate_changesis a dictionary{erd_key: new_value}of the changed
state.GeAppliance(mac_addr, client)Representation of a single appliancemac_addr: Union[str, slixmpp.JID]The appliance's MAC address, which is what GE uses as unique identifiersclient: GeBaseClientThe client used to communicate with the deviceUseful Methodsdecode_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 codeerd_code.  Iferd_codeis a string, this
function will attempt to convert it to anErdCodeobject 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 byerd_codetovalueset_unavailable()Mark the appliance as unavailableupdate_erd_value(erd_code: ErdType, value)Update the local property cache value forerd_codetovalue, where
value is the not yet decoded hex string sent from the API. ReturnsTrueif that is a change in state,Falseotherwise.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 emptydictif nothing actually changed.Propertiesappliance_type: Optional[ErdApplianceType]The type of appliance,Noneif unknownavailable: boolTrueif the appliance is available, otherwiseFalsemac_addrThe appliance's MAC address (used as the appliance ID)UsefulEnumtypesErdCodeEnumof known ERD property codesErdApplianceTypeValues forErdCode.APPLIANCE_TYPEErdMeasurementUnitsValues forErdCode.TEMPERATURE_UNITErdOvenCookModePossible oven cook modes, used forOvenCookSettingamong other thingsErdOvenStateValues forErdCode.LOWER_OVEN_CURRENT_STATEandErdCode.UPPER_OVEN_CURRENT_STATEOther typesOvenCookSettingAnamedtupleof anErdOvenCookModeand aninttemperatureOvenConfigurationAnamedtupleof boolean properties representing an oven's physical configurationAPI OverviewThe 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 byErdCodeobjects ingekitchen), 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.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.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.  Ingekitchen, support for the websocket API is provided by theGeWebsocketClientclass.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 theGeXmppClientclass, based onslixmpp, which it requires as an optional dependency.XMPP APIThe device informs the client of a state change by sending aPUBLISHmessage like this, informing us that the value of
property 0x5205 (ErdCode.LOWER_OVEN_KITCHEN_TIMERingekitchen) 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 byPOSTing to the same "endpoint":<body><request><method>POST</method><uri>/UUID/erd/0x5205</uri><json>{"0x5205":"002d"}</json></request></body>Ingekitchen, that would handled by theGeAppliance.set_erd_valuemethod: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 byGETing the/UUID/cacheendpoint:<body><request><id>0</id><method>GET</method><uri>/UUID/cache</uri></request></body>The device will then respond to theGETwith aresponsehaving 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>