pypetkitapi 1.7.9__tar.gz → 1.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pypetkitapi
3
- Version: 1.7.9
3
+ Version: 1.8.1
4
4
  Summary: Python client for PetKit API
5
5
  Home-page: https://github.com/Jezza34000/pypetkit
6
6
  License: MIT
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
16
16
  Requires-Dist: aiohttp (>=3.10.10,<4.0.0)
17
17
  Requires-Dist: pycryptodome (>=3.19.1,<4.0.0)
18
- Requires-Dist: pydantic (>=1.10.18,<2.0.0)
18
+ Requires-Dist: pydantic (>=1.10.18,<3.0.0)
19
19
  Description-Content-Type: text/markdown
20
20
 
21
21
  # Petkit API Client
@@ -66,6 +66,8 @@ pip install pypetkitapi
66
66
  ## Usage Example:
67
67
 
68
68
  ```python
69
+ import asyncio
70
+ import logging
69
71
  import aiohttp
70
72
  from pypetkitapi.client import PetKitClient
71
73
  from pypetkitapi.command import DeviceCommand, FeederCommand, LBCommand, LBAction, LitterCommand
@@ -46,6 +46,8 @@ pip install pypetkitapi
46
46
  ## Usage Example:
47
47
 
48
48
  ```python
49
+ import asyncio
50
+ import logging
49
51
  import aiohttp
50
52
  from pypetkitapi.client import PetKitClient
51
53
  from pypetkitapi.command import DeviceCommand, FeederCommand, LBCommand, LBAction, LitterCommand
