Compare commits

..

No commits in common. "main" and "v0.15.4" have entirely different histories.

15 changed files with 54 additions and 133 deletions

View File

@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11"]
steps:
- uses: actions/checkout@v3

View File

@ -1,14 +1,3 @@
# Announcement: I have to take the project down in the next few days
> Dear User,
>
> We are writing to inform you that we have discovered two Home Assistant integration plug-ins developed by you ( https://github.com/Andre0512/hon and https://github.com/Andre0512/pyhOn ) that are in violation of our terms of service. Specifically, the plug-ins are using our services in an unauthorized manner which is causing significant economic harm to our Company.
> We take the protection of our intellectual property very seriously and demand that you immediately cease and desist all illegal activities related to the development and distribution of these plug-ins. We also request that you remove the plug-ins from all stores and code hosting platforms where they are currently available.
> Please be advised that we will take all necessary legal action to protect our interests if you fail to comply with this notice. We reserve the right to pursue all available remedies, including but not limited to monetary damages, injunctive relief, and attorney's fees.
> We strongly urge you to take immediate action to rectify this situation and avoid any further legal action. If you have any questions or concerns, please do not hesitate to contact us.
>
> Haier Europe Security and Governance Department
**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

View File

@ -20,7 +20,7 @@ if TYPE_CHECKING:
_LOGGER = logging.getLogger(__name__)
T = TypeVar("T")
T = TypeVar('T')
# pylint: disable=too-many-public-methods,too-many-instance-attributes
@ -94,15 +94,15 @@ class HonAppliance:
@property
def appliance_model_id(self) -> str:
return str(self._info.get("applianceModelId", ""))
return self._info.get("applianceModelId", "")
@property
def appliance_type(self) -> str:
return str(self._info.get("applianceTypeName", ""))
return self._info.get("applianceTypeName", "")
@property
def mac_address(self) -> str:
return str(self.info.get("macAddress", ""))
return self.info.get("macAddress", "")
@property
def unique_id(self) -> str:
@ -138,11 +138,11 @@ class HonAppliance:
@property
def model_id(self) -> int:
return int(self._info.get("applianceModelId", 0))
return self._info.get("applianceModelId", 0)
@property
def options(self) -> Dict[str, Any]:
return dict(self._appliance_model.get("options", {}))
return self._appliance_model.get("options", {})
@property
def commands(self) -> Dict[str, HonCommand]:
@ -277,12 +277,7 @@ class HonAppliance:
_LOGGER.info("Can't set %s - %s", key, error)
continue
def sync_command(
self,
main: str,
target: Optional[List[str] | str] = None,
to_sync: Optional[List[str] | bool] = None,
) -> None:
def sync_command(self, main: str, target: Optional[List[str] | str] = None) -> None:
base: Optional[HonCommand] = self.commands.get(main)
if not base:
return
@ -292,12 +287,7 @@ class HonAppliance:
for name, target_param in data.parameters.items():
if not (base_param := base.parameters.get(name)):
continue
if to_sync and (
(isinstance(to_sync, list) and name not in to_sync)
or not base_param.mandatory
):
continue
return
self.sync_parameter(base_param, target_param)
def sync_parameter(self, main: Parameter, target: Parameter) -> None:

View File

@ -7,10 +7,10 @@ class Appliance(ApplianceBase):
def attributes(self, data: Dict[str, Any]) -> Dict[str, Any]:
data = super().attributes(data)
if data.get("lastConnEvent", {}).get("category", "") == "DISCONNECTED":
data["parameters"]["temp"].value = 0
data["parameters"]["onOffStatus"].value = 0
data["parameters"]["remoteCtrValid"].value = 0
data["parameters"]["remainingTimeMM"].value = 0
data["parameters"]["temp"].value = "0"
data["parameters"]["onOffStatus"].value = "0"
data["parameters"]["remoteCtrValid"].value = "0"
data["parameters"]["remainingTimeMM"].value = "0"
data["active"] = data["parameters"]["onOffStatus"].value == 1
data["active"] = data["parameters"]["onOffStatus"] == "1"
return data

View File

@ -1,16 +1,11 @@
from typing import Any, Dict
from pyhon.appliances.base import ApplianceBase
from pyhon.parameter.base import HonParameter
class Appliance(ApplianceBase):
def attributes(self, data: Dict[str, Any]) -> Dict[str, Any]:
data = super().attributes(data)
parameter = data.get("parameters", {}).get("onOffStatus")
is_class = isinstance(parameter, HonParameter)
data["active"] = parameter.value == 1 if is_class else parameter == 1
return data
data["active"] = data["parameters"]["onOffStatus"] == "1"
def settings(self, settings: Dict[str, Any]) -> Dict[str, Any]:
return settings
return data

View File

@ -184,43 +184,22 @@ class HonCommandLoader:
def _add_favourites(self) -> None:
"""Patch program categories with favourites"""
for favourite in self._favourites:
name, command_name, base = self._get_favourite_info(favourite)
if not base:
name = favourite.get("favouriteName", {})
command = favourite.get("command", {})
command_name = command.get("commandName", "")
program_name = self._clean_name(command.get("programName", ""))
if not (base := self.commands[command_name].categories.get(program_name)):
continue
base_command: HonCommand = copy(base)
self._update_base_command_with_data(base_command, favourite)
self._update_base_command_with_favourite(base_command)
self._update_program_categories(command_name, name, base_command)
def _get_favourite_info(
self, favourite: Dict[str, Any]
) -> tuple[str, str, HonCommand | None]:
name: str = favourite.get("favouriteName", {})
command = favourite.get("command", {})
command_name: str = command.get("commandName", "")
program_name = self._clean_name(command.get("programName", ""))
base_command = self.commands[command_name].categories.get(program_name)
return name, command_name, base_command
def _update_base_command_with_data(
self, base_command: HonCommand, command: Dict[str, Any]
) -> None:
for data in command.values():
if isinstance(data, str):
continue
for key, value in data.items():
if not (parameter := base_command.parameters.get(key)):
continue
if parameter := base_command.parameters.get(key):
with suppress(ValueError):
parameter.value = value
def _update_base_command_with_favourite(self, base_command: HonCommand) -> None:
extra_param = HonParameterFixed("favourite", {"fixedValue": "1"}, "custom")
base_command.parameters.update(favourite=extra_param)
def _update_program_categories(
self, command_name: str, name: str, base_command: HonCommand
) -> None:
program = base_command.parameters["program"]
if isinstance(program, HonParameterProgram):
program.set_value(name)

View File

@ -89,11 +89,8 @@ class HonCommand:
def parameter_value(self) -> Dict[str, Union[str, float]]:
return {n: p.value for n, p in self._parameters.items()}
def _load_parameters(self, attributes: Dict[str, Dict[str, Any] | Any]) -> None:
def _load_parameters(self, attributes: Dict[str, Dict[str, Any]]) -> None:
for key, items in attributes.items():
if not isinstance(items, dict):
_LOGGER.info("Loading Attributes - Skipping %s", str(items))
continue
for name, data in items.items():
self._create_parameters(data, name, key)
for rule in self._rules:
@ -105,12 +102,10 @@ class HonCommand:
if name == "zoneMap" and self._appliance.zone:
data["default"] = self._appliance.zone
if data.get("category") == "rule":
if "fixedValue" in data:
self._rules.append(HonRuleSet(self, data["fixedValue"]))
elif "enumValues" in data:
self._rules.append(HonRuleSet(self, data["enumValues"]))
if "fixedValue" not in data:
_LOGGER.error("Rule not supported: %s", data)
else:
_LOGGER.warning("Rule not supported: %s", data)
self._rules.append(HonRuleSet(self, data["fixedValue"]))
match data.get("typology"):
case "range":
self._parameters[name] = HonParameterRange(name, data, parameter)
@ -135,23 +130,17 @@ class HonCommand:
async def send_specific(self, param_names: List[str]) -> bool:
params: Dict[str, str | float] = {}
for key, parameter in self._parameters.items():
if key in param_names or parameter.mandatory:
if key in param_names:
params[key] = parameter.value
return await self.send_parameters(params)
async def send_parameters(self, params: Dict[str, str | float]) -> bool:
ancillary_params = self.parameter_groups.get("ancillaryParameters", {})
ancillary_params.pop("programRules", None)
if "prStr" in params:
params["prStr"] = self._category_name.upper()
self.appliance.sync_command_to_params(self.name)
try:
result = await self.api.send_command(
self._appliance,
self._name,
params,
ancillary_params,
self._category_name,
self._appliance, self._name, params, ancillary_params
)
if not result:
_LOGGER.error(result)

View File

@ -190,7 +190,6 @@ class HonAPI:
command: str,
parameters: Dict[str, Any],
ancillary_parameters: Dict[str, Any],
program_name: str = "",
) -> bool:
now: str = datetime.utcnow().isoformat()
data: Dict[str, Any] = {
@ -209,8 +208,6 @@ class HonAPI:
"parameters": parameters,
"applianceType": appliance.appliance_type,
}
if command == "startProgram" and program_name:
data.update({"programName": program_name.upper()})
url: str = f"{const.API_URL}/commands/v1/send"
async with self._hon.post(url, json=data) as response:
json_data: Dict[str, Any] = await response.json()
@ -322,12 +319,6 @@ class TestAPI(HonAPI):
command: str,
parameters: Dict[str, Any],
ancillary_parameters: Dict[str, Any],
program_name: str = "",
) -> bool:
_LOGGER.info(
"%s - %s - %s",
str(parameters),
str(ancillary_parameters),
str(program_name),
)
_LOGGER.info("%s - %s", str(parameters), str(ancillary_parameters))
return True

