aioleviton 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gtxaspec
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: aioleviton
3
+ Version: 0.1.0
4
+ Summary: Async Python client for the Leviton My Leviton cloud API
5
+ Author: gtxaspec
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/gtxaspec/aioleviton
8
+ Project-URL: Repository, https://github.com/gtxaspec/aioleviton
9
+ Project-URL: Issues, https://github.com/gtxaspec/aioleviton/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: aiohttp>=3.9.0
21
+ Dynamic: license-file
22
+
23
+ # aioleviton
24
+
25
+ Async Python client for the Leviton My Leviton cloud API.
26
+
27
+ Supports LWHEM and DAU/LDATA Smart Load Centers with WebSocket real-time push and REST API fallback.
28
+
29
+ ## Features
30
+
31
+ - Pure `asyncio` with `aiohttp` -- no blocking calls
32
+ - Accepts an injected `aiohttp.ClientSession` for connection pooling
33
+ - WebSocket real-time push notifications with automatic subscription management
34
+ - Full REST API coverage: authentication, device discovery, breaker control, energy history
35
+ - Typed data models with PEP 561 `py.typed` marker
36
+ - Support for both hub types: LWHEM (`IotWhem`) and DAU/LDATA (`ResidentialBreakerPanel`)
37
+ - Two-factor authentication (2FA) support
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install aioleviton
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ import aiohttp
49
+ from aioleviton import LevitonClient, LevitonWebSocket
50
+
51
+ async def main():
52
+ async with aiohttp.ClientSession() as session:
53
+ # Authenticate
54
+ client = LevitonClient(session)
55
+ auth = await client.login("user@example.com", "password")
56
+
57
+ # Discover devices
58
+ permissions = await client.get_permissions()
59
+ for perm in permissions:
60
+ if perm.residential_account_id:
61
+ residences = await client.get_residences(perm.residential_account_id)
62
+ for residence in residences:
63
+ whems = await client.get_whems(residence.id)
64
+ panels = await client.get_panels(residence.id)
65
+
66
+ # Get breakers for a LWHEM hub
67
+ for whem in whems:
68
+ breakers = await client.get_whem_breakers(whem.id)
69
+ cts = await client.get_cts(whem.id)
70
+
71
+ # Connect WebSocket for real-time updates
72
+ ws = LevitonWebSocket(
73
+ session=session,
74
+ token=auth.token,
75
+ user_id=auth.user_id,
76
+ user=auth.user,
77
+ token_created=auth.created,
78
+ token_ttl=auth.ttl,
79
+ )
80
+ await ws.connect()
81
+
82
+ # Subscribe to a hub (delivers all child breaker/CT updates)
83
+ await ws.subscribe("IotWhem", whem.id)
84
+
85
+ # Handle notifications
86
+ ws.on_notification(lambda data: print("Update:", data))
87
+ ```
88
+
89
+ ## Supported Devices
90
+
91
+ | Device | API Model | Hub Type |
92
+ |--------|-----------|----------|
93
+ | LWHEM (Whole Home Energy Module) | `IotWhem` | Wi-Fi hub |
94
+ | DAU / LDATA (Data Acquisition Unit) | `ResidentialBreakerPanel` | Wi-Fi hub |
95
+ | Smart Breaker Gen 1 (trip only) | `ResidentialBreaker` | Child of LWHEM or DAU |
96
+ | Smart Breaker Gen 2 (on/off) | `ResidentialBreaker` | Child of LWHEM or DAU |
97
+ | Current Transformer (CT) | `IotCt` | Child of LWHEM only |
98
+ | LSBMA Add-on CT | `ResidentialBreaker` | Virtual composite |
99
+
100
+ ## Breaker Control
101
+
102
+ ```python
103
+ # Trip a Gen 1 breaker (cannot turn back on remotely)
104
+ await client.trip_breaker(breaker_id)
105
+
106
+ # Turn on/off a Gen 2 breaker
107
+ await client.turn_on_breaker(breaker_id)
108
+ await client.turn_off_breaker(breaker_id)
109
+
110
+ # Blink LED on a breaker
111
+ await client.blink_led(breaker_id)
112
+
113
+ # Identify LED on a LWHEM hub
114
+ await client.identify_whem(whem_id)
115
+ ```
116
+
117
+ ## Energy History
118
+
119
+ Energy history endpoints return consumption data for all devices in a residence.
120
+ Data is keyed by hub ID, then by breaker position and CT channel.
121
+
122
+ ```python
123
+ # Daily energy (hourly data points)
124
+ day = await client.get_energy_for_day(
125
+ residence_id=713744,
126
+ start_day="2026-02-16",
127
+ timezone="America/Los_Angeles",
128
+ )
129
+
130
+ # Weekly energy (daily data points for 7 days)
131
+ week = await client.get_energy_for_week(
132
+ residence_id=713744,
133
+ start_day="2026-02-17",
134
+ timezone="America/Los_Angeles",
135
+ )
136
+
137
+ # Monthly energy (daily data points for billing month)
138
+ month = await client.get_energy_for_month(
139
+ residence_id=713744,
140
+ billing_day_in_month="2026-02-28",
141
+ timezone="America/Los_Angeles",
142
+ )
143
+
144
+ # Yearly energy (monthly data points for 12 months)
145
+ year = await client.get_energy_for_year(
146
+ residence_id=713744,
147
+ billing_day_in_end_month="2026-02-16",
148
+ timezone="America/Los_Angeles",
149
+ )
150
+
151
+ # Response structure:
152
+ # {
153
+ # "<hub_id>": {
154
+ # "residentialBreakers": {"<position>": [{x, timestamp, energyConsumption, totalCost, ...}]},
155
+ # "iotCts": {"<channel>": [...]},
156
+ # "totals": [...]
157
+ # },
158
+ # "totals": [...] # residence-level totals
159
+ # }
160
+ ```
161
+
162
+ ## Firmware Check
163
+
164
+ ```python
165
+ # Check for available firmware updates
166
+ firmware = await client.check_firmware(
167
+ app_id="LWHEM",
168
+ model="AZ",
169
+ serial="1000_003D_5D58",
170
+ model_type="IotWhem",
171
+ )
172
+ # Returns list of firmware objects with version, fileUrl, signature, hash, size, notes
173
+ for fw in firmware:
174
+ print(f"v{fw['version']}: {fw['fileUrl']}")
175
+ ```
176
+
177
+
178
+ ## License
179
+
180
+ MIT
@@ -0,0 +1,158 @@
1
+ # aioleviton
2
+
3
+ Async Python client for the Leviton My Leviton cloud API.
4
+
5
+ Supports LWHEM and DAU/LDATA Smart Load Centers with WebSocket real-time push and REST API fallback.
6
+
7
+ ## Features
8
+
9
+ - Pure `asyncio` with `aiohttp` -- no blocking calls
10
+ - Accepts an injected `aiohttp.ClientSession` for connection pooling
11
+ - WebSocket real-time push notifications with automatic subscription management
12
+ - Full REST API coverage: authentication, device discovery, breaker control, energy history
13
+ - Typed data models with PEP 561 `py.typed` marker
14
+ - Support for both hub types: LWHEM (`IotWhem`) and DAU/LDATA (`ResidentialBreakerPanel`)
15
+ - Two-factor authentication (2FA) support
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install aioleviton
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ import aiohttp
27
+ from aioleviton import LevitonClient, LevitonWebSocket
28
+
29
+ async def main():
30
+ async with aiohttp.ClientSession() as session:
31
+ # Authenticate
32
+ client = LevitonClient(session)
33
+ auth = await client.login("user@example.com", "password")
34
+
35
+ # Discover devices
36
+ permissions = await client.get_permissions()
37
+ for perm in permissions:
38
+ if perm.residential_account_id:
39
+ residences = await client.get_residences(perm.residential_account_id)
40
+ for residence in residences:
41
+ whems = await client.get_whems(residence.id)
42
+ panels = await client.get_panels(residence.id)
43
+
44
+ # Get breakers for a LWHEM hub
45
+ for whem in whems:
46
+ breakers = await client.get_whem_breakers(whem.id)
47
+ cts = await client.get_cts(whem.id)
48
+
49
+ # Connect WebSocket for real-time updates
50
+ ws = LevitonWebSocket(
51
+ session=session,
52
+ token=auth.token,
53
+ user_id=auth.user_id,
54
+ user=auth.user,
55
+ token_created=auth.created,
56
+ token_ttl=auth.ttl,
57
+ )
58
+ await ws.connect()
59
+
60
+ # Subscribe to a hub (delivers all child breaker/CT updates)
61
+ await ws.subscribe("IotWhem", whem.id)
62
+
63
+ # Handle notifications
64
+ ws.on_notification(lambda data: print("Update:", data))
65
+ ```
66
+
67
+ ## Supported Devices
68
+
69
+ | Device | API Model | Hub Type |
70
+ |--------|-----------|----------|
71
+ | LWHEM (Whole Home Energy Module) | `IotWhem` | Wi-Fi hub |
72
+ | DAU / LDATA (Data Acquisition Unit) | `ResidentialBreakerPanel` | Wi-Fi hub |
73
+ | Smart Breaker Gen 1 (trip only) | `ResidentialBreaker` | Child of LWHEM or DAU |
74
+ | Smart Breaker Gen 2 (on/off) | `ResidentialBreaker` | Child of LWHEM or DAU |
75
+ | Current Transformer (CT) | `IotCt` | Child of LWHEM only |
76
+ | LSBMA Add-on CT | `ResidentialBreaker` | Virtual composite |
77
+
78
+ ## Breaker Control
79
+
80
+ ```python
81
+ # Trip a Gen 1 breaker (cannot turn back on remotely)
82
+ await client.trip_breaker(breaker_id)
83
+
84
+ # Turn on/off a Gen 2 breaker
85
+ await client.turn_on_breaker(breaker_id)
86
+ await client.turn_off_breaker(breaker_id)
87
+
88
+ # Blink LED on a breaker
89
+ await client.blink_led(breaker_id)
90
+
91
+ # Identify LED on a LWHEM hub
92
+ await client.identify_whem(whem_id)
93
+ ```
94
+
95
+ ## Energy History
96
+
97
+ Energy history endpoints return consumption data for all devices in a residence.
98
+ Data is keyed by hub ID, then by breaker position and CT channel.
99
+
100
+ ```python
101
+ # Daily energy (hourly data points)
102
+ day = await client.get_energy_for_day(
103
+ residence_id=713744,
104
+ start_day="2026-02-16",
105
+ timezone="America/Los_Angeles",
106
+ )
107
+
108
+ # Weekly energy (daily data points for 7 days)
109
+ week = await client.get_energy_for_week(
110
+ residence_id=713744,
111
+ start_day="2026-02-17",
112
+ timezone="America/Los_Angeles",
113
+ )
114
+
115
+ # Monthly energy (daily data points for billing month)
116
+ month = await client.get_energy_for_month(
117
+ residence_id=713744,
118
+ billing_day_in_month="2026-02-28",
119
+ timezone="America/Los_Angeles",
120
+ )
121
+
122
+ # Yearly energy (monthly data points for 12 months)
123
+ year = await client.get_energy_for_year(
124
+ residence_id=713744,
125
+ billing_day_in_end_month="2026-02-16",
126
+ timezone="America/Los_Angeles",
127
+ )
128
+
129
+ # Response structure:
130
+ # {
131
+ # "<hub_id>": {
132
+ # "residentialBreakers": {"<position>": [{x, timestamp, energyConsumption, totalCost, ...}]},
133
+ # "iotCts": {"<channel>": [...]},
134
+ # "totals": [...]
135
+ # },
136
+ # "totals": [...] # residence-level totals
137
+ # }
138
+ ```
139
+
140
+ ## Firmware Check
141
+
142
+ ```python
143
+ # Check for available firmware updates
144
+ firmware = await client.check_firmware(
145
+ app_id="LWHEM",
146
+ model="AZ",
147
+ serial="1000_003D_5D58",
148
+ model_type="IotWhem",
149
+ )
150
+ # Returns list of firmware objects with version, fileUrl, signature, hash, size, notes
151
+ for fw in firmware:
152
+ print(f"v{fw['version']}: {fw['fileUrl']}")
153
+ ```
154
+
155
+
156
+ ## License
157
+
158
+ MIT
@@ -0,0 +1,33 @@
1
+ """Async Python client for the Leviton My Leviton cloud API."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .client import LevitonClient
6
+ from .exceptions import (
7
+ LevitonAuthError,
8
+ LevitonConnectionError,
9
+ LevitonError,
10
+ LevitonInvalidCode,
11
+ LevitonTokenExpired,
12
+ LevitonTwoFactorRequired,
13
+ )
14
+ from .models import AuthToken, Breaker, Ct, Panel, Permission, Residence, Whem
15
+ from .websocket import LevitonWebSocket
16
+
17
+ __all__ = [
18
+ "AuthToken",
19
+ "Breaker",
20
+ "Ct",
21
+ "LevitonAuthError",
22
+ "LevitonClient",
23
+ "LevitonConnectionError",
24
+ "LevitonError",
25
+ "LevitonInvalidCode",
26
+ "LevitonTokenExpired",
27
+ "LevitonTwoFactorRequired",
28
+ "LevitonWebSocket",
29
+ "Panel",
30
+ "Permission",
31
+ "Residence",
32
+ "Whem",
33
+ ]