@@ -0,0 +1,86 @@
1
+ """Pypetkit: A Python library for interfacing with PetKit"""
2
+
3
+ from .client import PetKitClient
4
+ from .command import (
5
+ DeviceAction,
6
+ DeviceCommand,
7
+ FeederCommand,
8
+ LBCommand,
9
+ LitterCommand,
10
+ PetCommand,
11
+ PurMode,
12
+ )
13
+ from .const import (
14
+ CTW3,
15
+ D3,
16
+ D4,
17
+ D4H,
18
+ D4S,
19
+ D4SH,
20
+ DEVICES_FEEDER,
21
+ DEVICES_LITTER_BOX,
22
+ DEVICES_PURIFIER,
23
+ DEVICES_WATER_FOUNTAIN,
24
+ FEEDER,
25
+ FEEDER_MINI,
26
+ K2,
27
+ K3,
28
+ T3,
29
+ T4,
30
+ T5,
31
+ T6,
32
+ W5,
33
+ RecordType,
34
+ )
35
+ from .containers import Pet
36
+ from .exceptions import PetkitAuthenticationError, PypetkitError
37
+ from .feeder_container import Feeder, RecordsItems
38
+ from .litter_container import Litter, LitterRecord, WorkState
39
+ from .medias import MediaHandler, MediasFiles
40
+ from .purifier_container import Purifier
41
+ from .water_fountain_container import WaterFountain
42
+
43
+ __version__ = "1.8.1"
44
+
45
+ __all__ = [
46
+ "CTW3",
47
+ "D3",
48
+ "D4",
49
+ "D4H",
50
+ "D4S",
51
+ "D4SH",
52
+ "DEVICES_FEEDER",
53
+ "DEVICES_LITTER_BOX",
54
+ "DEVICES_PURIFIER",
55
+ "DEVICES_WATER_FOUNTAIN",
56
+ "DeviceAction",
57
+ "DeviceCommand",
58
+ "FEEDER",
59
+ "FEEDER_MINI",
60
+ "Feeder",
61
+ "FeederCommand",
62
+ "K2",
63
+ "K3",
64
+ "LBCommand",
65
+ "Litter",
66
+ "LitterCommand",
67
+ "LitterRecord",
68
+ "MediaHandler",
69
+ "MediasFiles",
70
+ "Pet",
71
+ "PetCommand",
72
+ "PetKitClient",
73
+ "PetkitAuthenticationError",
74
+ "PurMode",
75
+ "Purifier",
76
+ "PypetkitError",
77
+ "RecordType",
78
+ "RecordsItems",
79
+ "T3",
80
+ "T4",
81
+ "T5",
82
+ "T6",
83
+ "W5",
84
+ "WaterFountain",
85
+ "WorkState",
86
+ ]
@@ -17,9 +17,11 @@ from pypetkitapi.const import (
17
17
  DEVICE_STATS,
18
18
  DEVICES_FEEDER,
19
19
  DEVICES_LITTER_BOX,
20
+ DEVICES_PURIFIER,
20
21
  DEVICES_WATER_FOUNTAIN,
21
22
  ERR_KEY,
22
23
  LOGIN_DATA,
24
+ PET,
23
25
  RES_KEY,
24
26
  T4,
25
27
  T6,
@@ -40,6 +42,7 @@ from pypetkitapi.exceptions import (
40
42
  )
41
43
  from pypetkitapi.feeder_container import Feeder, FeederRecord
42
44
  from pypetkitapi.litter_container import Litter, LitterRecord, LitterStats, PetOutGraph
45
+ from pypetkitapi.purifier_container import Purifier
43
46
  from pypetkitapi.water_fountain_container import WaterFountain, WaterFountainRecord
44
47
 
45
48
  _LOGGER = logging.getLogger(__name__)
@@ -50,7 +53,7 @@ class PetKitClient:
50
53
 
51
54
  _session: SessionInfo | None = None
52
55
  account_data: list[AccountData] = []
53
- petkit_entities: dict[int, Feeder | Litter | WaterFountain | Pet] = {}
56
+ petkit_entities: dict[int, Feeder | Litter | WaterFountain | Purifier | Pet] = {}
54
57
 
55
58
  def __init__(
56
59
  self,
@@ -75,6 +78,7 @@ class PetKitClient:
75
78
  async def _get_base_url(self) -> None:
76
79
  """Get the list of API servers, filter by region, and return the matching server."""
77
80
  _LOGGER.debug("Getting API server list")
81
+ self.req.base_url = PetkitDomain.PASSPORT_PETKIT
78
82
 
79
83
  if self.region.lower() == "china":
80
84
  self.req.base_url = PetkitDomain.CHINA_SRV
@@ -126,8 +130,9 @@ class PetKitClient:
126
130
  data["validCode"] = valid_code
127
131
  else:
128
132
  _LOGGER.debug("Login method: using password")
129
- pwd = hashlib.md5(self.password.encode()).hexdigest() # noqa: S324
130
- data["password"] = pwd # noqa: S324
133
+ data["password"] = hashlib.md5(
134
+ self.password.encode()
135
+ ).hexdigest() # noqa: S324
131
136
 
132
137
  # Send the login request
133
138
  response = await self.req.request(
@@ -149,6 +154,7 @@ class PetKitClient:
149
154
  )
150
155
  session_data = response["session"]
151
156
  self._session = SessionInfo(**session_data)
157
+ self._session.refreshed_at = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f")
152
158
 
153
159
  async def validate_session(self) -> None:
154
160
  """Check if the session is still valid and refresh or re-login if necessary."""
@@ -195,6 +201,7 @@ class PetKitClient:
195
201
  if account.pet_list:
196
202
  for pet in account.pet_list:
197
203
  self.petkit_entities[pet.pet_id] = pet
204
+ pet.device_nfo = Device(deviceType=PET, deviceId=pet.pet_id)
198
205
 
199
206
  async def get_devices_data(self) -> None:
200
207
  """Get the devices data from the PetKit servers."""
@@ -217,9 +224,11 @@ class PetKitClient:
217
224
 
218
225
  for device in device_list:
219
226
  device_type = device.device_type.lower()
227
+
220
228
  if device_type in DEVICES_FEEDER:
221
229
  main_tasks.append(self._fetch_device_data(device, Feeder))
222
230
  record_tasks.append(self._fetch_device_data(device, FeederRecord))
231
+
223
232
  elif device_type in DEVICES_LITTER_BOX:
224
233
  main_tasks.append(
225
234
  self._fetch_device_data(device, Litter),
@@ -237,6 +246,9 @@ class PetKitClient:
237
246
  self._fetch_device_data(device, WaterFountainRecord)
238
247
  )
239
248
 
249
+ elif device_type in DEVICES_PURIFIER:
250
+ main_tasks.append(self._fetch_device_data(device, Purifier))
251
+
240
252
  # Execute main device tasks first
241
253
  await asyncio.gather(*main_tasks)
242
254
 
@@ -264,6 +276,7 @@ class PetKitClient:
264
276
  Feeder
265
277
  | Litter
266
278
  | WaterFountain
279
+ | Purifier
267
280
  | FeederRecord
268
281
  | LitterRecord
269
282
  | WaterFountainRecord
@@ -309,15 +322,9 @@ class PetKitClient:
309
322
  _LOGGER.error("Unexpected response type: %s", type(response))
310
323
  return
311
324
 
312
- # Add the device type into dataclass
313
- if isinstance(device_data, list):
314
- for item in device_data:
315
- item.device_type = device_type
316
- else:
317
- device_data.device_type = device_type
318
-
319
325
  if data_class.data_type == DEVICE_DATA:
320
326
  self.petkit_entities[device.device_id] = device_data
327
+ self.petkit_entities[device.device_id].device_nfo = device
321
328
  _LOGGER.debug("Device data fetched OK for %s", device_type)
322
329
  elif data_class.data_type == DEVICE_RECORDS:
323
330
  self.petkit_entities[device.device_id].device_records = device_data
@@ -358,9 +365,12 @@ class PetKitClient:
358
365
 
359
366
  pets_list = await self.get_pets_list()
360
367
  for pet in pets_list:
361
- if stats_data.device_type == T4 and stats_data.device_records:
368
+ if stats_data.device_nfo.device_type == T4 and stats_data.device_records:
362
369
  await self._process_t4(pet, stats_data.device_records)
363
- elif stats_data.device_type == T6 and stats_data.device_pet_graph_out:
370
+ elif (
371
+ stats_data.device_nfo.device_type == T6
372
+ and stats_data.device_pet_graph_out
373
+ ):
364
374
  await self._process_t6(pet, stats_data.device_pet_graph_out)
365
375
 
366
376
  async def _process_t4(self, pet, device_records):
@@ -409,15 +419,15 @@ class PetKitClient:
409
419
 
410
420
  _LOGGER.debug(
411
421
  "Control API device=%s id=%s action=%s param=%s",
412
- device.device_type,
422
+ device.device_nfo.device_type,
413
423
  device_id,
414
424
  action,
415
425
  setting,
416
426
  )
417
427
 
418
428
  # Check if the device type is supported
419
- if device.device_type:
420
- device_type = device.device_type.lower()
429
+ if device.device_nfo.device_type:
430
+ device_type = device.device_nfo.device_type.lower()
421
431
  else:
422
432
  raise PypetkitError(
423
433
  "Device type is not available, and is mandatory for sending commands."
@@ -442,7 +452,7 @@ class PetKitClient:
442
452
  else:
443
453
  endpoint = action_info.endpoint
444
454
  _LOGGER.debug("Endpoint field")
445
- url = f"{device.device_type.lower()}/{endpoint}"
455
+ url = f"{device.device_nfo.device_type.lower()}/{endpoint}"
446
456
 
447
457
  # Get the parameters
448
458
  if setting is not None:
@@ -16,6 +16,8 @@ from pypetkitapi.const import (
16
16
  DEVICES_FEEDER,
17
17
  FEEDER,
18
18
  FEEDER_MINI,
19
+ K2,
20
+ K3,
19
21
  T3,
20
22
  T4,
21
23
  T5,
@@ -27,11 +29,13 @@ from pypetkitapi.const import (
27
29
  class DeviceCommand(StrEnum):
28
30
  """Device Command"""
29
31
 
32
+ POWER = "power_device"
33
+ CONTROL_DEVICE = "control_device"
30
34
  UPDATE_SETTING = "update_setting"
31
35
 
32
36
 
33
37
  class FeederCommand(StrEnum):
34
- """Feeder Command"""
38
+ """Specific Feeder Command"""
35
39
 
36
40
  CALL_PET = "call_pet"
37
41
  CALIBRATION = "food_reset"
@@ -45,25 +49,17 @@ class FeederCommand(StrEnum):
45
49
 
46
50
 
47
51
  class LitterCommand(StrEnum):
48
- """LitterCommand"""
52
+ """Specific LitterCommand"""
49
53
 
50
- POWER = "power"
51
- CONTROL_DEVICE = "control_device"
52
54
  RESET_DEODORIZER = "reset_deodorizer"
53
55
 
54
56
 
55
57
  class PetCommand(StrEnum):
56
- """PetCommand"""
58
+ """Specific PetCommand"""
57
59
 
58
60
  PET_UPDATE_SETTING = "pet_update_setting"
59
61
 
60
62
 
61
- class FountainCommand(StrEnum):
62
- """Fountain Command"""
63
-
64
- CONTROL_DEVICE = "control_device"
65
-
66
-
67
63
  class LBCommand(IntEnum):
68
64
  """LitterBoxCommand"""
69
65
 
@@ -79,14 +75,27 @@ class LBCommand(IntEnum):
79
75
  MAINTENANCE = 9
80
76
 
81
77
 
82
- class LBAction(StrEnum):
83
- """LitterBoxCommandKey"""
78
+ class PurMode(IntEnum):
79
+ """Purifier working mode"""
80
+
81
+ AUTO_MODE = 0
82
+ SILENT_MODE = 1
83
+ STANDARD_MODE = 2
84
+ STRONG_MODE = 3
84
85
 
86
+
87
+ class DeviceAction(StrEnum):
88
+ """Device action for LitterBox and Purifier"""
89
+
90
+ # LitterBox only
85
91
  CONTINUE = "continue_action"
86
92
  END = "end_action"
87
- POWER = "power_action"
88
93
  START = "start_action"
89
94
  STOP = "stop_action"
95
+ # Purifier only
96
+ MODE = "mode_action"
97
+ # All devices
98
+ POWER = "power_action"
90
99
 
91
100
 
92
101
  class FountainAction(StrEnum):
@@ -162,6 +171,15 @@ ACTIONS_MAP = {
162
171
  },
163
172
  supported_device=ALL_DEVICES,
164
173
  ),
174
+ DeviceCommand.CONTROL_DEVICE: CmdData(
175
+ endpoint=PetkitEndpoint.CONTROL_DEVICE,
176
+ params=lambda device, command: {
177
+ "id": device.id,
178
+ "kv": json.dumps(command),
179
+ "type": list(command.keys())[0].split("_")[0],
180
+ },
181
+ supported_device=[K2, K3, T3, T4, T5, T6],
182
+ ),
165
183
  FeederCommand.REMOVE_DAILY_FEED: CmdData(
166
184
  endpoint=PetkitEndpoint.REMOVE_DAILY_FEED,
167
185
  params=lambda device, setting: {
@@ -255,15 +273,6 @@ ACTIONS_MAP = {
255
273
  },
256
274
  supported_device=[D3],
257
275
  ),
258
- LitterCommand.CONTROL_DEVICE: CmdData(
259
- endpoint=PetkitEndpoint.CONTROL_DEVICE,
260
- params=lambda device, command: {
261
- "id": device.id,
262
- "kv": json.dumps(command),
263
- "type": list(command.keys())[0].split("_")[0],
264
- },
265
- supported_device=[T3, T4, T5, T6],
266
- ),
267
276
  PetCommand.PET_UPDATE_SETTING: CmdData(
268
277
  endpoint=PetkitEndpoint.CONTROL_DEVICE,
269
278
  params=lambda pet, setting: {
@@ -2,9 +2,6 @@
2
2
 
3
3
  from enum import StrEnum
4
4
 
5
- MIN_FEED_AMOUNT = 0
6
- MAX_FEED_AMOUNT = 10
7
-
8
5
  RES_KEY = "result"
9
6
  ERR_KEY = "error"
10
7
  SUCCESS_KEY = "success"
@@ -29,11 +26,19 @@ T6 = "t6"
29
26
  W5 = "w5"
30
27
  CTW3 = "ctw3"
31
28
  K2 = "k2"
29
+ K3 = "k3"
30
+ PET = "pet"
32
31
 
33
32
  DEVICES_LITTER_BOX = [T3, T4, T5, T6]
34
33
  DEVICES_FEEDER = [FEEDER, FEEDER_MINI, D3, D4, D4S, D4H, D4SH]
35
34
  DEVICES_WATER_FOUNTAIN = [W5, CTW3]
36
- ALL_DEVICES = [*DEVICES_LITTER_BOX, *DEVICES_FEEDER, *DEVICES_WATER_FOUNTAIN]
35
+ DEVICES_PURIFIER = [K2]
36
+ ALL_DEVICES = [
37
+ *DEVICES_LITTER_BOX,
38
+ *DEVICES_FEEDER,
39
+ *DEVICES_WATER_FOUNTAIN,
40
+ *DEVICES_PURIFIER,
41
+ ]
37
42
 
38
43
 
39
44
  class PetkitDomain(StrEnum):
@@ -86,6 +91,18 @@ LOGIN_DATA = {
86
91
  }
87
92
 
88
93
 
94
+ class RecordType(StrEnum):
95
+ """Record Type constants"""
96
+
97
+ EAT = "eat"
98
+ FEED = "feed"
99
+ MOVE = "move"
100
+ PET = "pet"
101
+
102
+
103
+ RecordTypeLST = [RecordType.EAT, RecordType.FEED, RecordType.MOVE, RecordType.PET]
104
+
105
+
89
106
  class PetkitEndpoint(StrEnum):
90
107
  """Petkit Endpoint constants"""
91
108
 
@@ -119,6 +136,11 @@ class PetkitEndpoint(StrEnum):
119
136
  STATISTIC_RELEASE = "statisticRelease"
120
137
  GET_PET_OUT_GRAPH = "getPetOutGraph"
121
138
 
139
+ # Video features
140
+ CLOUD_VIDEO = "cloud/video"
141
+ GET_DOWNLOAD_M3U8 = "getDownloadM3u8"
142
+ GET_M3U8 = "getM3u8"
143
+
122
144
  # Feeders
123
145
  REPLENISHED_FOOD = "added"
124
146
  FRESH_ELEMENT_CALIBRATION = "food_reset"
@@ -1,6 +1,6 @@
1
1
  """Dataclasses container for petkit API."""
2
2
 
3
- from pydantic import BaseModel, Field, root_validator
3
+ from pydantic import BaseModel, Field
4
4
 
5
5
 
6
6
  class RegionInfo(BaseModel):
@@ -43,6 +43,7 @@ class SessionInfo(BaseModel):
43
43
  expires_in: int = Field(alias="expiresIn")
44
44
  region: str | None = None
45
45
  created_at: str = Field(alias="createdAt")
46
+ refreshed_at: str | None = None
46
47
 
47
48
 
48
49
  class Device(BaseModel):
@@ -50,14 +51,14 @@ class Device(BaseModel):
50
51
  Subclass of AccountData.
51
52
  """
52
53
 
53
- created_at: int = Field(alias="createdAt")
54
- device_id: int = Field(alias="deviceId")
55
- device_name: str = Field(alias="deviceName")
56
- device_type: str = Field(alias="deviceType")
57
- group_id: int = Field(alias="groupId")
58
- type: int
59
- type_code: int = Field(alias="typeCode")
60
- unique_id: str = Field(alias="uniqueId")
54
+ created_at: int | None = Field(None, alias="createdAt")
55
+ device_id: int | None = Field(None, alias="deviceId")
56
+ device_name: str | None = Field(None, alias="deviceName")
57
+ device_type: str | None = Field(None, alias="deviceType")
58
+ group_id: int | None = Field(None, alias="groupId")
59
+ type: int | None = None
60
+ type_code: int = Field(0, alias="typeCode")
61
+ unique_id: str | None = Field(None, alias="uniqueId")
61
62
 
62
63
 
63
64
  class Pet(BaseModel):
@@ -69,25 +70,26 @@ class Pet(BaseModel):
69
70
  created_at: int = Field(alias="createdAt")
70
71
  pet_id: int = Field(alias="petId")
71
72
  pet_name: str | None = Field(None, alias="petName")
72
- id: int | None = None # Fictive field (for HA compatibility) copied from id
73
- sn: str # Fictive field (for HA compatibility) copied from id
74
- name: str | None = None # Fictive field (for HA compatibility) copied from pet_name
75
- device_type: str = "pet" # Fictive field (for HA compatibility) fixed
76
- firmware: str | None = None # Fictive field (for HA compatibility) fixed
73
+ id: int | None = None # Fictive field copied from id (for HA compatibility)
74
+ sn: str | None = None # Fictive field copied from id (for HA compatibility)
75
+ name: str | None = None # Fictive field copied from pet_name (for HA compatibility)
76
+ firmware: str | None = None # Fictive fixed field (for HA compatibility)
77
+ device_nfo: Device = Field(default_factory=Device)
77
78
 
78
- # Got from Litter stats
79
+ # Litter stats
79
80
  last_litter_usage: int = 0
80
81
  last_device_used: str | None = None
81
82
  last_duration_usage: int = 0
82
83
  last_measured_weight: int = 0
83
84
 
84
- @root_validator(pre=True)
85
- def populate_fictive_fields(cls, values): # noqa: N805
86
- """Populate fictive fields based on other fields."""
87
- values["id"] = values.get("id") or values.get("petId")
88
- values["sn"] = values.get("sn") or values.get("id")
89
- values["name"] = values.get("name") or values.get("petName")
90
- return values
85
+ def __init__(self, **data):
86
+ """Initialize the Pet dataclass.
87
+ This method is used to fill the fictive fields after the standard initialization.
88
+ """
89
+ super().__init__(**data)
90
+ self.id = self.id or self.pet_id
91
+ self.sn = self.sn or str(self.id)
92
+ self.name = self.name or self.pet_name
91
93
 
92
94
 
93
95
  class User(BaseModel):
@@ -12,12 +12,12 @@ from pypetkitapi.containers import CloudProduct, Device, FirmwareDetail, Wifi
12
12
  class FeedItem(BaseModel):
13
13
  """Dataclass for feed item data."""
14
14
 
15
- id: str | None = None
16
- name: str | None = None
17
- time: int | None = None
18
15
  amount: int | None = None
19
16
  amount1: int | None = Field(None, alias="amount1")
20
17
  amount2: int | None = Field(None, alias="amount2")
18
+ id: str | None = None
19
+ name: str | None = None
20
+ time: int | None = None
21
21
 
22
22
 
23
23
  class FeedDailyList(BaseModel):
@@ -50,9 +50,14 @@ class SettingsFeeder(BaseModel):
50
50
  attire_id: int | None = Field(None, alias="attireId")
51
51
  attire_switch: int | None = Field(None, alias="attireSwitch")
52
52
  auto_product: int | None = Field(None, alias="autoProduct")
53
+ bucket_name1: str | None = Field(None, alias="bucketName1")
54
+ bucket_name2: str | None = Field(None, alias="bucketName2")
53
55
  camera: int | None = None
54
56
  camera_config: int | None = Field(None, alias="cameraConfig")
57
+ camera_multi_new: list[CameraMultiNew] | None = Field(None, alias="cameraMultiNew")
55
58
  camera_multi_range: list | None = Field(None, alias="cameraMultiRange")
59
+ color_setting: int | None = None
60
+ conservation: int | None = None
56
61
  control_settings: int | None = Field(None, alias="controlSettings")
57
62
  desiccant_notify: int | None = Field(None, alias="desiccantNotify")
58
63
  detect_config: int | None = Field(None, alias="detectConfig")
@@ -62,8 +67,10 @@ class SettingsFeeder(BaseModel):
62
67
  eat_notify: int | None = Field(None, alias="eatNotify")
63
68
  eat_sensitivity: int | None = Field(None, alias="eatSensitivity")
64
69
  eat_video: int | None = Field(None, alias="eatVideo")
70
+ factor: int | None = None
65
71
  feed_notify: int | None = Field(None, alias="feedNotify")
66
72
  feed_picture: int | None = Field(None, alias="feedPicture")
73
+ feed_sound: int | None = Field(None, alias="feedSound")
67
74
  food_notify: int | None = Field(None, alias="foodNotify")
68
75
  food_warn: int | None = Field(None, alias="foodWarn")
69
76
  food_warn_range: list[int] | None = Field(None, alias="foodWarnRange")
@@ -87,6 +94,7 @@ class SettingsFeeder(BaseModel):
87
94
  pet_sensitivity: int | None = Field(None, alias="petSensitivity")
88
95
  pre_live: int | None = Field(None, alias="preLive")
89
96
  selected_sound: int | None = Field(None, alias="selectedSound")
97
+ shortest: int | None = None # D4S
90
98
  smart_frame: int | None = Field(None, alias="smartFrame")
91
99
  sound_enable: int | None = Field(None, alias="soundEnable")
92
100
  surplus: int | None = None # D3
@@ -99,35 +107,29 @@ class SettingsFeeder(BaseModel):
99
107
  tone_multi_range: list[tuple[int, int]] | None = Field(None, alias="toneMultiRange")
100
108
  upload: int | None = None
101
109
  volume: int | None = None
102
- feed_sound: int | None = Field(None, alias="feedSound")
103
- factor: int | None = None
104
- color_setting: int | None = Field(None, alias="colorSetting")
105
- conservation: int | None = None
106
- bucket_name1: str | None = Field(None, alias="bucketName1")
107
- bucket_name2: str | None = Field(None, alias="bucketName2")
108
- camera_multi_new: list[CameraMultiNew] | None = Field(None, alias="cameraMultiNew")
109
110
 
110
111
 
111
112
  class FeedState(BaseModel):
112
113
  """Dataclass for feed state data."""
113
114
 
115
+ add_amount_total: int | None = Field(None, alias="addAmountTotal")
116
+ add_amount_total1: int | None = Field(None, alias="addAmountTotal1")
117
+ add_amount_total2: int | None = Field(None, alias="addAmountTotal2")
118
+ eat_amount_total: int | None = Field(None, alias="eatAmountTotal") # D3
114
119
  eat_avg: int | None = Field(None, alias="eatAvg")
115
120
  eat_count: int | None = Field(None, alias="eatCount")
116
121
  eat_times: list[int] | None = Field(None, alias="eatTimes")
117
122
  feed_times: dict | list | None = Field(None, alias="feedTimes")
118
- times: int | None = None
119
- add_amount_total: int | None = Field(None, alias="addAmountTotal")
120
123
  plan_amount_total: int | None = Field(None, alias="planAmountTotal")
121
- plan_real_amount_total: int | None = Field(None, alias="planRealAmountTotal")
122
- real_amount_total: int | None = Field(None, alias="realAmountTotal")
123
- add_amount_total1: int | None = Field(None, alias="addAmountTotal1")
124
- add_amount_total2: int | None = Field(None, alias="addAmountTotal2")
125
124
  plan_amount_total1: int | None = Field(None, alias="planAmountTotal1")
126
125
  plan_amount_total2: int | None = Field(None, alias="planAmountTotal2")
126
+ plan_real_amount_total: int | None = Field(None, alias="planRealAmountTotal")
127
127
  plan_real_amount_total1: int | None = Field(None, alias="planRealAmountTotal1")
128
128
  plan_real_amount_total2: int | None = Field(None, alias="planRealAmountTotal2")
129
+ real_amount_total: int | None = Field(None, alias="realAmountTotal")
129
130
  real_amount_total1: int | None = Field(None, alias="realAmountTotal1")
130
131
  real_amount_total2: int | None = Field(None, alias="realAmountTotal2")
132
+ times: int | None = None
131
133
 
132
134
 
133
135
  class StateFeeder(BaseModel):
@@ -135,31 +137,31 @@ class StateFeeder(BaseModel):
135
137
 
136
138
  battery_power: int | None = Field(None, alias="batteryPower")
137
139
  battery_status: int | None = Field(None, alias="batteryStatus")
138
- bowl: int | None = None
139
140
  block: int | None = None
141
+ bowl: int | None = None
140
142
  broadcast: dict | None = None
141
143
  camera_status: int | None = Field(None, alias="cameraStatus")
142
144
  charge: int | None = None
145
+ conservation_status: int | None = Field(None, alias="conservationStatus")
143
146
  desiccant_left_days: int | None = Field(None, alias="desiccantLeftDays")
144
147
  desiccant_time: int | None = Field(None, alias="desiccantTime")
145
148
  door: int | None = None
146
- feed_state: FeedState | None = Field(None, alias="feedState")
147
- feeding: int | None = None
149
+ eating: int | None = None
148
150
  error_code: str | None = Field(None, alias="errorCode")
149
151
  error_detail: str | None = Field(None, alias="errorDetail")
150
152
  error_level: int | None = Field(None, alias="errorLevel")
151
153
  error_msg: str | None = Field(None, alias="errorMsg")
154
+ feed_state: FeedState | None = Field(None, alias="feedState")
155
+ feeding: int | None = None
156
+ food: int | None = None
157
+ food1: int | None = Field(None, alias="food1")
158
+ food2: int | None = Field(None, alias="food2")
152
159
  ota: int | None = None
153
160
  overall: int | None = None
154
161
  pim: int | None = None
155
162
  runtime: int | None = None
156
163
  weight: int | None = None
157
164
  wifi: Wifi | None = None
158
- eating: int | None = None
159
- food: int | None = None
160
- food1: int | None = Field(None, alias="food1")
161
- food2: int | None = Field(None, alias="food2")
162
- conservation_status: int | None = Field(None, alias="conservationStatus")
163
165
 
164
166
 
165
167
  class ManualFeed(BaseModel):
@@ -265,6 +267,7 @@ class FeederRecord(BaseModel):
265
267
  move: list[RecordsType] | None = None
266
268
  pet: list[RecordsType] | None = None
267
269
  device_type: str | None = Field(None, alias="deviceType")
270
+ type_code: int = Field(0, alias="typeCode")
268
271
 
269
272
  @classmethod
270
273
  def get_endpoint(cls, device_type: str) -> str | None:
@@ -302,15 +305,19 @@ class Feeder(BaseModel):
302
305
  bt_mac: str | None = Field(None, alias="btMac")
303
306
  cloud_product: CloudProduct | None = Field(None, alias="cloudProduct")
304
307
  created_at: str | None = Field(None, alias="createdAt")
308
+ device_records: FeederRecord | None = None
305
309
  firmware: float
306
310
  firmware_details: list[FirmwareDetail] | None = Field(None, alias="firmwareDetails")
307
311
  hardware: int
308
312
  id: int
309
313
  locale: str | None = None
310
314
  mac: str | None = None
315
+ manual_feed: ManualFeed | None = None
311
316
  model_code: int | None = Field(None, alias="modelCode")
317
+ multi_config: bool | None = Field(None, alias="multiConfig")
312
318
  multi_feed_item: MultiFeedItem | None = Field(None, alias="multiFeedItem")
313
319
  name: str | None = None
320
+ p2p_type: int | None = Field(None, alias="p2pType")
314
321
  secret: str | None = None
315
322
  service_status: int | None = Field(None, alias="serviceStatus")
316
323
  settings: SettingsFeeder | None = None
@@ -319,11 +326,7 @@ class Feeder(BaseModel):
319
326
  sn: str
320
327
  state: StateFeeder | None = None
321
328
  timezone: float | None = None
322
- p2p_type: int | None = Field(None, alias="p2pType")
323
- multi_config: bool | None = Field(None, alias="multiConfig")
324
- device_type: str | None = Field(None, alias="deviceType")
325
- manual_feed: ManualFeed | None = None
326
- device_records: FeederRecord | None = None
329
+ device_nfo: Device | None = None
327
330
 
328
331
  @classmethod
329
332
  def get_endpoint(cls, device_type: str) -> str:
@@ -10,6 +10,7 @@ from pypetkitapi.const import (
10
10
  DEVICE_RECORDS,
11
11
  DEVICE_STATS,
12
12
  T4,
13
+ T5,
13
14
  T6,
14
15
  PetkitEndpoint,
15
16
  )
@@ -77,7 +78,6 @@ class SettingsLitter(BaseModel):
77
78
  pet_detection: int | None = Field(None, alias="petDetection")
78
79
  pet_notify: int | None = Field(None, alias="petNotify")
79
80
  pre_live: int | None = Field(None, alias="preLive")
80
- shortest: int | None = None
81
81
  system_sound_enable: int | None = Field(None, alias="systemSoundEnable")
82
82
  time_display: int | None = Field(None, alias="timeDisplay")
83
83
  toilet_detection: int | None = Field(None, alias="toiletDetection")
@@ -235,14 +235,13 @@ class LitterRecord(BaseModel):
235
235
  toilet_detection: int | None = Field(None, alias="toiletDetection")
236
236
  upload: int | None = None
237
237
  user_id: str | None = Field(None, alias="userId")
238
- device_type: str | None = Field(None, alias="deviceType")
239
238
 
240
239
  @classmethod
241
240
  def get_endpoint(cls, device_type: str) -> str:
242
241
  """Get the endpoint URL for the given device type."""
243
242
  if device_type == T4:
244
243
  return PetkitEndpoint.GET_DEVICE_RECORD
245
- if device_type == T6:
244
+ if device_type in [T5, T6]:
246
245
  return PetkitEndpoint.GET_DEVICE_RECORD_RELEASE
247
246
  raise ValueError(f"Invalid device type: {device_type}")
248
247
 
@@ -259,7 +258,7 @@ class LitterRecord(BaseModel):
259
258
  if request_date is None:
260
259
  request_date = datetime.now().strftime("%Y%m%d")
261
260
  return {"date": int(request_date), "deviceId": device.device_id}
262
- if device_type == T6:
261
+ if device_type in [T5, T6]:
263
262
  return {
264
263
  "timestamp": int(datetime.now().timestamp()),
265
264
  "deviceId": device.device_id,
@@ -295,7 +294,6 @@ class LitterStats(BaseModel):
295
294
  statistic_time: str | None = Field(None, alias="statisticTime")
296
295
  times: int | None = None
297
296
  total_time: int | None = Field(None, alias="totalTime")
298
- device_type: str | None = None
299
297
 
300
298
  @classmethod
301
299
  def get_endpoint(cls, device_type: str) -> str:
@@ -359,7 +357,6 @@ class PetOutGraph(BaseModel):
359
357
  storage_space: int | None = Field(None, alias="storageSpace")
360
358
  time: int | None = None
361
359
  toilet_time: int | None = Field(None, alias="toiletTime")
362
- device_type: str | None = None
363
360
 
364
361
  @classmethod
365
362
  def get_endpoint(cls, device_type: str) -> str:
@@ -382,6 +379,31 @@ class PetOutGraph(BaseModel):
382
379
  }
383
380
 
384
381
 
382
+ class K3Device(BaseModel):
383
+ """Dataclass for K3 device data."""
384
+
385
+ battery: int | None = None
386
+ created_at: datetime | None = Field(None, alias="createdAt")
387
+ firmware: int | None = None
388
+ hardware: int | None = None
389
+ id: int | None = None
390
+ lighting: int | None = None
391
+ liquid: int | None = None
392
+ liquid_lack: int | None = Field(None, alias="liquidLack")
393
+ mac: str | None = None
394
+ name: str | None = None
395
+ refreshing: int | None = None
396
+ relate_t4: int | None = Field(None, alias="relateT4")
397
+ relation: dict | None = None
398
+ secret: str | None = None
399
+ settings: dict | None = None
400
+ sn: str | None = None
401
+ timezone: float | None = None
402
+ update_at: datetime | None = Field(None, alias="updateAt")
403
+ user_id: str | None = Field(None, alias="userId")
404
+ voltage: int | None = None
405
+
406
+
385
407
  class Litter(BaseModel):
386
408
  """Dataclass for Litter Data.
387
409
  Supported devices = T3, T4, T6
@@ -396,6 +418,7 @@ class Litter(BaseModel):
396
418
  firmware_details: list[FirmwareDetail] = Field(alias="firmwareDetails")
397
419
  hardware: int
398
420
  id: int
421
+ k3_device: K3Device | None = Field(None, alias="k3Device")
399
422
  is_pet_out_tips: int | None = Field(None, alias="isPetOutTips")
400
423
  locale: str | None = None
401
424
  mac: str | None = None
@@ -422,10 +445,10 @@ class Litter(BaseModel):
422
445
  service_status: int | None = Field(None, alias="serviceStatus")
423
446
  total_time: int | None = Field(None, alias="totalTime")
424
447
  with_k3: int | None = Field(None, alias="withK3")
425
- device_type: str | None = Field(None, alias="deviceType")
426
448
  device_records: list[LitterRecord] | None = None
427
449
  device_stats: LitterStats | None = None
428
450
  device_pet_graph_out: list[PetOutGraph] | None = None
451
+ device_nfo: Device | None = None
429
452
 
430
453
  @classmethod
431
454
  def get_endpoint(cls, device_type: str) -> str:
@@ -0,0 +1,77 @@
1
+ """Dataclasses for feeder data."""
2
+
3
+ from typing import Any, ClassVar
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from pypetkitapi.const import DEVICE_DATA, PetkitEndpoint
8
+ from pypetkitapi.containers import Device, FirmwareDetail, Wifi
9
+
10
+
11
+ class Settings(BaseModel):
12
+ """Dataclass for settings data."""
13
+
14
+ auto_work: int | None = Field(None, alias="autoWork")
15
+ lack_notify: int | None = Field(None, alias="lackNotify")
16
+ light_mode: int | None = Field(None, alias="lightMode")
17
+ light_range: list[int] | None = Field(None, alias="lightRange")
18
+ log_notify: int | None = Field(None, alias="logNotify")
19
+ manual_lock: int | None = Field(None, alias="manualLock")
20
+ sound: int | None = None
21
+ temp_unit: int | None = Field(None, alias="tempUnit")
22
+
23
+
24
+ class State(BaseModel):
25
+ """Dataclass for state data."""
26
+
27
+ humidity: int | None = None
28
+ left_day: int | None = Field(None, alias="leftDay")
29
+ liquid: int | None = None
30
+ mode: int | None = None
31
+ ota: int | None = None
32
+ overall: int | None = None
33
+ pim: int | None = None
34
+ power: int | None = None
35
+ refresh: float | None = None
36
+ temp: int | None = None
37
+ wifi: Wifi | None = None
38
+
39
+
40
+ class Purifier(BaseModel):
41
+ """Dataclass for feeder data."""
42
+
43
+ data_type: ClassVar[str] = DEVICE_DATA
44
+
45
+ bt_mac: str | None = Field(None, alias="btMac")
46
+ created_at: str | None = Field(None, alias="createdAt")
47
+ firmware: str | None = None
48
+ firmware_details: list[FirmwareDetail] | None = Field(None, alias="firmwareDetails")
49
+ hardware: int | None = None
50
+ id: int | None = None
51
+ locale: str | None = None
52
+ mac: str | None = None
53
+ name: str | None = None
54
+ relation: dict[str, str]
55
+ secret: str | None = None
56
+ settings: Settings | None = None
57
+ share_open: int | None = Field(None, alias="shareOpen")
58
+ signup_at: str | None = Field(None, alias="signupAt")
59
+ sn: str | None = None
60
+ state: State | None = None
61
+ timezone: float | None = None
62
+ work_time: list[tuple[int, int]] | None = Field(None, alias="workTime")
63
+ device_nfo: Device | None = None
64
+
65
+ @classmethod
66
+ def get_endpoint(cls, device_type: str) -> str:
67
+ """Get the endpoint URL for the given device type."""
68
+ return PetkitEndpoint.DEVICE_DETAIL
69
+
70
+ @classmethod
71
+ def query_param(
72
+ cls,
73
+ device: Device,
74
+ device_data: Any | None = None,
75
+ ) -> dict:
76
+ """Generate query parameters including request_date."""
77
+ return {"id": int(device.device_id)}
@@ -93,7 +93,6 @@ class WaterFountainRecord(BaseModel):
93
93
  day_time: int | None = Field(None, alias="dayTime")
94
94
  stay_time: int | None = Field(None, alias="stayTime")
95
95
  work_time: int | None = Field(None, alias="workTime")
96
- device_type: str | None = Field(None, alias="deviceType")
97
96
 
98
97
  @classmethod
99
98
  def get_endpoint(cls, device_type: str) -> str | None:
@@ -163,8 +162,8 @@ class WaterFountain(BaseModel):
163
162
  update_at: str | None = Field(None, alias="updateAt")
164
163
  user_id: str | None = Field(None, alias="userId")
165
164
  water_pump_run_time: int | None = Field(None, alias="waterPumpRunTime")
166
- device_type: str | None = Field(None, alias="deviceType")
167
165
  device_records: list[WaterFountainRecord] | None = None
166
+ device_nfo: Device | None = None
168
167
 
169
168
  @classmethod
170
169
  def get_endpoint(cls, device_type: str) -> str:
@@ -187,7 +187,7 @@ build-backend = "poetry.core.masonry.api"
187
187
 
188
188
  [tool.poetry]
189
189
  name = "pypetkitapi"
190
- version = "1.7.9"
190
+ version = "1.8.1"
191
191
  description = "Python client for PetKit API"
192
192
  authors = ["Jezza34000 <info@mail.com>"]
193
193
  readme = "README.md"
@@ -199,7 +199,7 @@ python = ">=3.11"
199
199
  aiohttp = "^3.10.10"
200
200
  aiofiles = "^24.1.0"
201
201
  pycryptodome = "^3.19.1"
202
- pydantic = "^1.10.18"
202
+ pydantic = ">=1.10.18,<3.0.0"
203
203
 
204
204
  [tool.poetry.dev-dependencies]
205
205
  pre-commit = "^4.0.1"
@@ -208,7 +208,7 @@ ruff = "^0.8.1"
208
208
  types-aiofiles = "^24.1.0.20240626"
209
209
 
210
210
  [tool.bumpver]
211
- current_version = "1.7.9"
211
+ current_version = "1.8.1"
212
212
  version_pattern = "MAJOR.MINOR.PATCH"
213
213
  commit_message = "bump version {old_version} -> {new_version}"
214
214
  tag_message = "{new_version}"
@@ -224,6 +224,9 @@ push = true
224
224
  '^version = "{version}"',
225
225
  '^current_version = "{version}"',
226
226
  ]
227
+ "pypetkitapi/__init__.py" = [
228
+ '^__version__ = "{version}"',
229
+ ]
227
230
 
228
231
  [tool.tox]
229
232
  envlist = ["pre-commit"]
@@ -1 +0,0 @@
1
- """Pypetkit: A Python library for interfacing with PetKit"""
File without changes