Skip to content

Commit

Permalink
Merge pull request #7 from RobertD502/magicube_support
Browse files Browse the repository at this point in the history
Add Air Magicube support
  • Loading branch information
RobertD502 authored Jun 21, 2023
2 parents 05d8fe0 + a83093c commit 0fd4258
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 16 deletions.
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ Asynchronous Python library for PetKit's API.
This is PetKit's undocumented API. With that said, future changes made by PetKit may break this library. The API endpoint used is determined based on the region your account is locked to. Although support has been added for the PetKit Asia API endpoint, I have not personally tested it.

## **Currently Supported Devices**:
- [D3 Feeder (Fresh Element Infinity)](https://www.amazon.com/PETKIT-Automatic-Stainless-Programmable-Dispenser/dp/B09JFK8BCQ)
- [D4 Feeder (Fresh Element Solo)](https://www.amazon.com/PETKIT-Automatic-Dispenser-Compatible-Freeze-Dried/dp/B09158J9PF/)
- [D4s Feeder (Fresh Element Gemini)](https://www.amazon.com/PETKIT-Automatic-Combination-Dispenser-Stainless/dp/B0BF56RTQH)

`Feeders`
- [D3 (Fresh Element Infinity)](https://www.amazon.com/PETKIT-Automatic-Stainless-Programmable-Dispenser/dp/B09JFK8BCQ)
- [D4 (Fresh Element Solo)](https://www.amazon.com/PETKIT-Automatic-Dispenser-Compatible-Freeze-Dried/dp/B09158J9PF/)
- [D4s (Fresh Element Gemini)](https://www.amazon.com/PETKIT-Automatic-Combination-Dispenser-Stainless/dp/B0BF56RTQH)
- [Mini Feeder](https://www.amazon.com/PETKIT-Automatic-Stainless-Indicator-Dispenser-2-8L/dp/B08GS1CPHH/)
- [W5 Water Fountain (Eversweet Solo 2)](https://www.amazon.com/PETKIT-EVERSWEET-Wireless-Visualization-Dispenser-2L/dp/B0B3RWF653)
- [W5 Water Fountain (Eversweet 3 Pro)](https://www.amazon.com/PETKIT-Wireless-Fountain-Stainless-Dispenser/dp/B09QRH6L3M/)
- [W5 Water Fountain (Eversweet 5 Mini)](https://www.petkit.nl/products/eversweet-5-mini-binnen-2-weken-geleverd)
- [T3 Litter Box (Pura X)](https://www.amazon.com/PETKIT-Self-Cleaning-Scooping-Automatic-Multiple/dp/B08T9CCP1M)
- [T4 Litter Box (Pura MAX) with/without Pura Air](https://www.amazon.com/PETKIT-Self-Cleaning-Capacity-Multiple-Automatic/dp/B09KC7Q4YF)

`Litter Boxes`
- [T3 (Pura X)](https://www.amazon.com/PETKIT-Self-Cleaning-Scooping-Automatic-Multiple/dp/B08T9CCP1M)
- [T4 (Pura MAX) with/without Pura Air](https://www.amazon.com/PETKIT-Self-Cleaning-Capacity-Multiple-Automatic/dp/B09KC7Q4YF)

`Purifiers`
- [K2 (Air Magicube)](https://www.instachew.com/product-page/petkit-air-magicube-smart-odor-eliminator)

`Water Fountains`
- [W5 (Eversweet Solo 2)](https://www.amazon.com/PETKIT-EVERSWEET-Wireless-Visualization-Dispenser-2L/dp/B0B3RWF653)
- [W5 (Eversweet 3 Pro)](https://www.amazon.com/PETKIT-Wireless-Fountain-Stainless-Dispenser/dp/B09QRH6L3M/)
- [W5 (Eversweet 5 Mini)](https://www.petkit.nl/products/eversweet-5-mini-binnen-2-weken-geleverd)


## Important

Expand Down
18 changes: 13 additions & 5 deletions petkitaio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
LitterBoxSetting,
LITTER_LIST,
PetSetting,
PurifierCommand,
PurifierCommandKey,
PurifierCommandType,
PUR_CMD_TO_KEY,
PUR_CMD_TO_TYPE,
PUR_CMD_TO_VALUE,
PURIFIER_LIST,
PurifierSetting,
Header,
Endpoint,
Region,
Expand All @@ -31,12 +39,12 @@
)
from .petkit_client import (PetKitClient, LOGGER,)
from .exceptions import (AuthError, BluetoothError, PetKitError, ServerError)
from .model import (Feeder, LitterBox, Pet, PetKitData, W5Fountain, )
from .model import (Feeder, LitterBox, Pet, PetKitData, Purifier, W5Fountain, )
from .str_enum import StrEnum

__all__ = ['ASIA_REGIONS', 'AuthError', 'AUTH_ERROR_CODES', 'BLE_HEADER', 'BluetoothError', 'BLUETOOTH_ERRORS', 'CLIENT_DICT', 'Feeder', 'Endpoint',
'FEEDER_LIST', 'FeederSetting', 'Header', 'LB_CMD_TO_KEY', 'LB_CMD_TO_TYPE', 'LB_CMD_TO_VALUE',
'LitterBox', 'LitterBoxCommand', 'LitterBoxCommandKey', 'LitterBoxCommandType', 'LitterBoxSetting', 'LITTER_LIST',
'LOGGER', 'Pet', 'PetKitClient', 'PetKitData', 'PetKitError', 'PetSetting', 'Region', 'ServerError', 'SERVER_ERROR_CODES', 'StrEnum', 'TIMEOUT',
'WATER_FOUNTAIN_LIST', 'W5Command', 'W5_COMMAND_TO_CODE', 'W5_DND_COMMANDS', 'W5Fountain', 'W5_LIGHT_BRIGHTNESS',
'FEEDER_LIST', 'FeederSetting', 'Header', 'LB_CMD_TO_KEY', 'LB_CMD_TO_TYPE', 'LB_CMD_TO_VALUE', 'LitterBox', 'LitterBoxCommand', 'LitterBoxCommandKey',
'LitterBoxCommandType', 'LitterBoxSetting', 'LITTER_LIST', 'LOGGER', 'Pet', 'PetKitClient', 'PetKitData', 'PetKitError', 'PetSetting', 'Purifier',
'PurifierCommand', 'PurifierCommandKey', 'PurifierCommandType', 'PUR_CMD_TO_KEY', 'PUR_CMD_TO_TYPE', 'PUR_CMD_TO_VALUE', 'PURIFIER_LIST', 'PurifierSetting', 'Region',
'ServerError', 'SERVER_ERROR_CODES', 'StrEnum', 'TIMEOUT', 'WATER_FOUNTAIN_LIST', 'W5Command', 'W5_COMMAND_TO_CODE', 'W5_DND_COMMANDS', 'W5Fountain', 'W5_LIGHT_BRIGHTNESS',
'W5_LIGHT_POWER', 'W5_MODE', 'W5_SETTINGS_COMMANDS', ]
47 changes: 47 additions & 0 deletions petkitaio/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,29 @@ class PetSetting(StrEnum):

WEIGHT = 'weight'

class PurifierSetting(StrEnum):

LIGHT = 'lightMode'
SOUND = 'sound'

class PurifierCommand(StrEnum):

POWER = 'power'
AUTO_MODE = 'auto_mode'
SILENT_MODE = 'silent_mode'
STANDARD_MODE = 'standard_mode'
STRONG_MODE = 'strong_mode'

class PurifierCommandKey(StrEnum):

POWER = 'power_action'
MODE = 'mode_action'

class PurifierCommandType(StrEnum):

POWER = 'power'
MODE = 'mode'

class W5Command(StrEnum):

PAUSE = 'Pause'
Expand Down Expand Up @@ -231,6 +254,7 @@ class W5Command(StrEnum):
BLE_HEADER = [-6, -4, -3]
FEEDER_LIST = ['D3', 'D4', 'D4s', 'FeederMini']
LITTER_LIST = ['T3', 'T4']
PURIFIER_LIST = ['K2']
WATER_FOUNTAIN_LIST = ['W5']

LB_CMD_TO_KEY = {
Expand Down Expand Up @@ -286,6 +310,29 @@ class W5Command(StrEnum):
LitterBoxCommand.RESET_MAX_DEODOR: 8,
}

PUR_CMD_TO_KEY = {
PurifierCommand.POWER: PurifierCommandKey.POWER,
PurifierCommand.AUTO_MODE: PurifierCommandKey.MODE,
PurifierCommand.SILENT_MODE: PurifierCommandKey.MODE,
PurifierCommand.STANDARD_MODE: PurifierCommandKey.MODE,
PurifierCommand.STRONG_MODE: PurifierCommandKey.MODE
}

PUR_CMD_TO_TYPE = {
PurifierCommand.POWER: PurifierCommandType.POWER,
PurifierCommand.AUTO_MODE: PurifierCommandType.MODE,
PurifierCommand.SILENT_MODE: PurifierCommandType.MODE,
PurifierCommand.STANDARD_MODE: PurifierCommandType.MODE,
PurifierCommand.STRONG_MODE: PurifierCommandType.MODE
}

PUR_CMD_TO_VALUE = {
PurifierCommand.AUTO_MODE: 0,
PurifierCommand.SILENT_MODE: 1,
PurifierCommand.STANDARD_MODE: 2,
PurifierCommand.STRONG_MODE: 3
}

W5_COMMAND_TO_CODE = {
W5Command.DO_NOT_DISTURB: '221',
W5Command.DO_NOT_DISTURB_OFF: '221',
Expand Down
14 changes: 14 additions & 0 deletions petkitaio/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class PetKitData:
litter_boxes: Optional[dict[int, Any]] = None
water_fountains: Optional[dict[int, W5Fountain]] = None
pets: Optional[dict[int, Pet]] = None
purifiers: Optional[dict[int, Purifier]] = None


@dataclass
class Feeder:
Expand All @@ -25,6 +27,7 @@ class Feeder:
sound_list: Optional[dict[int, str]] = None
last_manual_feed_id: Optional[str] = None


@dataclass
class LitterBox:
"""Dataclass for PetKit Litter Boxes."""
Expand All @@ -37,6 +40,16 @@ class LitterBox:
manually_paused: bool
manual_pause_end: Optional[datetime] = None


@dataclass
class Purifier:
"""Dataclass for PetKit Purifiers."""

id: int
device_detail: dict[str, Any]
type: str


@dataclass
class W5Fountain:
"""Dataclass for W5 Water Fountain."""
Expand All @@ -46,6 +59,7 @@ class W5Fountain:
type: str
ble_relay: Optional[int] = None


@dataclass
class Pet:
"""Dataclass for registered pets."""
Expand Down
67 changes: 65 additions & 2 deletions petkitaio/petkit_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
LitterBoxSetting,
LITTER_LIST,
PetSetting,
PurifierCommand,
PUR_CMD_TO_KEY,
PUR_CMD_TO_TYPE,
PUR_CMD_TO_VALUE,
PURIFIER_LIST,
PurifierSetting,
Region,
SERVER_ERROR_CODES,
TIMEOUT,
Expand All @@ -43,7 +49,7 @@
W5_SETTINGS_COMMANDS,
)
from petkitaio.exceptions import (AuthError, BluetoothError, PetKitError, ServerError)
from petkitaio.model import (Feeder, LitterBox, Pet, PetKitData, W5Fountain)
from petkitaio.model import (Feeder, LitterBox, Pet, PetKitData, Purifier, W5Fountain)

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -195,6 +201,7 @@ async def get_petkit_data(self) -> PetKitData:
feeders_data: dict[int, Feeder] = {}
litter_boxes_data: dict[int, LitterBox] = {}
pets_data: dict[int, Pet] = {}
purifiers_data: dict[int, Purifier] = {}

devices = device_roster['result']['devices']
LOGGER.debug(f'Found the following PetKit devices: {devices}')
Expand Down Expand Up @@ -365,6 +372,23 @@ async def get_petkit_data(self) -> PetKitData:
manually_paused=manually_paused,
manual_pause_end=manual_pause_end,
)

# Purifiers
if device['type'] in PURIFIER_LIST:
### Fetch device_detail page
dd_url = f'{self.base_url}/{device["type"].lower()}{Endpoint.DEVICE_DETAIL}'
dd_data = {
'id': device['data']['id']
}
device_detail = await self._post(dd_url, header, dd_data)

### Create Purifier Object ###
purifiers_data[device_detail['result']['id']] = Purifier(
id=device_detail['result']['id'],
device_detail=device_detail['result'],
type=device['type'].lower(),
)

### Get user details page
details_url = f'{self.base_url}{Endpoint.USER_DETAILS}'
details_data = {
Expand All @@ -381,7 +405,7 @@ async def get_petkit_data(self) -> PetKitData:
type=pet['type']['name']
)

return PetKitData(user_id=self.user_id, feeders=feeders_data, litter_boxes=litter_boxes_data, water_fountains=fountains_data, pets=pets_data)
return PetKitData(user_id=self.user_id, feeders=feeders_data, litter_boxes=litter_boxes_data, water_fountains=fountains_data, pets=pets_data, purifiers=purifiers_data)


async def _post(self, url: str, headers: dict[str, Any], data: dict[str, Any]) -> dict[str, Any]:
Expand Down Expand Up @@ -719,6 +743,31 @@ async def control_litter_box(self, litter_box: LitterBox, command: LitterBoxComm
## The manual pause will end after a 10-minute wait + 1 minute to complete cleaning
self.manual_pause_end[litter_box.id] = datetime.now() + timedelta(seconds=660)

async def control_purifier(self, purifier: Purifier, command: PurifierCommand) -> None:
"""Control PetKit purifiers."""

url = f'{self.base_url}/{purifier.type}{Endpoint.CONTROL_DEVICE}'
value: int = 0
if command == PurifierCommand.POWER:
# Power of 1 means it is on. Power of 2 means it is on and in standby mode
if purifier.device_detail['state']['power'] in [1, 2]:
value = 0
else:
value = 1
else:
value = PUR_CMD_TO_VALUE[command]
key = PUR_CMD_TO_KEY[command]
header = await self.create_header()
command_dict = {
key: value
}
data = {
'id': purifier.id,
'kv': json.dumps(command_dict),
'type': PUR_CMD_TO_TYPE[command]
}
await self._post(url, header, data)

async def check_manual_pause_expiration(self, id: int):
"""Check to see if manual pause has expired and litter box resumed the cleaning on its own."""

Expand Down Expand Up @@ -817,6 +866,20 @@ async def update_pet_settings(self, pet: Pet, setting: PetSetting, value: int |
}
await self._post(url, header, data)

async def update_purifier_settings(self, purifier: Purifier, setting: PurifierSetting, value: int) -> None:
"""Change the setting on a purifier."""

url = f'{self.base_url}/{purifier.type}{Endpoint.UPDATE_SETTING}'
header = await self.create_header()
setting_dict = {
setting: value
}
data = {
'id': purifier.id,
'kv': json.dumps(setting_dict)
}
await self._post(url, header, data)

async def cancel_manual_feed(self, feeder: Feeder) -> None:
"""Cancel a manual feed that is currently in progress. Not available for mini feeders"""

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="petkitaio",
version="0.1.3",
version="0.1.4",
author="Robert Drinovac",
author_email="unlisted@gmail.com",
description="Asynchronous Python library for PetKit's API",
Expand Down

0 comments on commit 0fd4258

Please sign in to comment.