View File

@ -6,7 +6,7 @@ CLIENT_ID = (
"3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9."
"HJvpHGKoAS_dcMN8LYpTSYeVFCraUnV.2Ag1Ki7m4znVO6"
)
APP_VERSION = "2.4.7"
APP_VERSION = "2.1.2"
OS_VERSION = 31
OS = "android"
DEVICE_MODEL = "exynos9820"

View File

@ -68,9 +68,8 @@ class HonParameter:
self._triggers.setdefault(value, []).append((func, data))
def check_trigger(self, value: str | float) -> None:
triggers = {str(k).lower(): v for k, v in self._triggers.items()}
if str(value).lower() in triggers:
for trigger in triggers[str(value)]:
if str(value) in self._triggers:
for trigger in self._triggers[str(value)]:
func, args = trigger
func(args)

View File

@ -18,7 +18,7 @@ class HonParameterFixed(HonParameter):
@property
def value(self) -> str | float:
return self._value if self._value != "" else "0"
return self._value if self._value is not None else "0"
@value.setter
def value(self, value: str | float) -> None:

View File

@ -56,11 +56,6 @@ class HonRuleSet:
extra[trigger_key] = trigger_value
for extra_key, extra_data in param_data.items():
self._parse_conditions(param_key, extra_key, extra_data, extra)
else:
param_data = {"typology": "fixed", "fixedValue": param_data}
self._create_rule(
param_key, trigger_key, trigger_value, param_data, extra
)
def _create_rule(
self,
@ -107,10 +102,6 @@ class HonRuleSet:
param.values = [str(value)]
param.value = str(value)
elif isinstance(param, HonParameterRange):
if float(value) < param.min:
param.min = float(value)
elif float(value) > param.max:
param.max = float(value)
param.value = float(value)
return
param.value = str(value)

View File

@ -1,3 +1,3 @@
aiohttp>=3.8
yarl>=1.8
typing-extensions>=4.8
aiohttp==3.8.5
yarl==1.9.2
typing-extensions==4.7.1

View File

@ -1,5 +1,4 @@
black>=22.12
flake8>=6.0
mypy>=0.991
pylint>=2.15
setuptools>=62.3
black==23.7.0
flake8==6.0.0
mypy==1.4.1
pylint==2.17.4

View File

@ -7,7 +7,7 @@ with open("README.md", "r", encoding="utf-8") as f:
setup(
name="pyhOn",
version="0.15.15",
version="0.15.5",
author="Andre Basche",
description="Control hOn devices with python",
long_description=long_description,
@ -21,7 +21,7 @@ setup(
packages=find_packages(),
include_package_data=True,
python_requires=">=3.10",
install_requires=["aiohttp>=3.8", "typing-extensions>=4.8", "yarl>=1.8"],
install_requires=["aiohttp==3.8.5", "typing-extensions==4.7.1"],
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
@ -30,7 +30,6 @@ setup(
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
],
entry_points={