petsafe-api 3.2.0__tar.gz → 3.3.0__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.
Files changed (28) hide show
  1. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/PKG-INFO +13 -2
  2. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/README.md +12 -1
  3. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/devices.py +43 -0
  4. petsafe_api-3.3.0/petsafe/local.py +556 -0
  5. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe_api.egg-info/PKG-INFO +13 -2
  6. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe_api.egg-info/SOURCES.txt +1 -0
  7. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/setup.py +1 -1
  8. petsafe_api-3.3.0/tests/test_local.py +502 -0
  9. petsafe_api-3.3.0/tests/test_local_server.py +75 -0
  10. petsafe_api-3.2.0/petsafe/local.py +0 -215
  11. petsafe_api-3.2.0/tests/test_local.py +0 -243
  12. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/LICENSE +0 -0
  13. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/__init__.py +0 -0
  14. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/__main__.py +0 -0
  15. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/client.py +0 -0
  16. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/const.py +0 -0
  17. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/general.py +0 -0
  18. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/pets.py +0 -0
  19. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/provisioning.py +0 -0
  20. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe/setup.py +0 -0
  21. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe_api.egg-info/dependency_links.txt +0 -0
  22. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe_api.egg-info/requires.txt +0 -0
  23. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/petsafe_api.egg-info/top_level.txt +0 -0
  24. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/setup.cfg +0 -0
  25. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/tests/test_provisioning.py +0 -0
  26. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/tests/test_setup.py +0 -0
  27. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/tests/test_smartdoor_modes.py +0 -0
  28. {petsafe_api-3.2.0 → petsafe_api-3.3.0}/tests/test_smartfeed_last_feeding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: petsafe-api
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Provides ability to connect and control a PetSafe Smart Feed, Scoopfree, and Smart Door devices using the PetSafe API.
5
5
  Home-page: https://github.com/ThomasHFWright/petsafe-api
6
6
  Author: Jordan Stremming & Dominick Meglio & Thomas Wright
@@ -57,7 +57,7 @@ Thank you to both projects for building the API integrations for existing feeder
57
57
  If installing from source code,
58
58
  `python setup.py install`
59
59
 
60
- ## Local SmartDoor control and provisioning (3.2.0)
60
+ ## Local SmartDoor control, management and provisioning (3.3.0)
61
61
 
62
62
  `LocalPetSafeClient` connects to a compatible local server using its HTTPS origin
63
63
  and API token. It does not use a PetSafe vendor login, and returns the same
@@ -80,6 +80,13 @@ async def main() -> None:
80
80
  async for snapshot in client.iter_events():
81
81
  print(snapshot["revision"], snapshot["door"])
82
82
 
83
+ pet = await client.create_pet({
84
+ "profile": {"name": "Example pet", "breed": []}, "timezone": "UTC"
85
+ })
86
+ async for scan in client.scan_rfid(door.api_name, "PETSAFE_KEY"):
87
+ if scan["state"] == "captured" and scan["restored"]:
88
+ await client.assign_rfid_scan(door.api_name, scan["id"], pet["petId"])
89
+
83
90
 
84
91
  asyncio.run(main())
85
92
  ```
@@ -110,6 +117,10 @@ also requires supported BLE hardware or a nearby proxy and an HTTPS hostname and
110
117
  certificate trusted by the SmartDoor; hardware testing used an Amazon-trusted
111
118
  certificate. A raw IP address is not a supported or promised substitute.
112
119
 
120
+ Local maintenance helpers include allowlisted native command submission and firmware
121
+ inspection, staging, installation, and cancellation. `install_firmware()` is an explicit
122
+ physical-device mutation; returned job state is not a promise of firmware outcome.
123
+
113
124
  ## Cloud login tokens
114
125
  You **must** use tokens to interact with the PetSafe Smart-Feed API.
115
126
  There are two methods to retrieve tokens:
@@ -24,7 +24,7 @@ Thank you to both projects for building the API integrations for existing feeder
24
24
  If installing from source code,
25
25
  `python setup.py install`
26
26
 
27
- ## Local SmartDoor control and provisioning (3.2.0)
27
+ ## Local SmartDoor control, management and provisioning (3.3.0)
28
28
 
29
29
  `LocalPetSafeClient` connects to a compatible local server using its HTTPS origin
30
30
  and API token. It does not use a PetSafe vendor login, and returns the same
@@ -47,6 +47,13 @@ async def main() -> None:
47
47
  async for snapshot in client.iter_events():
48
48
  print(snapshot["revision"], snapshot["door"])
49
49
 
50
+ pet = await client.create_pet({
51
+ "profile": {"name": "Example pet", "breed": []}, "timezone": "UTC"
52
+ })
53
+ async for scan in client.scan_rfid(door.api_name, "PETSAFE_KEY"):
54
+ if scan["state"] == "captured" and scan["restored"]:
55
+ await client.assign_rfid_scan(door.api_name, scan["id"], pet["petId"])
56
+
50
57
 
51
58
  asyncio.run(main())
52
59
  ```
