Compare commits

...

5 Commits

Author SHA1 Message Date
43d61ab853 Show more command data 2023-03-06 19:45:46 +01:00
79a121263f Fix connection issues 2023-03-06 18:57:08 +01:00
00ac6912e2 Fixed setting wrong current value 2023-03-05 19:03:03 +01:00
0f630e854e Read out connection 2023-03-05 18:46:51 +01:00
1ef5f7a64d Add badges 2023-03-04 22:38:43 +01:00
9 changed files with 86 additions and 21 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@ venv/
__pycache__/ __pycache__/
dist/ dist/
**/*.egg-info/ **/*.egg-info/
test.py test*

View File

@ -1,13 +1,24 @@
**This python package is unofficial and is not related in any way to Haier. It was developed by reversed engineered requests and can stop working at anytime!** **This python package is unofficial and is not related in any way to Haier. It was developed by reversed engineered requests and can stop working at anytime!**
# pyhOn # pyhOn
[![PyPI - Status](https://img.shields.io/pypi/status/pyhOn)](https://pypi.org/project/pyhOn)
[![PyPI](https://img.shields.io/pypi/v/pyhOn?color=blue)](https://pypi.org/project/pyhOn)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyhOn)](https://www.python.org/)
[![PyPI - License](https://img.shields.io/pypi/l/pyhOn)](https://github.com/Andre0512/pyhOn/blob/main/LICENSE)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/pyhOn)](https://pypistats.org/packages/pyhon)
Control your Haier appliances with python! Control your Haier appliances with python!
The idea behind this library is, to make the use of all available commands as simple as possible. The idea behind this library is, to make the use of all available commands as simple as possible.
## Installation
```bash
pip install pyhOn
```
### Quick overview ### Quick overview
To get an idea of what is possible, use the commandline-tool `pyhOn`. This lists all available options of the appliances from your Haier Account. To get an idea of what is possible, use the commandline-tool `pyhOn`. This command requests all available options of connected appliances from the hOn api of your Haier Account.
```commandline ```commandline
$ pyhOn --user example@mail.com --password pass123 $ pyhOn --user example@mail.com --password pass123
========== Waschmaschine ========== ========== WM - Waschmaschine ==========
commands: commands:
pauseProgram: pauseProgram command pauseProgram: pauseProgram command
resumeProgram: resumeProgram command resumeProgram: resumeProgram command
@ -20,7 +31,6 @@ data:
antiAllergyStatus: 0 antiAllergyStatus: 0
... ...
``` ```
The claim is, to see everything what you can see in your hOn app and to execute everything you can execute there.
## Python-API ## Python-API
### List devices ### List devices
@ -67,7 +77,11 @@ async with HonConnection(USER, PASSWORD) as hon:
## Tested devices ## Tested devices
- Haier Washing Machine HW90 - Haier Washing Machine HW90
_Unfortunately I don't have any more haier appliances_ _Unfortunately I don't have any more Haier appliances..._
## Usage example ## Usage example
This library is used for the custom [HomeAssistant Integration "Haier hOn"](https://github.com/Andre0512/hOn). This library is used for the custom [HomeAssistant Integration "Haier hOn"](https://github.com/Andre0512/hOn).
## Contribution
Any kind of contribution is welcome!

View File

@ -24,7 +24,7 @@ def get_arguments():
return vars(parser.parse_args()) return vars(parser.parse_args())
# yaml.dump() would be done the same, but needs an additional import... # yaml.dump() would be done the same, but needs an additional dependency...
def pretty_print(data, key="", intend=0, is_list=False): def pretty_print(data, key="", intend=0, is_list=False):
if type(data) is list: if type(data) is list:
if key: if key:
@ -47,6 +47,18 @@ def pretty_print(data, key="", intend=0, is_list=False):
print(f"{' ' * intend}{'- ' if is_list else ''}{key}{': ' if key else ''}{data}") print(f"{' ' * intend}{'- ' if is_list else ''}{key}{': ' if key else ''}{data}")
def create_command(commands):
result = {}
for name, command in commands.items():
result[name] = {}
for parameter, data in command.parameters.items():
if data.typology == "enum":
result[name][parameter] = data.values
if data.typology == "range":
result[name][parameter] = {"min": data.min, "max": data.max, "step": data.step}
return result
async def main(): async def main():
args = get_arguments() args = get_arguments()
if not (user := args["user"]): if not (user := args["user"]):
@ -55,7 +67,7 @@ async def main():
password = getpass("Password for hOn account: ") password = getpass("Password for hOn account: ")
async with HonConnection(user, password) as hon: async with HonConnection(user, password) as hon:
for device in hon.devices: for device in hon.devices:
print("=" * 10, device.nick_name, "=" * 10) print("=" * 10, device.appliance_type_name, "-", device.nick_name, "=" * 10)
pretty_print({"commands": device.commands}) pretty_print({"commands": device.commands})
pretty_print({"data": device.data}) pretty_print({"data": device.data})

View File

@ -84,7 +84,7 @@ class HonConnection:
return {} return {}
return result return result
async def load_attributes(self, device: HonDevice): async def load_attributes(self, device: HonDevice, loop=False):
params = { params = {
"macAddress": device.mac_address, "macAddress": device.mac_address,
"applianceType": device.appliance_type_name, "applianceType": device.appliance_type_name,
@ -92,6 +92,10 @@ class HonConnection:
} }
url = f"{const.API_URL}/commands/v1/context" url = f"{const.API_URL}/commands/v1/context"
async with self._session.get(url, params=params, headers=await self._headers) as response: async with self._session.get(url, params=params, headers=await self._headers) as response:
if response.status >= 400 and not loop:
_LOGGER.error("%s - Error %s - %s", url, response.status, await response.text)
await self.setup()
return await self.load_attributes(device, loop=True)
return (await response.json()).get("payload", {}) return (await response.json()).get("payload", {})
async def load_statistics(self, device: HonDevice): async def load_statistics(self, device: HonDevice):

8
pyhon/appliances/wm.py Normal file
View File

@ -0,0 +1,8 @@
class Appliance:
def __init__(self, data):
self._data = data
def get(self):
if self._data["lastConnEvent.category"] == "DISCONNECTED":
self._data["machMode"] = "0"
return self._data

View File

@ -31,18 +31,15 @@ class HonCommand:
@property @property
def parameters(self): def parameters(self):
result = {key: parameter.value for key, parameter in self._parameters.items()} return self._parameters
if self._multi:
result |= {"program": self._category}
return result
@property @property
def ancillary_parameters(self): def ancillary_parameters(self):
return {key: parameter.value for key, parameter in self._ancillary_parameters.items()} return {key: parameter.value for key, parameter in self._ancillary_parameters.items()}
async def send(self): async def send(self):
return await self._connector.send_command(self._device, self._name, self.parameters, parameters = {name: parameter.value for name, parameter in self._parameters}
self.ancillary_parameters) return await self._connector.send_command(self._device, self._name, parameters, self.ancillary_parameters)
def get_programs(self): def get_programs(self):
return self._multi return self._multi

View File

@ -1,9 +1,14 @@
import importlib
from pprint import pprint
from pyhon.commands import HonCommand from pyhon.commands import HonCommand
class HonDevice: class HonDevice:
def __init__(self, connector, appliance): def __init__(self, connector, appliance):
self._appliance = appliance self._appliance = appliance
for values in self._appliance.pop("attributes"):
self._appliance[values["parName"]] = values["parValue"]
self._connector = connector self._connector = connector
self._appliance_model = {} self._appliance_model = {}
@ -123,6 +128,10 @@ class HonDevice:
def statistics(self): def statistics(self):
return self._statistics return self._statistics
@property
def appliance(self):
return self._appliance
async def load_commands(self): async def load_commands(self):
raw = await self._connector.load_commands(self) raw = await self._connector.load_commands(self)
self._appliance_model = raw.pop("applianceModel") self._appliance_model = raw.pop("applianceModel")
@ -153,13 +162,15 @@ class HonDevice:
result = {} result = {}
for name, command in self._commands.items(): for name, command in self._commands.items():
for key, parameter in command.parameters.items(): for key, parameter in command.parameters.items():
result[f"{name}.{key}"] = parameter result[f"{name}.{key}"] = parameter.value
return result return result
async def load_attributes(self): async def load_attributes(self):
data = await self._connector.load_attributes(self) data = await self._connector.load_attributes(self)
for name, values in data.get("shadow").get("parameters").items(): for name, values in data.get("shadow").get("parameters").items():
self._attributes[name] = values["parNewVal"] self._attributes[name] = values["parNewVal"]
for name, value in data.get("lastConnEvent").items():
self._attributes[f"lastConnEvent.{name}"] = value
async def load_statistics(self): async def load_statistics(self):
self._statistics = await self._connector.load_statistics(self) self._statistics = await self._connector.load_statistics(self)
@ -169,4 +180,9 @@ class HonDevice:
@property @property
def data(self): def data(self):
return self.attributes | self.parameters | self._appliance | self._statistics result = self.attributes | self.parameters | self.appliance | self._statistics
try:
extra = importlib.import_module(f'appliances.{self.appliance_type_name.lower()}')
return result | extra.Appliance(result).get()
except ModuleNotFoundError:
return result

View File

@ -76,7 +76,7 @@ class HonParameterRange(HonParameter):
@value.setter @value.setter
def value(self, value): def value(self, value):
if self._min <= value <= self._max and not value % self._step: if self._min <= value <= self._max and not value % self._step:
self._value = self._value self._value = value
else: else:
raise ValueError(f"Allowed: min {self._min} max {self._max} step {self._step}") raise ValueError(f"Allowed: min {self._min} max {self._max} step {self._step}")
@ -102,7 +102,7 @@ class HonParameterEnum(HonParameter):
@value.setter @value.setter
def value(self, value): def value(self, value):
if value in self.values: if value in self.values:
self._value = self._value self._value = value
else: else:
raise ValueError(f"Allowed values {self._value}") raise ValueError(f"Allowed values {self._value}")
@ -113,6 +113,7 @@ class HonParameterProgram(HonParameterEnum):
self._command = command self._command = command
self._value = command._category self._value = command._category
self._values = command._multi self._values = command._multi
self._typology = "enum"
@property @property
def value(self): def value(self):
@ -123,4 +124,4 @@ class HonParameterProgram(HonParameterEnum):
if value in self.values: if value in self.values:
self._command.set_program(value) self._command.set_program(value)
else: else:
raise ValueError(f"Allowed values {self._value}") raise ValueError(f"Allowed values {self._values}")

View File

@ -7,18 +7,31 @@ with open("README.md", "r") as f:
setup( setup(
name="pyhOn", name="pyhOn",
version="0.2.1", version="0.2.6",
author="Andre Basche", author="Andre Basche",
description="Control hOn devices with python", description="Control hOn devices with python",
long_description=long_description, long_description=long_description,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
url="https://github.com/Andre0512/pyh0n", project_urls={
"GitHub": "https://github.com/Andre0512/pyhOn",
"PyPI": "https://pypi.org/project/pyhOn",
},
license="MIT", license="MIT",
platforms="any", platforms="any",
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
python_requires=">=3.10", python_requires=">=3.10",
install_requires=["aiohttp"], install_requires=["aiohttp"],
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Software Development :: Libraries :: Python Modules",
],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'pyhOn = pyhon.__main__:start', 'pyhOn = pyhon.__main__:start',