wyzeapy 0.5.30__tar.gz → 0.5.31rc1__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 (76) hide show
  1. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.github/CODEOWNERS +3 -3
  2. wyzeapy-0.5.31rc1/.github/workflows/auto-assign.yml +22 -0
  3. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.github/workflows/codeql.yml +4 -4
  4. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.github/workflows/lint.yml +3 -3
  5. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.github/workflows/pre-release.yml +3 -3
  6. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.github/workflows/publish.yml +3 -3
  7. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.github/workflows/test.yml +4 -4
  8. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/PKG-INFO +3 -3
  9. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/README.md +3 -3
  10. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/pyproject.toml +4 -4
  11. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/__init__.py +1 -1
  12. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/payload_factory.py +23 -19
  13. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/base_service.py +73 -35
  14. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/camera_service.py +11 -3
  15. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/irrigation_service.py +104 -47
  16. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/tests/test_irrigation_service.py +373 -177
  17. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_camera_service.py +57 -0
  18. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.coveragerc +0 -0
  19. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.github/dependabot.yml +0 -0
  20. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.gitignore +0 -0
  21. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/.reuse/dep5 +0 -0
  22. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/CHANGELOG.md +0 -0
  23. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/LICENSES/GPL-3.0-only.txt +0 -0
  24. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/bin/act +0 -0
  25. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/index.html +0 -0
  26. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/search.js +0 -0
  27. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/const.html +0 -0
  28. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/crypto.html +0 -0
  29. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/exceptions.html +0 -0
  30. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/payload_factory.html +0 -0
  31. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/base_service.html +0 -0
  32. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/bulb_service.html +0 -0
  33. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/camera_service.html +0 -0
  34. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/hms_service.html +0 -0
  35. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/lock_service.html +0 -0
  36. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/sensor_service.html +0 -0
  37. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/switch_service.html +0 -0
  38. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/thermostat_service.html +0 -0
  39. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/update_manager.html +0 -0
  40. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services/wall_switch_service.html +0 -0
  41. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/services.html +0 -0
  42. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/types.html +0 -0
  43. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/utils.html +0 -0
  44. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy/wyze_auth_lib.html +0 -0
  45. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/docs/wyzeapy.html +0 -0
  46. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/scripts/create_pre_release.sh +0 -0
  47. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/const.py +0 -0
  48. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/crypto.py +0 -0
  49. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/exceptions.py +0 -0
  50. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/__init__.py +0 -0
  51. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/bulb_service.py +0 -0
  52. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/hms_service.py +0 -0
  53. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/lock_service.py +0 -0
  54. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/sensor_service.py +0 -0
  55. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/switch_service.py +0 -0
  56. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/thermostat_service.py +0 -0
  57. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/update_manager.py +0 -0
  58. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/services/wall_switch_service.py +0 -0
  59. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/types.py +0 -0
  60. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/utils.py +0 -0
  61. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/src/wyzeapy/wyze_auth_lib.py +0 -0
  62. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/__init__.py +0 -0
  63. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_bulb_service.py +0 -0
  64. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_hms_service.py +0 -0
  65. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_lock_service.py +0 -0
  66. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_payload_factory.py +0 -0
  67. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_sensor_service.py +0 -0
  68. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_switch_service.py +0 -0
  69. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_thermostat_service.py +0 -0
  70. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_types.py +0 -0
  71. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_update_manager.py +0 -0
  72. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_utils.py +0 -0
  73. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_wall_switch_service.py +0 -0
  74. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_wyze_auth_lib.py +0 -0
  75. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/tests/test_wyzeapy.py +0 -0
  76. {wyzeapy-0.5.30 → wyzeapy-0.5.31rc1}/uv.lock +0 -0
@@ -1,8 +1,8 @@
1
- # Lines starting with ‘#’ are comments.
1
+ # Lines starting with '#' are comments.
2
2
  # Each line is a file pattern followed by one or more owners.
3
3
 
4
4
  # These owners will be the default owners for everything in the repo.
5
- * @JoshuaMulliken
5
+ * @SecKatie
6
6
 
7
7
  # Order is important. The last matching pattern has the most precedence.
8
8
  # So if a pull request only touches javascript files, only these owners
@@ -10,4 +10,4 @@
10
10
  # *.js @octocat @github/js
11
11
 
12
12
  # You can also use email addresses if you prefer.