@@ -77,6 +84,10 @@ also requires supported BLE hardware or a nearby proxy and an HTTPS hostname and
77
84
  certificate trusted by the SmartDoor; hardware testing used an Amazon-trusted
78
85
  certificate. A raw IP address is not a supported or promised substitute.
79
86
 
87
+ Local maintenance helpers include allowlisted native command submission and firmware
88
+ inspection, staging, installation, and cancellation. `install_firmware()` is an explicit
89
+ physical-device mutation; returned job state is not a promise of firmware outcome.
90
+
80
91
  ## Cloud login tokens
81
92
  You **must** use tokens to interact with the PetSafe Smart-Feed API.
82
93
  There are two methods to retrieve tokens:
@@ -705,6 +705,21 @@ class DeviceSmartDoor:
705
705
  self.data["schedules"] = schedules
706
706
  return schedules
707
707
 
708
+ async def get_schedule(self, schedule_id: str) -> dict:
709
+ """Return one SmartDoor schedule by identifier."""
710
+
711
+ if not schedule_id:
712
+ raise ValueError("schedule_id must be provided")
713
+ response = await self.client.api_get(
714
+ f"smartdoor/product/schedules/{self.api_name}/{schedule_id}"
715
+ )
716
+ response.raise_for_status()
717
+ payload = json.loads(response.content.decode("UTF-8"))
718
+ data = payload.get("data", payload)
719
+ if not isinstance(data, dict):
720
+ raise ValueError("Unexpected response payload for SmartDoor schedule")
721
+ return data
722
+
708
723
  async def get_override_schedules(self) -> list[dict]:
709
724
  """Return override schedules configured for the SmartDoor."""
710
725
 
@@ -736,6 +751,15 @@ class DeviceSmartDoor:
736
751
  await self.update_data()
737
752
  return data
738
753
 
754
+ async def clear_override_schedule(self, *, update_data: bool = True) -> None:
755
+ """Clear the SmartDoor's temporary access override."""
756
+
757
+ await self.client.api_delete(
758
+ f"smartdoor/product/override/schedules/{self.api_name}"
759
+ )
760
+ if update_data:
761
+ await self.update_data()
762
+
739
763
  async def save_schedule(
740
764
  self, schedule: dict, *, update_data: bool = True
741
765
  ) -> dict:
@@ -755,6 +779,25 @@ class DeviceSmartDoor:
755
779
  await self.update_data()
756
780
  return data
757
781
 
782
+ async def update_schedule(
783
+ self, schedule: dict, *, update_data: bool = True
784
+ ) -> dict:
785
+ """Update an existing SmartDoor schedule."""
786
+
787
+ if not isinstance(schedule, dict) or not schedule.get("scheduleId"):
788
+ raise ValueError("schedule with scheduleId must be provided")
789
+ payload = dict(schedule)
790
+ payload.setdefault("thingName", self.api_name)
791
+ response = await self.client.api_put(
792
+ "smartdoor/product/schedules", data=payload
793
+ )
794
+ response.raise_for_status()
795
+ response_payload = json.loads(response.content.decode("UTF-8"))
796
+ data = response_payload.get("data", response_payload)
797
+ if update_data:
798
+ await self.update_data()
799
+ return data
800
+
758
801
  async def delete_schedule(
759
802
  self, schedule_id: str, *, update_data: bool = True
760
803
  ) -> None: