summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCoprDistGit <infra@openeuler.org>2023-03-09 11:24:02 +0000
committerCoprDistGit <infra@openeuler.org>2023-03-09 11:24:02 +0000
commitf4302643785059e5b41ebe2747e69ed992118a05 (patch)
tree36acfbb002299e9e97a2b9e841c65470c974a99f
parent70dc20617875a70e04b65843a71997ad08085176 (diff)
automatic import of python-gekitchen
-rw-r--r--.gitignore1
-rw-r--r--python-gekitchen.spec740
-rw-r--r--sources1
3 files changed, 742 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..dffa53d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/gekitchen-0.2.20.tar.gz
diff --git a/python-gekitchen.spec b/python-gekitchen.spec
new file mode 100644
index 0000000..5503917
--- /dev/null
+++ b/python-gekitchen.spec
@@ -0,0 +1,740 @@
+%global _empty_manifest_terminate_build 0
+Name: python-gekitchen
+Version: 0.2.20
+Release: 1
+Summary: Python SDK for GE Kitchen Appliances
+License: MIT
+URL: https://github.com/ajmarks/gekitchen
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/bf/51/fd2c613f8956f48b3b29822706432924091bda6cdac1936e97b359a66f2e/gekitchen-0.2.20.tar.gz
+BuildArch: noarch
+
+Requires: python3-aiohttp
+Requires: python3-bidict
+Requires: python3-requests
+Requires: python3-websockets
+Requires: python3-slixmpp
+
+%description
+# gekitchen
+Python SDK for GE WiFi-enabled kitchen appliances.
+The primary goal is to use this to power integrations for [Home Assistant](https://www.home-assistant.io/), though that
+will probably need to wait on some new entity types.
+
+## Installation
+```pip install gekitchen```
+
+## Usage
+### 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!
+```python
+import aiohttp
+import asyncio
+import logging
+from gekitchen.secrets import USERNAME, PASSWORD
+from gekitchen 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)
+
+ 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 `gekitchen`, 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
+ * `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
+#### Useful Methods
+ * `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 above
+ * `add_event_handler(event, callback)` Add an event handler
+ * `disconnect()` Disconnect the client
+ * `async_run_client()` Run the client
+ * `async_get_credentials_and_run(sessions, username=None, password=None)` Authenticate and run the client
+#### Properties
+ * `appliances` A `Dict[str, GeAppliance]` of all known appliances keyed on the appliances' JIDs.
+#### Events
+* `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 changed
+* `EVENT_CONNECTED` - Triggered when the API connects, after adding basic subscriptions
+* `EVENT_DISCONNECTED` - Triggered when the API disconnects
+* `EVENT_GOT_APPLIANCE_LIST` - Triggered when we get the list of appliances
+
+### GeXmppClient(xmpp_credentials, event_loop=None, **kwargs)
+Main 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`
+#### Useful Methods
+ * `connect()` Connect to the XMPP server
+ * `process_in_running_loop(timeout: Optional[int] = None)` Run in an existing event loop. If `timeout` is given, stop
+ running after `timeout` seconds
+ * `add_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.
+#### Properties
+ * `appliances` A `Dict[str, GeAppliance]` of all known appliances keyed on the appliances' JIDs.
+#### Events
+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.
+
+### GeAppliance(mac_addr, client)
+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
+#### Useful Methods
+ * `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 properties
+ * `set_available()` Mark the appliance as available
+ * `async_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 unavailable
+ * `update_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.
+#### Properties
+ * `appliance_type: Optional[ErdApplianceType]` The type of appliance, `None` if unknown
+ * `available: bool` `True` if the appliance is available, otherwise `False`
+ * `mac_addr` The appliance's MAC address (used as the appliance ID)
+
+
+### Useful `Enum` types
+* `ErdCode` `Enum` of known ERD property codes
+* `ErdApplianceType` Values for `ErdCode.APPLIANCE_TYPE`
+* `ErdMeasurementUnits` Values for `ErdCode.TEMPERATURE_UNIT`
+* `ErdOvenCookMode` Possible oven cook modes, used for `OvenCookSetting` among other things
+* `ErdOvenState` Values for `ErdCode.LOWER_OVEN_CURRENT_STATE` and `ErdCode.UPPER_OVEN_CURRENT_STATE`
+
+### Other types
+* `OvenCookSetting` A `namedtuple` of an `ErdOvenCookMode` and an `int` temperature
+* `OvenConfiguration` A `namedtuple` of boolean properties representing an oven's physical configuration
+
+
+
+## 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 `gekitchen`), 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.
+
+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
+ `gekitchen`, support for the websocket API is provided by the `GeWebsocketClient` class.
+2. 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`](https://slixmpp.readthedocs.io/), 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 `gekitchen`) is now "002d" (45 minutes):
+
+```xml
+<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":
+```xml
+<body>
+ <request>
+ <method>POST</method>
+ <uri>/UUID/erd/0x5205</uri>
+ <json>{"0x5205":"002d"}</json>
+ </request>
+</body>
+```
+In `gekitchen`, that would handled by the `GeAppliance.set_erd_value` method:
+```python
+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:
+
+```xml
+<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:
+```xml
+<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>
+```
+
+
+
+
+%package -n python3-gekitchen
+Summary: Python SDK for GE Kitchen Appliances
+Provides: python-gekitchen
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-gekitchen
+# gekitchen
+Python SDK for GE WiFi-enabled kitchen appliances.
+The primary goal is to use this to power integrations for [Home Assistant](https://www.home-assistant.io/), though that
+will probably need to wait on some new entity types.
+
+## Installation
+```pip install gekitchen```
+
+## Usage
+### 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!
+```python
+import aiohttp
+import asyncio
+import logging
+from gekitchen.secrets import USERNAME, PASSWORD
+from gekitchen 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)
+
+ 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 `gekitchen`, 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
+ * `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
+#### Useful Methods
+ * `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 above
+ * `add_event_handler(event, callback)` Add an event handler
+ * `disconnect()` Disconnect the client
+ * `async_run_client()` Run the client
+ * `async_get_credentials_and_run(sessions, username=None, password=None)` Authenticate and run the client
+#### Properties
+ * `appliances` A `Dict[str, GeAppliance]` of all known appliances keyed on the appliances' JIDs.
+#### Events
+* `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 changed
+* `EVENT_CONNECTED` - Triggered when the API connects, after adding basic subscriptions
+* `EVENT_DISCONNECTED` - Triggered when the API disconnects
+* `EVENT_GOT_APPLIANCE_LIST` - Triggered when we get the list of appliances
+
+### GeXmppClient(xmpp_credentials, event_loop=None, **kwargs)
+Main 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`
+#### Useful Methods
+ * `connect()` Connect to the XMPP server
+ * `process_in_running_loop(timeout: Optional[int] = None)` Run in an existing event loop. If `timeout` is given, stop
+ running after `timeout` seconds
+ * `add_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.
+#### Properties
+ * `appliances` A `Dict[str, GeAppliance]` of all known appliances keyed on the appliances' JIDs.
+#### Events
+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.
+
+### GeAppliance(mac_addr, client)
+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
+#### Useful Methods
+ * `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 properties
+ * `set_available()` Mark the appliance as available
+ * `async_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 unavailable
+ * `update_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.
+#### Properties
+ * `appliance_type: Optional[ErdApplianceType]` The type of appliance, `None` if unknown
+ * `available: bool` `True` if the appliance is available, otherwise `False`
+ * `mac_addr` The appliance's MAC address (used as the appliance ID)
+
+
+### Useful `Enum` types
+* `ErdCode` `Enum` of known ERD property codes
+* `ErdApplianceType` Values for `ErdCode.APPLIANCE_TYPE`
+* `ErdMeasurementUnits` Values for `ErdCode.TEMPERATURE_UNIT`
+* `ErdOvenCookMode` Possible oven cook modes, used for `OvenCookSetting` among other things
+* `ErdOvenState` Values for `ErdCode.LOWER_OVEN_CURRENT_STATE` and `ErdCode.UPPER_OVEN_CURRENT_STATE`
+
+### Other types
+* `OvenCookSetting` A `namedtuple` of an `ErdOvenCookMode` and an `int` temperature
+* `OvenConfiguration` A `namedtuple` of boolean properties representing an oven's physical configuration
+
+
+
+## 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 `gekitchen`), 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.
+
+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
+ `gekitchen`, support for the websocket API is provided by the `GeWebsocketClient` class.
+2. 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`](https://slixmpp.readthedocs.io/), 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 `gekitchen`) is now "002d" (45 minutes):
+
+```xml
+<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":
+```xml
+<body>
+ <request>
+ <method>POST</method>
+ <uri>/UUID/erd/0x5205</uri>
+ <json>{"0x5205":"002d"}</json>
+ </request>
+</body>
+```
+In `gekitchen`, that would handled by the `GeAppliance.set_erd_value` method:
+```python
+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:
+
+```xml
+<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:
+```xml
+<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>
+```
+
+
+
+
+%package help
+Summary: Development documents and examples for gekitchen
+Provides: python3-gekitchen-doc
+%description help
+# gekitchen
+Python SDK for GE WiFi-enabled kitchen appliances.
+The primary goal is to use this to power integrations for [Home Assistant](https://www.home-assistant.io/), though that
+will probably need to wait on some new entity types.
+
+## Installation
+```pip install gekitchen```
+
+## Usage
+### 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!
+```python
+import aiohttp
+import asyncio
+import logging
+from gekitchen.secrets import USERNAME, PASSWORD
+from gekitchen 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)
+
+ 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 `gekitchen`, 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
+ * `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
+#### Useful Methods
+ * `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 above
+ * `add_event_handler(event, callback)` Add an event handler
+ * `disconnect()` Disconnect the client
+ * `async_run_client()` Run the client
+ * `async_get_credentials_and_run(sessions, username=None, password=None)` Authenticate and run the client
+#### Properties
+ * `appliances` A `Dict[str, GeAppliance]` of all known appliances keyed on the appliances' JIDs.
+#### Events
+* `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 changed
+* `EVENT_CONNECTED` - Triggered when the API connects, after adding basic subscriptions
+* `EVENT_DISCONNECTED` - Triggered when the API disconnects
+* `EVENT_GOT_APPLIANCE_LIST` - Triggered when we get the list of appliances
+
+### GeXmppClient(xmpp_credentials, event_loop=None, **kwargs)
+Main 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`
+#### Useful Methods
+ * `connect()` Connect to the XMPP server
+ * `process_in_running_loop(timeout: Optional[int] = None)` Run in an existing event loop. If `timeout` is given, stop
+ running after `timeout` seconds
+ * `add_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.
+#### Properties
+ * `appliances` A `Dict[str, GeAppliance]` of all known appliances keyed on the appliances' JIDs.
+#### Events
+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.
+
+### GeAppliance(mac_addr, client)
+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
+#### Useful Methods
+ * `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 properties
+ * `set_available()` Mark the appliance as available
+ * `async_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 unavailable
+ * `update_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.
+#### Properties
+ * `appliance_type: Optional[ErdApplianceType]` The type of appliance, `None` if unknown
+ * `available: bool` `True` if the appliance is available, otherwise `False`
+ * `mac_addr` The appliance's MAC address (used as the appliance ID)
+
+
+### Useful `Enum` types
+* `ErdCode` `Enum` of known ERD property codes
+* `ErdApplianceType` Values for `ErdCode.APPLIANCE_TYPE`
+* `ErdMeasurementUnits` Values for `ErdCode.TEMPERATURE_UNIT`
+* `ErdOvenCookMode` Possible oven cook modes, used for `OvenCookSetting` among other things
+* `ErdOvenState` Values for `ErdCode.LOWER_OVEN_CURRENT_STATE` and `ErdCode.UPPER_OVEN_CURRENT_STATE`
+
+### Other types
+* `OvenCookSetting` A `namedtuple` of an `ErdOvenCookMode` and an `int` temperature
+* `OvenConfiguration` A `namedtuple` of boolean properties representing an oven's physical configuration
+
+
+
+## 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 `gekitchen`), 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.
+
+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
+ `gekitchen`, support for the websocket API is provided by the `GeWebsocketClient` class.
+2. 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`](https://slixmpp.readthedocs.io/), 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 `gekitchen`) is now "002d" (45 minutes):
+
+```xml
+<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":
+```xml
+<body>
+ <request>
+ <method>POST</method>
+ <uri>/UUID/erd/0x5205</uri>
+ <json>{"0x5205":"002d"}</json>
+ </request>
+</body>
+```
+In `gekitchen`, that would handled by the `GeAppliance.set_erd_value` method:
+```python
+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:
+
+```xml
+<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:
+```xml
+<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>
+```
+
+
+
+
+%prep
+%autosetup -n gekitchen-0.2.20
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-gekitchen -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Thu Mar 09 2023 Python_Bot <Python_Bot@openeuler.org> - 0.2.20-1
+- Package Spec generated
diff --git a/sources b/sources
new file mode 100644
index 0000000..39745f7
--- /dev/null
+++ b/sources
@@ -0,0 +1 @@
+9478910ec969a1615966c4ec6e14850c gekitchen-0.2.20.tar.gz