13
- # docs/* docs@example.com
13
+ # docs/* docs@example.com
@@ -0,0 +1,22 @@
1
+ name: Auto Assign Issues
2
+
3
+ on:
4
+ issues:
5
+ types: [opened]
6
+
7
+ jobs:
8
+ assign:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ issues: write
12
+ steps:
13
+ - name: Assign issue to SecKatie
14
+ uses: actions/github-script@v7
15
+ with:
16
+ script: |
17
+ await github.rest.issues.addAssignees({
18
+ owner: context.repo.owner,
19
+ repo: context.repo.repo,
20
+ issue_number: context.issue.number,
21
+ assignees: ['SecKatie']
22
+ });
@@ -32,17 +32,17 @@ jobs:
32
32
 
33
33
  steps:
34
34
  - name: Checkout repository
35
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v3
35
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v3
36
36
 
37
37
  # Initializes the CodeQL tools for scanning.
38
38
  - name: Initialize CodeQL
39
- uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
39
+ uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3
40
40
 
41
41
 
42
42
  # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
43
43
  # If this step fails, then you should remove it and run the build manually (see below)
44
44
  - name: Autobuild
45
- uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
45
+ uses: github/codeql-action/autobuild@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3
46
46
 
47
47
  # Command-line programs to run using the OS shell.
48
48
  # See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -55,5 +55,5 @@ jobs:
55
55
  # ./location_of_script_within_repo/buildscript.sh
56
56
 
57
57
  - name: Perform CodeQL Analysis
58
- uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
58
+ uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3
59
59
 
@@ -11,15 +11,15 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
 
13
13
  steps:
14
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
14
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
15
15
 
16
16
  - name: Set up Python
17
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
17
+ uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5
18
18
  with:
19
19
  python-version: '3.11'
20
20
 
21
21
  - name: Install uv
22
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
22
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
23
23
 
24
24
  - name: Install dependencies
25
25
  run: |
@@ -10,14 +10,14 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
 
12
12
  steps:
13
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
13
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
14
14
 
15
- - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
15
+ - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5
16
16
  with:
17
17
  python-version: "3.12"
18
18
 
19
19
  - name: Install uv
20
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v4
20
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v4
21
21
  with:
22
22
  enable-cache: true
23
23
 
@@ -12,14 +12,14 @@ jobs:
12
12
  id-token: write # Required for trusted publishing
13
13
 
14
14
  steps:
15
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
15
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
16
16
 
17
- - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
17
+ - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5
18
18
  with:
19
19
  python-version: "3.12"
20
20
 
21
21
  - name: Install uv
22
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v4
22
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v4
23
23
  with:
24
24
  enable-cache: true
25
25
 
@@ -14,15 +14,15 @@ jobs:
14
14
  python-version: ['3.11', '3.12']
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
17
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
18
18
 
19
19
  - name: Set up Python ${{ matrix.python-version }}
20
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
20
+ uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v5
21
21
  with:
22
22
  python-version: ${{ matrix.python-version }}
23
23
 
24
24
  - name: Install uv
25
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
25
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
26
26
 
27
27
  - name: Install dependencies
28
28
  run: |
@@ -39,7 +39,7 @@ jobs:
39
39
  coverage xml
40
40
 
41
41
  - name: Upload coverage reports to Codecov
42
- uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v4
42
+ uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v4
43
43
  if: matrix.python-version == '3.11'
44
44
  with:
45
45
  file: ./coverage.xml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wyzeapy
3
- Version: 0.5.30
3
+ Version: 0.5.31rc1
4
4
  Summary: A library for interacting with Wyze devices
5
5
  Author-email: Katie Mulliken <katie@mulliken.net>
6
6
  License: GPL-3.0-only
@@ -9,5 +9,5 @@ Requires-Dist: aiodns<4.0.0,>=3.2.0
9
9
  Requires-Dist: aiohttp<4.0.0,>=3.11.12
10
10
  Requires-Dist: pycryptodome<4.0.0,>=3.21.0
11
11
  Provides-Extra: dev
12
- Requires-Dist: pdoc<16.0.0,>=15.0.3; extra == 'dev'
13
- Requires-Dist: pytest<9.0.0,>=7.0.0; extra == 'dev'
12
+ Requires-Dist: pdoc<17.0.0,>=15.0.3; extra == 'dev'
13
+ Requires-Dist: pytest<10.0.0,>=7.0.0; extra == 'dev'
@@ -23,13 +23,13 @@ from wyzeapy import Wyzeapy
23
23
 
24
24
  async def async_main():
25
25
  client = await Wyzeapy.create()
26
- await client.login("EMAIL", "PASSWORD")
26
+ await client.login(email="EMAIL", password="PASSWORD", key_id="KEY_ID", api_key="API_KEY")
27
27
 
