pypetkitapi 1.8.1__py3-none-any.whl → 1.9.0__py3-none-any.whl

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.
pypetkitapi/__init__.py CHANGED
@@ -40,7 +40,7 @@ from .medias import MediaHandler, MediasFiles
40
40
  from .purifier_container import Purifier
41
41
  from .water_fountain_container import WaterFountain
42
42
 
43
- __version__ = "1.8.1"
43
+ __version__ = "1.9.0"
44
44
 
45
45
  __all__ = [
46
46
  "CTW3",
pypetkitapi/client.py CHANGED
@@ -1,17 +1,22 @@
1
1
  """Pypetkit Client: A Python library for interfacing with PetKit"""
2
2
 
3
3
  import asyncio
4
+ import base64
4
5
  from datetime import datetime, timedelta
5
6
  from enum import StrEnum
6
7
  import hashlib
7
8
  from http import HTTPMethod
8
9
  import logging
10
+ import urllib.parse
9
11
 
10
12
  import aiohttp
11
13
  from aiohttp import ContentTypeError
12
14
 
13
- from pypetkitapi.command import ACTIONS_MAP
15
+ from pypetkitapi.command import ACTIONS_MAP, FOUNTAIN_COMMAND, FountainAction
14
16
  from pypetkitapi.const import (
17
+ BLE_CONNECT_ATTEMPT,
18
+ BLE_END_TRAME,
19
+ BLE_START_TRAME,
15
20
  DEVICE_DATA,
16
21
  DEVICE_RECORDS,
17
22
  DEVICE_STATS,
@@ -29,7 +34,14 @@ from pypetkitapi.const import (
29
34
  PetkitDomain,
30
35
  PetkitEndpoint,
31
36
  )
32
- from pypetkitapi.containers import AccountData, Device, Pet, RegionInfo, SessionInfo
37
+ from pypetkitapi.containers import (
38
+ AccountData,
39
+ BleRelay,
40
+ Device,
41
+ Pet,
42
+ RegionInfo,
43
+ SessionInfo,
44
+ )
33
45
  from pypetkitapi.exceptions import (
34
46
  PetkitAuthenticationError,
35
47
  PetkitAuthenticationUnregisteredEmailError,
@@ -201,7 +213,16 @@ class PetKitClient:
201
213
  if account.pet_list:
202
214
  for pet in account.pet_list:
203
215
  self.petkit_entities[pet.pet_id] = pet
204
- pet.device_nfo = Device(deviceType=PET, deviceId=pet.pet_id)
216
+ pet.device_nfo = Device(
217
+ deviceType=PET,
218
+ deviceId=pet.pet_id,
219
+ createdAt=pet.created_at,
220
+ deviceName=pet.pet_name,
221
+ groupId=0,
222
+ type=0,
223
+ typeCode=0,
224
+ uniqueId=pet.sn,
225
+ )
205
226
 
206
227
  async def get_devices_data(self) -> None:
207
228
  """Get the devices data from the PetKit servers."""
@@ -223,7 +244,7 @@ class PetKitClient:
223
244
  _LOGGER.debug("Found %s devices", len(account.device_list))
224
245
 
225
246
  for device in device_list:
226
- device_type = device.device_type.lower()
247
+ device_type = device.device_type
227
248
 
228
249
  if device_type in DEVICES_FEEDER:
229
250
  main_tasks.append(self._fetch_device_data(device, Feeder))
@@ -259,7 +280,7 @@ class PetKitClient:
259
280
  stats_tasks = [
260
281
  self.populate_pet_stats(self.petkit_entities[device.device_id])
261
282
  for device in device_list
262
- if device.device_type.lower() in DEVICES_LITTER_BOX
283
+ if device.device_type in DEVICES_LITTER_BOX
263
284
  ]
264
285
 
265
286
  # Execute stats tasks
@@ -285,7 +306,7 @@ class PetKitClient:
285
306
  ],
286
307
  ) -> None:
287
308
  """Fetch the device data from the PetKit servers."""
288
- device_type = device.device_type.lower()
309
+ device_type = device.device_type
289
310
 
290
311
  _LOGGER.debug("Reading device type : %s (id=%s)", device_type, device.device_id)
291
312
 
@@ -404,6 +425,171 @@ class PetKitClient:
404
425
  pet.last_duration_usage = self.get_safe_value(graph.toilet_time)
405
426
  pet.last_device_used = "Purobot Ultra"
406
427
 
428
+ async def _get_fountain_instance(self, fountain_id: int) -> WaterFountain:
429
+ # Retrieve the water fountain object
430
+ water_fountain = self.petkit_entities.get(fountain_id)
431
+ if not water_fountain:
432
+ _LOGGER.error("Water fountain with ID %s not found.", fountain_id)
433
+ raise ValueError(f"Water fountain with ID {fountain_id} not found.")
434
+ return water_fountain
435
+
436
+ async def check_relay_availability(self, fountain_id: int) -> bool:
437
+ """Check if BLE relay is available for the account."""
438
+ fountain = None
439
+ for account in self.account_data:
440
+ fountain = next(
441
+ (
442
+ device
443
+ for device in account.device_list
444
+ if device.device_id == fountain_id
445
+ ),
446
+ None,
447
+ )
448
+ if fountain:
449
+ break
450
+
451
+ if not fountain:
452
+ raise ValueError(
453
+ f"Fountain with device_id {fountain_id} not found for the current account"
454
+ )
455
+
456
+ group_id = fountain.group_id
457
+
458
+ response = await self.req.request(
459
+ method=HTTPMethod.POST,
460
+ url=f"{PetkitEndpoint.BLE_AS_RELAY}",
461
+ params={"groupId": group_id},
462
+ headers=await self.get_session_id(),
463
+ )
464
+ ble_relays = [BleRelay(**relay) for relay in response]
465
+
466
+ if len(ble_relays) == 0:
467
+ _LOGGER.warning("No BLE relay devices found.")
468
+ return False
469
+ return True
470
+
471
+ async def open_ble_connection(self, fountain_id: int) -> bool:
472
+ """Open a BLE connection to a PetKit device."""
473
+ _LOGGER.info("Opening BLE connection to fountain %s", fountain_id)
474
+ water_fountain = await self._get_fountain_instance(fountain_id)
475
+
476
+ if await self.check_relay_availability(fountain_id) is False:
477
+ _LOGGER.error("BLE relay not available.")
478
+ return False
479
+
480
+ if water_fountain.is_connected is True:
481
+ _LOGGER.error("BLE connection already established.")
482
+ return True
483
+
484
+ response = await self.req.request(
485
+ method=HTTPMethod.POST,
486
+ url=PetkitEndpoint.BLE_CONNECT,
487
+ data={
488
+ "bleId": fountain_id,
489
+ "type": 24,
490
+ "mac": water_fountain.mac,
491
+ },
492
+ headers=await self.get_session_id(),
493
+ )
494
+ if response != {"state": 1}:
495
+ _LOGGER.error("Failed to establish BLE connection.")
496
+ water_fountain.is_connected = False
497
+ return False
498
+
499
+ for attempt in range(BLE_CONNECT_ATTEMPT):
500
+ _LOGGER.warning("BLE connection attempt n%s", attempt)
501
+ response = await self.req.request(
502
+ method=HTTPMethod.POST,
503
+ url=PetkitEndpoint.BLE_POLL,
504
+ data={
505
+ "bleId": fountain_id,
506
+ "type": 24,
507
+ "mac": water_fountain.mac,
508
+ },
509
+ headers=await self.get_session_id(),
510
+ )
511
+ if response == 1:
512
+ _LOGGER.info("BLE connection established successfully.")
513
+ water_fountain.is_connected = True
514
+ water_fountain.last_ble_poll = datetime.now().strftime(
515
+ "%Y-%m-%dT%H:%M:%S.%f"
516
+ )
517
+ return True
518
+ await asyncio.sleep(4)
519
+ _LOGGER.error("Failed to establish BLE connection after multiple attempts.")
520
+ water_fountain.is_connected = False
521
+ return False
522
+
523
+ async def close_ble_connection(self, fountain_id: int) -> None:
524
+ """Close the BLE connection to a PetKit device."""
525
+ _LOGGER.info("Closing BLE connection to fountain %s", fountain_id)
526
+ water_fountain = await self._get_fountain_instance(fountain_id)
527
+
528
+ await self.req.request(
529
+ method=HTTPMethod.POST,
530
+ url=PetkitEndpoint.BLE_CANCEL,
531
+ data={
532
+ "bleId": fountain_id,
533
+ "type": 24,
534
+ "mac": water_fountain.mac,
535
+ },
536
+ headers=await self.get_session_id(),
537
+ )
538
+ _LOGGER.info("BLE connection closed successfully.")
539
+
540
+ async def get_ble_cmd_data(
541
+ self, fountain_command: list, counter: int
542
+ ) -> tuple[int, str]:
543
+ """Prepare BLE data by adding start and end trame to the command and extracting the first number."""
544
+ cmd_code = fountain_command[0]
545
+ modified_command = fountain_command[:2] + [counter] + fountain_command[2:]
546
+ ble_data = [*BLE_START_TRAME, *modified_command, *BLE_END_TRAME]
547
+ encoded_data = await self._encode_ble_data(ble_data)
548
+ return cmd_code, encoded_data
549
+
550
+ @staticmethod
551
+ async def _encode_ble_data(byte_list: list) -> str:
552
+ """Encode a list of bytes to a URL encoded base64 string."""
553
+ byte_array = bytearray(byte_list)
554
+ b64_encoded = base64.b64encode(byte_array)
555
+ return urllib.parse.quote(b64_encoded)
556
+
557
+ async def send_ble_command(self, fountain_id: int, command: FountainAction) -> bool:
558
+ """BLE command to a PetKit device."""
559
+ _LOGGER.info("Sending BLE command to fountain %s", fountain_id)
560
+ water_fountain = await self._get_fountain_instance(fountain_id)
561
+
562
+ if water_fountain.is_connected is False:
563
+ _LOGGER.error("BLE connection not established.")
564
+ return False
565
+
566
+ command = FOUNTAIN_COMMAND.get[command, None]
567
+ if command is None:
568
+ _LOGGER.error("Command not found.")
569
+ return False
570
+
571
+ cmd_code, cmd_data = await self.get_ble_cmd_data(
572
+ command, water_fountain.ble_counter
573
+ )
574
+
575
+ response = await self.req.request(
576
+ method=HTTPMethod.POST,
577
+ url=PetkitEndpoint.BLE_CONTROL_DEVICE,
578
+ data={
579
+ "bleId": water_fountain.id,
580
+ "cmd": cmd_code,
581
+ "data": cmd_data,
582
+ "mac": water_fountain.mac,
583
+ "type": 24,
584
+ },
585
+ headers=await self.get_session_id(),
586
+ )
587
+ if response != 1:
588
+ _LOGGER.error("Failed to send BLE command.")
589
+ return False
590
+ _LOGGER.info("BLE command sent successfully.")
591
+ return True
592
+
407
593
  async def send_api_request(
408
594
  self,
409
595
  device_id: int,
@@ -413,7 +599,7 @@ class PetKitClient:
413
599
  """Control the device using the PetKit API."""
414
600
  await self.validate_session()
415
601
 
416
- device = self.petkit_entities.get(device_id)
602
+ device = self.petkit_entities.get(device_id, None)
417
603
  if not device:
418
604
  raise PypetkitError(f"Device with ID {device_id} not found.")
419
605
 
@@ -427,7 +613,7 @@ class PetKitClient:
427
613
 
428
614
  # Check if the device type is supported
429
615
  if device.device_nfo.device_type:
430
- device_type = device.device_nfo.device_type.lower()
616
+ device_type = device.device_nfo.device_type
431
617
  else:
432
618
  raise PypetkitError(
433
619
  "Device type is not available, and is mandatory for sending commands."
@@ -452,7 +638,7 @@ class PetKitClient:
452
638
  else:
453
639
  endpoint = action_info.endpoint
454
640
  _LOGGER.debug("Endpoint field")
455
- url = f"{device.device_nfo.device_type.lower()}/{endpoint}"
641
+ url = f"{device.device_nfo.device_type}/{endpoint}"
456
642
 
457
643
  # Get the parameters
458
644
  if setting is not None:
pypetkitapi/command.py CHANGED
@@ -34,6 +34,12 @@ class DeviceCommand(StrEnum):
34
34
  UPDATE_SETTING = "update_setting"
35
35
 
36
36
 
37
+ class FountainCommand(StrEnum):
38
+ """Device Command"""
39
+
40
+ CONTROL_DEVICE = "control_device"
41
+
42
+
37
43
  class FeederCommand(StrEnum):
38
44
  """Specific Feeder Command"""
39
45
 
@@ -101,16 +107,17 @@ class DeviceAction(StrEnum):
101
107
  class FountainAction(StrEnum):
102
108
  """Fountain Action"""
103
109
 
110
+ MODE_NORMAL = "Normal"
111
+ MODE_SMART = "Smart"
112
+ MODE_STANDARD = "Standard"
113
+ MODE_INTERMITTENT = "Intermittent"
104
114
  PAUSE = "Pause"
105
- NORMAL_TO_PAUSE = "Normal To Pause"
106
- SMART_TO_PAUSE = "Smart To Pause"
107
- NORMAL = "Normal"
108
- SMART = "Smart"
115
+ CONTINUE = "Continue"
116
+ POWER_OFF = "Power Off"
117
+ POWER_ON = "Power On"
109
118
  RESET_FILTER = "Reset Filter"
110
119
  DO_NOT_DISTURB = "Do Not Disturb"
111
120
  DO_NOT_DISTURB_OFF = "Do Not Disturb Off"
112
- FIRST_BLE_CMND = "First BLE Command"
113
- SECOND_BLE_CMND = "Second BLE Command"
114
121
  LIGHT_LOW = "Light Low"
115
122
  LIGHT_MEDIUM = "Light Medium"
116
123
  LIGHT_HIGH = "Light High"
@@ -118,20 +125,12 @@ class FountainAction(StrEnum):
118
125
  LIGHT_OFF = "Light Off"
119
126
 
120
127
 
121
- FOUNTAIN_COMMAND_TO_CODE = {
122
- FountainAction.DO_NOT_DISTURB: "221",
123
- FountainAction.DO_NOT_DISTURB_OFF: "221",
124
- FountainAction.LIGHT_ON: "221",
125
- FountainAction.LIGHT_OFF: "221",
126
- FountainAction.LIGHT_LOW: "221",
127
- FountainAction.LIGHT_MEDIUM: "221",
128
- FountainAction.LIGHT_HIGH: "221",
129
- FountainAction.PAUSE: "220",
130
- FountainAction.RESET_FILTER: "222",
131
- FountainAction.NORMAL: "220",
132
- FountainAction.NORMAL_TO_PAUSE: "220",
133
- FountainAction.SMART: "220",
134
- FountainAction.SMART_TO_PAUSE: "220",
128
+ FOUNTAIN_COMMAND = {
129
+ FountainAction.PAUSE: [220, 1, 3, 0, 1, 0, 2],
130
+ FountainAction.CONTINUE: [220, 1, 3, 0, 1, 1, 2],
131
+ FountainAction.RESET_FILTER: [222, 1, 0, 0],
132
+ FountainAction.POWER_OFF: [220, 1, 3, 0, 0, 1, 1],
133
+ FountainAction.POWER_ON: [220, 1, 3, 0, 1, 1, 1],
135
134
  }
136
135
 
137
136
 
@@ -146,18 +145,18 @@ class CmdData:
146
145
 
147
146
  def get_endpoint_manual_feed(device):
148
147
  """Get the endpoint for the device"""
149
- if device.device_type == FEEDER_MINI:
148
+ if device.device_nfo.device_type == FEEDER_MINI:
150
149
  return PetkitEndpoint.MANUAL_FEED_MINI
151
- if device.device_type == FEEDER:
150
+ if device.device_nfo.device_type == FEEDER:
152
151
  return PetkitEndpoint.MANUAL_FEED_FRESH_ELEMENT
153
152
  return PetkitEndpoint.MANUAL_FEED_DUAL
154
153
 
155
154
 
156
155
  def get_endpoint_reset_desiccant(device):
157
156
  """Get the endpoint for the device"""
158
- if device.device_type == FEEDER_MINI:
157
+ if device.device_nfo.device_type == FEEDER_MINI:
159
158
  return PetkitEndpoint.MINI_DESICCANT_RESET
160
- if device.device_type == FEEDER:
159
+ if device.device_nfo.device_type == FEEDER:
161
160
  return PetkitEndpoint.FRESH_ELEMENT_DESICCANT_RESET
162
161
  return PetkitEndpoint.DESICCANT_RESET
163
162
 
@@ -222,7 +221,7 @@ ACTIONS_MAP = {
222
221
  FeederCommand.CANCEL_MANUAL_FEED: CmdData(
223
222
  endpoint=lambda device: (
224
223
  PetkitEndpoint.FRESH_ELEMENT_CANCEL_FEED
225
- if device.device_type == FEEDER
224
+ if device.device_nfo.device_type == FEEDER
226
225
  else PetkitEndpoint.CANCEL_FEED
227
226
  ),
228
227
  params=lambda device: {
@@ -230,7 +229,7 @@ ACTIONS_MAP = {
230
229
  "deviceId": device.id,
231
230
  **(
232
231
  {"id": device.manual_feed_id}
233
- if device.device_type.lower() in [D4H, D4S, D4SH]
232
+ if device.device_nfo.device_type in [D4H, D4S, D4SH]
234
233
  else {}
235
234
  ),
236
235
  },
@@ -281,15 +280,4 @@ ACTIONS_MAP = {
281
280
  },
282
281
  supported_device=ALL_DEVICES,
283
282
  ),
284
- # FountainCommand.CONTROL_DEVICE: CmdData(
285
- # endpoint=PetkitEndpoint.CONTROL_DEVICE,
286
- # params=lambda device, setting: {
287
- # "bleId": device.id,
288
- # "cmd": cmnd_code,
289
- # "data": ble_data,
290
- # "mac": device.mac,
291
- # "type": water_fountain.ble_relay,
292
- # },
293
- # supported_device=[CTW3],
294
- # ),
295
283
  }
pypetkitapi/const.py CHANGED
@@ -11,6 +11,11 @@ DEVICE_DATA = "deviceData"
11
11
  DEVICE_STATS = "deviceStats"
12
12
  PET_DATA = "petData"
13
13
 
14
+ # Bluetooth
15
+ BLE_CONNECT_ATTEMPT = 4
16
+ BLE_START_TRAME = [250, 252, 253]
17
+ BLE_END_TRAME = [251]
18
+
14
19
  # PetKit Models
15
20
  FEEDER = "feeder"
16
21
  FEEDER_MINI = "feedermini"
@@ -120,11 +125,12 @@ class PetkitEndpoint(StrEnum):
120
125
  GET_DEVICE_RECORD_RELEASE = "getDeviceRecordRelease"
121
126
  UPDATE_SETTING = "updateSettings"
122
127
 
123
- # Bluetooth relay
128
+ # Bluetooth
124
129
  BLE_AS_RELAY = "ble/ownSupportBleDevices"
125
130
  BLE_CONNECT = "ble/connect"
126
131
  BLE_POLL = "ble/poll"
127
132
  BLE_CANCEL = "ble/cancel"
133
+ BLE_CONTROL_DEVICE = "ble/controlDevice"
128
134
 
129
135
  # Fountain & Litter Box
130
136
  CONTROL_DEVICE = "controlDevice"
pypetkitapi/containers.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Dataclasses container for petkit API."""
2
2
 
3
- from pydantic import BaseModel, Field
3
+ from pydantic import BaseModel, Field, field_validator
4
4
 
5
5
 
6
6
  class RegionInfo(BaseModel):
@@ -51,14 +51,21 @@ class Device(BaseModel):
51
51
  Subclass of AccountData.
52
52
  """
53
53
 
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
54
+ created_at: int = Field(alias="createdAt")
55
+ device_id: int = Field(alias="deviceId")
56
+ device_name: str = Field(alias="deviceName")
57
+ device_type: str = Field(alias="deviceType")
58
+ group_id: int = Field(alias="groupId")
59
+ type: int
60
60
  type_code: int = Field(0, alias="typeCode")
61
- unique_id: str | None = Field(None, alias="uniqueId")
61
+ unique_id: str = Field(alias="uniqueId")
62
+
63
+ @field_validator("device_name", "device_type", "unique_id", mode="before")
64
+ def convert_to_lower(cls, value): # noqa: N805
65
+ """Convert device_name, device_type and unique_id to lowercase."""
66
+ if value is not None and isinstance(value, str):
67
+ return value.lower()
68
+ return value
62
69
 
63
70
 
64
71
  class Pet(BaseModel):
@@ -66,15 +73,15 @@ class Pet(BaseModel):
66
73
  Subclass of AccountData.
67
74
  """
68
75
 
69
- avatar: str | None = None
76
+ avatar: str
70
77
  created_at: int = Field(alias="createdAt")
71
78
  pet_id: int = Field(alias="petId")
72
- pet_name: str | None = Field(None, alias="petName")
79
+ pet_name: str = Field(alias="petName")
73
80
  id: int | None = None # Fictive field copied from id (for HA compatibility)
74
81
  sn: str | None = None # Fictive field copied from id (for HA compatibility)
75
82
  name: str | None = None # Fictive field copied from pet_name (for HA compatibility)
76
83
  firmware: str | None = None # Fictive fixed field (for HA compatibility)
77
- device_nfo: Device = Field(default_factory=Device)
84
+ device_nfo: Device | None = None # Device is now optional
78
85
 
79
86
  # Litter stats
80
87
  last_litter_usage: int = 0
@@ -291,7 +291,7 @@ class FeederRecord(BaseModel):
291
291
  if request_date is None:
292
292
  request_date = datetime.now().strftime("%Y%m%d")
293
293
 
294
- if device.device_type.lower() == D4:
294
+ if device.device_type == D4:
295
295
  return {"date": request_date, "type": 0, "deviceId": device.device_id}
296
296
  return {"days": request_date, "deviceId": device.device_id}
297
297
 
@@ -253,7 +253,7 @@ class LitterRecord(BaseModel):
253
253
  request_date: str | None = None,
254
254
  ) -> dict:
255
255
  """Generate query parameters including request_date."""
256
- device_type = device.device_type.lower()
256
+ device_type = device.device_type
257
257
  if device_type == T4:
258
258
  if request_date is None:
259
259
  request_date = datetime.now().strftime("%Y%m%d")
@@ -164,6 +164,9 @@ class WaterFountain(BaseModel):
164
164
  water_pump_run_time: int | None = Field(None, alias="waterPumpRunTime")
165
165
  device_records: list[WaterFountainRecord] | None = None
166
166
  device_nfo: Device | None = None
167
+ is_connected: bool = False
168
+ ble_counter: int = 0
169
+ last_ble_poll: str | None = None
167
170
 
168
171
  @classmethod
169
172
  def get_endpoint(cls, device_type: str) -> str:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: pypetkitapi
3
- Version: 1.8.1
3
+ Version: 1.9.0
4
4
  Summary: Python client for PetKit API
5
5
  Home-page: https://github.com/Jezza34000/pypetkit
6
6
  License: MIT
@@ -101,7 +101,10 @@ async def main():
101
101
 
102
102
  ### Example 2 : Feed the pet
103
103
  ### Device_ID, Command, Payload
104
+ # simple hopper :
104
105
  await client.send_api_request(123456789, FeederCommand.MANUAL_FEED, {"amount": 1})
106
+ # dual hopper :
107
+ await client.send_api_request(123456789, FeederCommand.MANUAL_FEED_DUAL, {"amount1": 2})
105
108
 
106
109
  ### Example 3 : Start the cleaning process
107
110
  ### Device_ID, Command, Payload
@@ -112,6 +115,10 @@ if __name__ == "__main__":
112
115
  asyncio.run(main())
113
116
  ```
114
117
 
118
+ ## More example usage
119
+
120
+ Check at the usage in the Home Assistant integration : [here](https://github.com/Jezza34000/homeassistant_petkit)
121
+
115
122
  ## Contributing
116
123
 
117
124
  Contributions are welcome! Please open an issue or submit a pull request.
@@ -0,0 +1,16 @@
1
+ pypetkitapi/__init__.py,sha256=FSbNIvn4PuEEWbVnJ2rQEQkyOZgCYiXVAgrWqokbnVg,1562
2
+ pypetkitapi/client.py,sha256=J7d8lgrCyR1fJPEB9zUTu_z7HVDp_ALgD_qihu9KAYo,27630
3
+ pypetkitapi/command.py,sha256=G7AEtUcaK-lcRliNf4oUxPkvDO_GNBkJ-ZUcOo7DGHM,7697
4
+ pypetkitapi/const.py,sha256=pkTJ0l-8mQix9aoJNC2UYfyUdG7ie826xnv7EkOZtPw,4208
5
+ pypetkitapi/containers.py,sha256=oJR22ZruMr-0IRgiucdnj_nutOH59MKvmaFTwLJNiJI,4635
6
+ pypetkitapi/exceptions.py,sha256=fuTLT6Iw2_kA7eOyNJPf59vQkgfByhAnTThY4lC0Rt0,1283
7
+ pypetkitapi/feeder_container.py,sha256=q9lsUvBMxVt-vs8gE3Gg7jVyiIz_eh-3Vq-vMhDIzGA,14472
8
+ pypetkitapi/litter_container.py,sha256=kVbPddHV7wjaeBbRIsigHn_-G42qLZyFUXJg5U5PcQ0,19061
9
+ pypetkitapi/medias.py,sha256=IuWkC7usw0Hbx173X8TGv24jOp4nqv6bIUosZBpXMGg,6945
10
+ pypetkitapi/purifier_container.py,sha256=ssyIxhNben5XJ4KlQTXTrtULg2ji6DqHqjzOq08d1-I,2491
11
+ pypetkitapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ pypetkitapi/water_fountain_container.py,sha256=5J0b-fDZYcFLNX2El7fifv8H6JMhBCt-ttxSow1ozRQ,6787
13
+ pypetkitapi-1.9.0.dist-info/LICENSE,sha256=4FWnKolNLc1e3w6cVlT61YxfPh0DQNeQLN1CepKKSBg,1067
14
+ pypetkitapi-1.9.0.dist-info/METADATA,sha256=ga5bwlaFeRelb8zt8nUMGpSt6X8Uh3mvnbbJwjYteEU,5167
15
+ pypetkitapi-1.9.0.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
16
+ pypetkitapi-1.9.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.0.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,16 +0,0 @@
1
- pypetkitapi/__init__.py,sha256=qV8NZZeCMyY22gFOav0uic4QiORt9TsenDZkfC0aBqY,1562
2
- pypetkitapi/client.py,sha256=_iR45tRH59FEQ_Ra1RAMAyTDvpzSLLCNtiKu1yJSW24,20886
3
- pypetkitapi/command.py,sha256=ONP2BBdnb1nGgAK0xz50GiLO3KPzuQJD1BjqAhblkAs,8165
4
- pypetkitapi/const.py,sha256=q0HCUoryVyNzuH8erMrTqAR8yW4vEiPE79TTbhKNIEM,4076
5
- pypetkitapi/containers.py,sha256=9fJVXG-F0lTGcfYyz0WF9xTph5FCQJbqS8LHE5MpZhE,4411
6
- pypetkitapi/exceptions.py,sha256=fuTLT6Iw2_kA7eOyNJPf59vQkgfByhAnTThY4lC0Rt0,1283
7
- pypetkitapi/feeder_container.py,sha256=l8SbimdfWhmKcHYINw9_3-jXo9Rxy1K_dV3eSnn7wyk,14480
8
- pypetkitapi/litter_container.py,sha256=UkF3eAV4ehPcCM0eBRZR1ZNc7foHJTnniLW7HZK--oU,19069
9
- pypetkitapi/medias.py,sha256=IuWkC7usw0Hbx173X8TGv24jOp4nqv6bIUosZBpXMGg,6945
10
- pypetkitapi/purifier_container.py,sha256=ssyIxhNben5XJ4KlQTXTrtULg2ji6DqHqjzOq08d1-I,2491
11
- pypetkitapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- pypetkitapi/water_fountain_container.py,sha256=_5K2aTpcqHG-VBu7J2vXSZuAjA_SK6DkgV14u5RkFZA,6694
13
- pypetkitapi-1.8.1.dist-info/LICENSE,sha256=4FWnKolNLc1e3w6cVlT61YxfPh0DQNeQLN1CepKKSBg,1067
14
- pypetkitapi-1.8.1.dist-info/METADATA,sha256=b1aaD3dPzfW1ftitW2ApBwETN2IAJ8aWRzJnWrfy4PU,4882
15
- pypetkitapi-1.8.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
16
- pypetkitapi-1.8.1.dist-info/RECORD,,