pytigo 0.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.
pytigo-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Blind Badger Studios
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,2 @@
1
+ include README.md
2
+ include LICENSE
pytigo-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytigo
3
+ Version: 0.3.0
4
+ Summary: Python client for the documented Tigo REST API v3
5
+ Author: Blind Badger Studios
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/BlindBadgerStudios/PyTigo
8
+ Project-URL: Repository, https://github.com/BlindBadgerStudios/PyTigo
9
+ Project-URL: Issues, https://github.com/BlindBadgerStudios/PyTigo/issues
10
+ Keywords: tigo,solar,energy,monitoring,photovoltaic,pv,api
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Topic :: Home Automation
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: requests>=2.31.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8.0; extra == "dev"
24
+ Requires-Dist: build; extra == "dev"
25
+ Requires-Dist: twine; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # pytigo
29
+
30
+ pytigo is a Python client for the documented Tigo REST API v3. It is focussed on near-real time monitoring (for time-series systems like Prometheus).
31
+
32
+ It is centered on the official API described in Tigo-API-V3.pdf, using:
33
+ - base URL: https://api2.tigoenergy.com/api/v3/
34
+ - token auth from `users/login`
35
+ - documented systems, objects, sources, and data endpoints
36
+
37
+ Supported official API areas:
38
+ - users/login
39
+ - users/logout
40
+ - users/get
41
+ - systems/list
42
+ - systems/view
43
+ - systems/layout
44
+ - objects/system
45
+ - objects/types
46
+ - sources/system
47
+ - data/summary
48
+ - data/aggregate
49
+ - data/combined
50
+ - alerts/system
51
+ - alerts/types
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install pytigo
57
+ ```
58
+
59
+ ## Quick start
60
+
61
+ ```python
62
+ from pytigo import TigoClient
63
+
64
+ client = TigoClient(username="you@example.com", password="super-secret")
65
+ auth = client.login()
66
+
67
+ systems = client.list_systems()
68
+ system = systems[0]
69
+
70
+ layout = client.get_layout(system.system_id)
71
+ objects = client.get_objects(system.system_id)
72
+ sources = client.get_sources(system.system_id)
73
+ summary = client.get_summary(system.system_id)
74
+ aggregate = client.get_aggregate(
75
+ system.system_id,
76
+ start="2026-03-31T00:00:00",
77
+ end="2026-03-31T23:59:59",
78
+ level="day",
79
+ param="Pin",
80
+ )
81
+ combined = client.get_combined(
82
+ system.system_id,
83
+ start="2026-03-31T00:00:00",
84
+ end="2026-03-31T23:59:59",
85
+ agg="day",
86
+ )
87
+ alerts = client.get_alerts(system.system_id, limit=10)
88
+ alert_types = client.get_alert_types()
89
+
90
+ print(auth.user_id)
91
+ print(system.name)
92
+ print(layout.inverters[0].label if layout.inverters else None)
93
+ print(len(objects))
94
+ print(sources[0].serial if sources else None)
95
+ print(summary.daily_energy_dc)
96
+ print(aggregate.rows[0].values)
97
+ print(combined.rows[0].values)
98
+ print(alerts[0].title if alerts else None)
99
+ print(alert_types[0].title if alert_types else None)
100
+ ```
101
+
102
+ ## Notes
103
+
104
+ The library returns typed Python models for JSON endpoints and a parsed table model for CSV-style telemetry endpoints like `data/aggregate` and `data/combined`.
105
+
106
+ `data/aggregate` and `data/combined` are especially useful for exporter/monitoring use cases because they preserve timestamped telemetry in a table-shaped format that is easy to flatten for Prometheus, Grafana, or ETL pipelines.
107
+
108
+
pytigo-0.3.0/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # pytigo
2
+
3
+ pytigo is a Python client for the documented Tigo REST API v3. It is focussed on near-real time monitoring (for time-series systems like Prometheus).
4
+
5
+ It is centered on the official API described in Tigo-API-V3.pdf, using:
6
+ - base URL: https://api2.tigoenergy.com/api/v3/
7
+ - token auth from `users/login`
8
+ - documented systems, objects, sources, and data endpoints
9
+
10
+ Supported official API areas:
11
+ - users/login
12
+ - users/logout
13
+ - users/get
14
+ - systems/list
15
+ - systems/view
16
+ - systems/layout
17
+ - objects/system
18
+ - objects/types
19
+ - sources/system
20
+ - data/summary
21
+ - data/aggregate
22
+ - data/combined
23
+ - alerts/system
24
+ - alerts/types
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install pytigo
30
+ ```
31
+
32
+ ## Quick start
33
+
34
+ ```python
35
+ from pytigo import TigoClient
36
+
37
+ client = TigoClient(username="you@example.com", password="super-secret")
38
+ auth = client.login()
39
+
40
+ systems = client.list_systems()
41
+ system = systems[0]
42
+
43
+ layout = client.get_layout(system.system_id)
44
+ objects = client.get_objects(system.system_id)
45
+ sources = client.get_sources(system.system_id)
46
+ summary = client.get_summary(system.system_id)
47
+ aggregate = client.get_aggregate(
48
+ system.system_id,
49
+ start="2026-03-31T00:00:00",
50
+ end="2026-03-31T23:59:59",
51
+ level="day",
52
+ param="Pin",
53
+ )
54
+ combined = client.get_combined(
55
+ system.system_id,
56
+ start="2026-03-31T00:00:00",
57
+ end="2026-03-31T23:59:59",
58
+ agg="day",
59
+ )
60
+ alerts = client.get_alerts(system.system_id, limit=10)
61
+ alert_types = client.get_alert_types()
62
+
63
+ print(auth.user_id)
64
+ print(system.name)
65
+ print(layout.inverters[0].label if layout.inverters else None)
66
+ print(len(objects))
67
+ print(sources[0].serial if sources else None)
68
+ print(summary.daily_energy_dc)
69
+ print(aggregate.rows[0].values)
70
+ print(combined.rows[0].values)
71
+ print(alerts[0].title if alerts else None)
72
+ print(alert_types[0].title if alert_types else None)
73
+ ```
74
+
75
+ ## Notes
76
+
77
+ The library returns typed Python models for JSON endpoints and a parsed table model for CSV-style telemetry endpoints like `data/aggregate` and `data/combined`.
78
+
79
+ `data/aggregate` and `data/combined` are especially useful for exporter/monitoring use cases because they preserve timestamped telemetry in a table-shaped format that is easy to flatten for Prometheus, Grafana, or ETL pipelines.
80
+
81
+
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pytigo"
7
+ version = "0.3.0"
8
+ description = "Python client for the documented Tigo REST API v3"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Blind Badger Studios" },
14
+ ]
15
+ keywords = ["tigo", "solar", "energy", "monitoring", "photovoltaic", "pv", "api"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Topic :: Home Automation",
22
+ "Topic :: Scientific/Engineering",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "requests>=2.31.0",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/BlindBadgerStudios/PyTigo"
31
+ Repository = "https://github.com/BlindBadgerStudios/PyTigo"
32
+ Issues = "https://github.com/BlindBadgerStudios/PyTigo/issues"
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "pytest>=8.0",
37
+ "build",
38
+ "twine",
39
+ ]
40
+
41
+ [tool.pytest.ini_options]
42
+ pythonpath = ["src"]
43
+ testpaths = ["tests"]
pytigo-0.3.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ from .client import TigoClient
2
+ from .models import TigoPage
3
+
4
+ __version__ = "0.3.0"
5
+
6
+ __all__ = ["TigoClient", "TigoPage", "__version__"]
@@ -0,0 +1,297 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Literal
5
+
6
+ from requests import Session
7
+
8
+ from .models import (
9
+ TigoAlert,
10
+ TigoAlertType,
11
+ TigoAuth,
12
+ TigoCSVTable,
13
+ TigoObjectNode,
14
+ TigoObjectType,
15
+ TigoPage,
16
+ TigoSource,
17
+ TigoSummary,
18
+ TigoSystem,
19
+ TigoSystemLayout,
20
+ TigoUser,
21
+ )
22
+ from .parsing import (
23
+ parse_alert_types_response,
24
+ parse_alerts_response,
25
+ parse_csv_table,
26
+ parse_layout_response,
27
+ parse_login_response,
28
+ parse_object_types_response,
29
+ parse_objects_response,
30
+ parse_sources_response,
31
+ parse_summary_response,
32
+ parse_system_response,
33
+ parse_systems_response,
34
+ parse_user_response,
35
+ )
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ class TigoClient:
41
+ api_root = "https://api2.tigoenergy.com/api/v3"
42
+
43
+ def __init__(
44
+ self,
45
+ username: str,
46
+ password: str,
47
+ *,
48
+ session: Session | None = None,
49
+ timeout: int = 30,
50
+ ) -> None:
51
+ self.username = username
52
+ self.password = password
53
+ self.timeout = timeout
54
+ self.session = session or Session()
55
+ self.auth: TigoAuth | None = None
56
+
57
+ def _headers(self) -> dict[str, str]:
58
+ if not self.auth:
59
+ raise ValueError("Not authenticated. Call login() first.")
60
+ return {"Authorization": f"Bearer {self.auth.auth_token}"}
61
+
62
+ def __enter__(self) -> TigoClient:
63
+ return self
64
+
65
+ def __exit__(self, *args: object) -> None:
66
+ self.session.close()
67
+
68
+ def login(self) -> TigoAuth:
69
+ logger.debug("GET /users/login user=%s", self.username)
70
+ response = self.session.get(
71
+ f"{self.api_root}/users/login",
72
+ auth=(self.username, self.password),
73
+ timeout=self.timeout,
74
+ )
75
+ response.raise_for_status()
76
+ self.auth = parse_login_response(response.json())
77
+ logger.debug("login successful user_id=%s", self.auth.user_id)
78
+ return self.auth
79
+
80
+ def logout(self) -> dict:
81
+ logger.debug("GET /users/logout")
82
+ response = self.session.get(
83
+ f"{self.api_root}/users/logout",
84
+ headers=self._headers(),
85
+ timeout=self.timeout,
86
+ )
87
+ response.raise_for_status()
88
+ payload = response.json()
89
+ self.auth = None
90
+ return payload
91
+
92
+ def get_current_user(self, user_id: int | None = None) -> TigoUser:
93
+ if user_id is None:
94
+ if not self.auth:
95
+ raise ValueError("user_id is required before login; after login it defaults to auth.user_id")
96
+ user_id = self.auth.user_id
97
+ logger.debug("GET /users/%s", user_id)
98
+ response = self.session.get(
99
+ f"{self.api_root}/users/{user_id}",
100
+ headers=self._headers(),
101
+ timeout=self.timeout,
102
+ )
103
+ response.raise_for_status()
104
+ return parse_user_response(response.json())
105
+
106
+ def list_systems(self, *, page: int | None = None, limit: int | None = None, sort: str | None = None) -> TigoPage[TigoSystem]:
107
+ params: dict[str, int | str] = {}
108
+ if page is not None:
109
+ params["page"] = page
110
+ if limit is not None:
111
+ params["limit"] = limit
112
+ if sort is not None:
113
+ params["sort"] = sort
114
+ logger.debug("GET /systems params=%s", params)
115
+ response = self.session.get(
116
+ f"{self.api_root}/systems",
117
+ headers=self._headers(),
118
+ params=params or None,
119
+ timeout=self.timeout,
120
+ )
121
+ response.raise_for_status()
122
+ return parse_systems_response(response.json())
123
+
124
+ def get_system(self, system_id: int, *, include: list[str] | None = None) -> TigoSystem:
125
+ params: dict[str, str | int] = {"id": system_id}
126
+ if include:
127
+ params["include"] = ",".join(include)
128
+ logger.debug("GET /systems/view system_id=%s", system_id)
129
+ response = self.session.get(
130
+ f"{self.api_root}/systems/view",
131
+ headers=self._headers(),
132
+ params=params,
133
+ timeout=self.timeout,
134
+ )
135
+ response.raise_for_status()
136
+ return parse_system_response(response.json())
137
+
138
+ def get_layout(self, system_id: int) -> TigoSystemLayout:
139
+ logger.debug("GET /systems/layout system_id=%s", system_id)
140
+ response = self.session.get(
141
+ f"{self.api_root}/systems/layout",
142
+ headers=self._headers(),
143
+ params={"id": system_id},
144
+ timeout=self.timeout,
145
+ )
146
+ response.raise_for_status()
147
+ return parse_layout_response(response.json())
148
+
149
+ def get_objects(self, system_id: int) -> list[TigoObjectNode]:
150
+ logger.debug("GET /objects/system system_id=%s", system_id)
151
+ response = self.session.get(
152
+ f"{self.api_root}/objects/system",
153
+ headers=self._headers(),
154
+ params={"system_id": system_id},
155
+ timeout=self.timeout,
156
+ )
157
+ response.raise_for_status()
158
+ return parse_objects_response(response.json())
159
+
160
+ def get_object_types(self) -> list[TigoObjectType]:
161
+ logger.debug("GET /objects/types")
162
+ response = self.session.get(
163
+ f"{self.api_root}/objects/types",
164
+ headers=self._headers(),
165
+ timeout=self.timeout,
166
+ )
167
+ response.raise_for_status()
168
+ return parse_object_types_response(response.json())
169
+
170
+ def get_sources(self, system_id: int) -> list[TigoSource]:
171
+ logger.debug("GET /sources/system system_id=%s", system_id)
172
+ response = self.session.get(
173
+ f"{self.api_root}/sources/system",
174
+ headers=self._headers(),
175
+ params={"system_id": system_id},
176
+ timeout=self.timeout,
177
+ )
178
+ response.raise_for_status()
179
+ return parse_sources_response(response.json())
180
+
181
+ def get_summary(self, system_id: int) -> TigoSummary:
182
+ logger.debug("GET /data/summary system_id=%s", system_id)
183
+ response = self.session.get(
184
+ f"{self.api_root}/data/summary",
185
+ headers=self._headers(),
186
+ params={"system_id": system_id},
187
+ timeout=self.timeout,
188
+ )
189
+ response.raise_for_status()
190
+ return parse_summary_response(response.json())
191
+
192
+ def get_aggregate(
193
+ self,
194
+ system_id: int,
195
+ *,
196
+ start: str,
197
+ end: str,
198
+ level: Literal["min", "hour", "day"] = "min",
199
+ param: str = "Pin",
200
+ object_ids: list[int] | None = None,
201
+ header: str | None = None,
202
+ sensors: bool | None = None,
203
+ ) -> TigoCSVTable:
204
+ params: dict[str, str] = {
205
+ "system_id": str(system_id),
206
+ "start": start,
207
+ "end": end,
208
+ "level": level,
209
+ "param": param,
210
+ }
211
+ if object_ids:
212
+ params["object_ids"] = ",".join(str(i) for i in object_ids)
213
+ if header:
214
+ params["header"] = header
215
+ if sensors is not None:
216
+ params["sensors"] = "true" if sensors else "false"
217
+ logger.debug("GET /data/aggregate system_id=%s param=%s level=%s", system_id, param, level)
218
+ response = self.session.get(
219
+ f"{self.api_root}/data/aggregate",
220
+ headers=self._headers(),
221
+ params=params,
222
+ timeout=self.timeout,
223
+ )
224
+ response.raise_for_status()
225
+ return parse_csv_table(response.text)
226
+
227
+ def get_combined(
228
+ self,
229
+ system_id: int,
230
+ *,
231
+ start: str,
232
+ end: str,
233
+ agg: Literal["min", "hour", "day"],
234
+ object_ids: list[int] | None = None,
235
+ ) -> TigoCSVTable:
236
+ params: dict[str, str] = {
237
+ "system_id": str(system_id),
238
+ "start": start,
239
+ "end": end,
240
+ "agg": agg,
241
+ }
242
+ if object_ids:
243
+ params["object_ids"] = ",".join(str(i) for i in object_ids)
244
+ logger.debug("GET /data/combined system_id=%s agg=%s", system_id, agg)
245
+ response = self.session.get(
246
+ f"{self.api_root}/data/combined",
247
+ headers=self._headers(),
248
+ params=params,
249
+ timeout=self.timeout,
250
+ )
251
+ response.raise_for_status()
252
+ return parse_csv_table(response.text)
253
+
254
+ def get_alerts(
255
+ self,
256
+ system_id: int,
257
+ *,
258
+ language: str | None = None,
259
+ start_added: str | None = None,
260
+ end_added: str | None = None,
261
+ page: int | None = None,
262
+ limit: int | None = None,
263
+ ) -> TigoPage[TigoAlert]:
264
+ params: dict[str, str | int] = {"system_id": system_id}
265
+ if language:
266
+ params["language"] = language
267
+ if start_added:
268
+ params["start_added"] = start_added
269
+ if end_added:
270
+ params["end_added"] = end_added
271
+ if page is not None:
272
+ params["page"] = page
273
+ if limit is not None:
274
+ params["limit"] = limit
275
+ logger.debug("GET /alerts/system system_id=%s", system_id)
276
+ response = self.session.get(
277
+ f"{self.api_root}/alerts/system",
278
+ headers=self._headers(),
279
+ params=params,
280
+ timeout=self.timeout,
281
+ )
282
+ response.raise_for_status()
283
+ return parse_alerts_response(response.json())
284
+
285
+ def get_alert_types(self, *, language: str | None = None) -> list[TigoAlertType]:
286
+ params: dict[str, str] = {}
287
+ if language:
288
+ params["language"] = language
289
+ logger.debug("GET /alerts/types")
290
+ response = self.session.get(
291
+ f"{self.api_root}/alerts/types",
292
+ headers=self._headers(),
293
+ params=params or None,
294
+ timeout=self.timeout,
295
+ )
296
+ response.raise_for_status()
297
+ return parse_alert_types_response(response.json())