28
28
 
29
29
  if __name__ == "__main__":
30
- loop = asyncio.get_event_loop()
31
- loop.run_until_complete(async_main())
30
+ asyncio.run(async_main())
32
31
  ```
32
+ Note: Visit the [Wyze developer console](https://developer-api-console.wyze.com/#/apikey/view) to generate an Key Id and Api Key.
33
33
 
34
34
  ## Thanks to:
35
35
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wyzeapy"
3
- version = "0.5.30"
3
+ version = "0.5.31pre1"
4
4
  description = "A library for interacting with Wyze devices"
5
5
  authors = [
6
6
  { name = "Katie Mulliken", email = "katie@mulliken.net" },
@@ -15,8 +15,8 @@ dependencies = [
15
15
 
16
16
  [project.optional-dependencies]
17
17
  dev = [
18
- "pdoc>=15.0.3,<16.0.0",
19
- "pytest>=7.0.0,<9.0.0",
18
+ "pdoc>=15.0.3,<17.0.0",
19
+ "pytest>=7.0.0,<10.0.0",
20
20
  ]
21
21
 
22
22
  [build-system]
@@ -26,6 +26,6 @@ build-backend = "hatchling.build"
26
26
  [dependency-groups]
27
27
  dev = [
28
28
  "ruff>=0.12.3",
29
- "pytest>=7.0.0,<9.0.0",
29
+ "pytest>=7.0.0,<10.0.0",
30
30
  "coverage>=7.9.2",
31
31
  ]
@@ -443,7 +443,7 @@ class Wyzeapy:
443
443
  if self._sensor_service is None:
444
444
  self._sensor_service = SensorService(self._auth_lib)
445
445
  return self._sensor_service
446
-
446
+
447
447
  @property
448
448
  async def irrigation_service(self) -> IrrigationService:
449
449
  """Returns an instance of the irrigation service"""
@@ -51,37 +51,41 @@ def olive_create_get_payload(device_mac: str, keys: str) -> Dict[str, Any]:
51
51
 
52
52
  return {"keys": keys, "did": device_mac, "nonce": nonce}
53
53
 
54
+
54
55
  def olive_create_get_payload_irrigation(device_mac: str) -> Dict[str, Any]:
55
56
  nonce = int(time.time() * 1000)
56
57
 
57
- return {
58
- 'device_id': device_mac,
59
- 'nonce': str(nonce)
60
- }
58
+ return {"device_id": device_mac, "nonce": str(nonce)}
61
59
 
62
- def olive_create_post_payload_irrigation_stop(device_mac: str, action: str) -> Dict[str, Any]:
60
+
61
+ def olive_create_post_payload_irrigation_stop(
62
+ device_mac: str, action: str
63
+ ) -> Dict[str, Any]:
63
64
  nonce = int(time.time() * 1000)
64
65
 
65
- return {
66
- 'device_id': device_mac,
67
- 'nonce': str(nonce),
68
- "action": action
69
- }
66
+ return {"device_id": device_mac, "nonce": str(nonce), "action": action}
70
67
 
71
- def olive_create_post_payload_irrigation_quickrun(device_mac: str, zone_number: int, duration: int) -> Dict[str, Any]:
68
+
69
+ def olive_create_post_payload_irrigation_quickrun(
70
+ device_mac: str, zone_number: int, duration: int
71
+ ) -> Dict[str, Any]:
72
72
  nonce = int(time.time() * 1000)
73
73
 
74
74
  return {
75
- 'device_id': device_mac,
76
- 'nonce': str(nonce),
77
- "zone_runs": [
78
- {
79
- "zone_number": zone_number,
80
- "duration": duration
81
- }
82
- ]
75
+ "device_id": device_mac,
76
+ "nonce": str(nonce),
77
+ "zone_runs": [{"zone_number": zone_number, "duration": duration}],
83
78
  }
84
79
 
80
+
81
+ def olive_create_get_payload_irrigation_schedule_runs(
82
+ device_mac: str,
83
+ ) -> Dict[str, Any]:
84
+ nonce = int(time.time() * 1000)
85
+
86
+ return {"device_id": device_mac, "nonce": str(nonce)}
87
+
88
+
85
89
  def olive_create_post_payload(
86
90
  device_mac: str, device_model: str, prop_key: str, value: Any
87
91
  ) -> Dict[str, Any]:
@@ -39,6 +39,7 @@ from ..payload_factory import (
39
39
  olive_create_get_payload_irrigation,
40
40
  olive_create_post_payload_irrigation_stop,
41
41
  olive_create_post_payload_irrigation_quickrun,
42
+ olive_create_get_payload_irrigation_schedule_runs,
42
43
  )
43
44
  from ..types import PropertyIDs, Device, DeviceMgmtToggleType
44
45
  from ..utils import (
@@ -906,13 +907,13 @@ class BaseService:
906
907
  payload = olive_create_get_payload_irrigation(device.mac)
907
908
  signature = olive_create_signature(payload, self._auth_lib.token.access_token)
908
909
  headers = {
909
- 'Accept-Encoding': 'gzip',
910
- 'User-Agent': 'myapp',
911
- 'appid': OLIVE_APP_ID,
912
- 'appinfo': APP_INFO,
913
- 'phoneid': PHONE_ID,
914
- 'access_token': self._auth_lib.token.access_token,
915
- 'signature2': signature
910
+ "Accept-Encoding": "gzip",
911
+ "User-Agent": "myapp",
912
+ "appid": OLIVE_APP_ID,
913
+ "appinfo": APP_INFO,
914
+ "phoneid": PHONE_ID,
915
+ "access_token": self._auth_lib.token.access_token,
916
+ "signature2": signature,
916
917
  }
917
918
 
918
919
  response_json = await self._auth_lib.get(url, headers=headers, params=payload)
@@ -921,50 +922,87 @@ class BaseService:
921
922
 
922
923
  return response_json
923
924
 
924
-
925
- async def _stop_running_schedule(self, url: str, device: Device, action: str) -> Dict[Any, Any]:
925
+ async def _stop_running_schedule(
926
+ self, url: str, device: Device, action: str
927
+ ) -> Dict[Any, Any]:
926
928
  await self._auth_lib.refresh_if_should()
927
929
 
928
930
  payload = olive_create_post_payload_irrigation_stop(device.mac, action)
929
- signature = olive_create_signature(json.dumps(payload, separators=(',', ':')),
930
- self._auth_lib.token.access_token)
931
+ signature = olive_create_signature(
932
+ json.dumps(payload, separators=(",", ":")),
933
+ self._auth_lib.token.access_token,
934
+ )
931
935
  headers = {
932
- 'Accept-Encoding': 'gzip',
933
- 'Content-Type': 'application/json',
934
- 'User-Agent': 'myapp',
935
- 'appid': OLIVE_APP_ID,
936
- 'appinfo': APP_INFO,
937
- 'phoneid': PHONE_ID,
938
- 'access_token': self._auth_lib.token.access_token,
939
- 'signature2': signature
936
+ "Accept-Encoding": "gzip",
937
+ "Content-Type": "application/json",
938
+ "User-Agent": "myapp",
939
+ "appid": OLIVE_APP_ID,
940
+ "appinfo": APP_INFO,
941
+ "phoneid": PHONE_ID,
942
+ "access_token": self._auth_lib.token.access_token,
943
+ "signature2": signature,
940
944
  }
941
945
 
942
- payload_str = json.dumps(payload, separators=(',', ':'))
943
- response_json = await self._auth_lib.post(url, headers=headers, data=payload_str)
946
+ payload_str = json.dumps(payload, separators=(",", ":"))
947
+ response_json = await self._auth_lib.post(
948
+ url, headers=headers, data=payload_str
949
+ )
944
950
 
945
951
  check_for_errors_iot(self, response_json)
946
952
 
947
953
  return response_json
948
954
 
949
- async def _start_zone(self, url: str, device: Device, zone_number: int, duration: int) -> Dict[Any, Any]:
955
+ async def _start_zone(
956
+ self, url: str, device: Device, zone_number: int, duration: int
957
+ ) -> Dict[Any, Any]:
950
958
  await self._auth_lib.refresh_if_should()
951
959
 
952
- payload = olive_create_post_payload_irrigation_quickrun(device.mac, zone_number, duration)
953
- signature = olive_create_signature(json.dumps(payload, separators=(',', ':')),
954
- self._auth_lib.token.access_token)
960
+ payload = olive_create_post_payload_irrigation_quickrun(
961
+ device.mac, zone_number, duration
962
+ )
963
+ signature = olive_create_signature(
964
+ json.dumps(payload, separators=(",", ":")),
965
+ self._auth_lib.token.access_token,
966
+ )
955
967
  headers = {
956
- 'Accept-Encoding': 'gzip',
957
- 'Content-Type': 'application/json',
958
- 'User-Agent': 'myapp',
959
- 'appid': OLIVE_APP_ID,
960
- 'appinfo': APP_INFO,
961
- 'phoneid': PHONE_ID,
962
- 'access_token': self._auth_lib.token.access_token,
963
- 'signature2': signature
968
+ "Accept-Encoding": "gzip",
969
+ "Content-Type": "application/json",
970
+ "User-Agent": "myapp",
971
+ "appid": OLIVE_APP_ID,
972
+ "appinfo": APP_INFO,
973
+ "phoneid": PHONE_ID,
974
+ "access_token": self._auth_lib.token.access_token,
975
+ "signature2": signature,
964
976
  }
965
977
 
966
- payload_str = json.dumps(payload, separators=(',', ':'))
967
- response_json = await self._auth_lib.post(url, headers=headers, data=payload_str)
978
+ payload_str = json.dumps(payload, separators=(",", ":"))
979
+ response_json = await self._auth_lib.post(
980
+ url, headers=headers, data=payload_str
981
+ )
982
+
983
+ check_for_errors_iot(self, response_json)
984
+
985
+ return response_json
986
+
987
+ async def _get_schedule_runs(
988
+ self, url: str, device: Device, limit: int = 2
989
+ ) -> Dict[Any, Any]:
990
+ await self._auth_lib.refresh_if_should()
991
+
992
+ payload = olive_create_get_payload_irrigation_schedule_runs(device.mac)
993
+ payload["limit"] = limit
994
+ signature = olive_create_signature(payload, self._auth_lib.token.access_token)
995
+ headers = {
996
+ "Accept-Encoding": "gzip",
997
+ "User-Agent": "myapp",
998
+ "appid": OLIVE_APP_ID,
999
+ "appinfo": APP_INFO,
1000
+ "phoneid": PHONE_ID,
1001
+ "access_token": self._auth_lib.token.access_token,
1002
+ "signature2": signature,
1003
+ }
1004
+
1005
+ response_json = await self._auth_lib.get(url, headers=headers, params=payload)
968
1006
 
969
1007
  check_for_errors_iot(self, response_json)
970
1008
 
@@ -88,8 +88,10 @@ class CameraService(BaseService):
88
88
  if property is PropertyIDs.CAMERA_SIREN:
89
89
  camera.siren = value == "1"
90
90
  if property is PropertyIDs.ACCESSORY:
91
+ # Bulb Cam (HL_BC): '1' = ON, '2' = OFF
92
+ # Other cameras with accessories: same logic
91
93
  camera.floodlight = value == "1"
92
- if camera.device_params["dongle_product_model"] == "HL_CGDC":
94
+ if camera.device_params.get("dongle_product_model") == "HL_CGDC":
93
95
  camera.garage = (
94
96
  value == "1"
95
97
  ) # 1 = open, 2 = closed by automation or smart platform (Alexa, Google Home, Rules), 0 = closed by app
@@ -186,7 +188,7 @@ class CameraService(BaseService):
186
188
  else:
187
189
  await self._run_action(camera, "siren_off")
188
190
 
189
- # Also controls lamp socket and BCP spotlight
191
+ # Also controls lamp socket, BCP spotlight, and Bulb Cam light
190
192
  async def floodlight_on(self, camera: Camera):
191
193
  if camera.product_model == "AN_RSCW":
192
194
  await self._run_action_devicemgmt(
@@ -196,10 +198,13 @@ class CameraService(BaseService):
196
198
  await self._run_action_devicemgmt(
197
199
  camera, "floodlight", "1"
198
200
  ) # Some camera models use a diffrent api
201
+ elif camera.product_model == "HL_BC":
202
+ # Bulb Cam uses run_action with floodlight_on action
203
+ await self._run_action(camera, "floodlight_on")
199
204
  else:
200
205
  await self._set_property(camera, PropertyIDs.ACCESSORY.value, "1")
201
206
 
202
- # Also controls lamp socket and BCP spotlight
207
+ # Also controls lamp socket, BCP spotlight, and Bulb Cam light
203
208
  async def floodlight_off(self, camera: Camera):
204
209
  if camera.product_model == "AN_RSCW":
205
210
  await self._run_action_devicemgmt(
@@ -209,6 +214,9 @@ class CameraService(BaseService):
209
214
  await self._run_action_devicemgmt(
210
215
  camera, "floodlight", "0"
211
216
  ) # Some camera models use a diffrent api
217
+ elif camera.product_model == "HL_BC":
218
+ # Bulb Cam uses run_action with floodlight_off action
219
+ await self._run_action(camera, "floodlight_off")
212
220
  else:
213
221
  await self._set_property(camera, PropertyIDs.ACCESSORY.value, "2")
214
222