leneda-client 0.1.0__tar.gz → 0.2.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.
- {leneda_client-0.1.0 → leneda_client-0.2.0}/PKG-INFO +1 -1
- {leneda_client-0.1.0 → leneda_client-0.2.0}/examples/advanced_usage.py +10 -13
- {leneda_client-0.1.0 → leneda_client-0.2.0}/examples/basic_usage.py +5 -6
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda/__init__.py +2 -4
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda/client.py +49 -39
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda/models.py +16 -12
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda/obis_codes.py +60 -63
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda/version.py +1 -1
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda_client.egg-info/PKG-INFO +1 -1
- {leneda_client-0.1.0 → leneda_client-0.2.0}/tests/test_client.py +25 -11
- {leneda_client-0.1.0 → leneda_client-0.2.0}/MANIFEST.in +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/README.md +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/pyproject.toml +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/requirements.txt +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/setup.cfg +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/setup.py +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda_client.egg-info/SOURCES.txt +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda_client.egg-info/dependency_links.txt +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda_client.egg-info/requires.txt +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/src/leneda_client.egg-info/top_level.txt +0 -0
- {leneda_client-0.1.0 → leneda_client-0.2.0}/tests/__init__.py +0 -0
@@ -10,8 +10,8 @@ LENEDA_API_KEY: Your Leneda API key
|
|
10
10
|
LENEDA_ENERGY_ID: Your Energy ID
|
11
11
|
|
12
12
|
Usage:
|
13
|
-
python advanced_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID
|
14
|
-
python advanced_usage.py --metering-point LU-METERING_POINT1 --example 2
|
13
|
+
python advanced_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID --metering-point LU-METERING_POINT1
|
14
|
+
python advanced_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID --metering-point LU-METERING_POINT1 --example 2
|
15
15
|
"""
|
16
16
|
|
17
17
|
import argparse
|
@@ -24,12 +24,9 @@ from typing import Optional
|
|
24
24
|
import matplotlib.pyplot as plt
|
25
25
|
import pandas as pd
|
26
26
|
|
27
|
-
from leneda import
|
28
|
-
|
29
|
-
|
30
|
-
LenedaClient,
|
31
|
-
MeteringData,
|
32
|
-
)
|
27
|
+
from leneda import LenedaClient
|
28
|
+
from leneda.models import AggregatedMeteringData, MeteringData
|
29
|
+
from leneda.obis_codes import ObisCode
|
33
30
|
|
34
31
|
# Set up logging
|
35
32
|
logging.basicConfig(
|
@@ -193,7 +190,7 @@ def compare_consumption_periods(
|
|
193
190
|
# Get data for period 1
|
194
191
|
period1_data = client.get_metering_data(
|
195
192
|
metering_point_code=metering_point,
|
196
|
-
obis_code=
|
193
|
+
obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
197
194
|
start_date_time=period1_start,
|
198
195
|
end_date_time=period1_end,
|
199
196
|
)
|
@@ -201,7 +198,7 @@ def compare_consumption_periods(
|
|
201
198
|
# Get data for period 2
|
202
199
|
period2_data = client.get_metering_data(
|
203
200
|
metering_point_code=metering_point,
|
204
|
-
obis_code=
|
201
|
+
obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
205
202
|
start_date_time=period2_start,
|
206
203
|
end_date_time=period2_end,
|
207
204
|
)
|
@@ -290,7 +287,7 @@ def analyze_monthly_trends(
|
|
290
287
|
|
291
288
|
monthly_data = client.get_aggregated_metering_data(
|
292
289
|
metering_point_code=metering_point,
|
293
|
-
obis_code=
|
290
|
+
obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
294
291
|
start_date=start_date,
|
295
292
|
end_date=end_date,
|
296
293
|
aggregation_level="Month",
|
@@ -355,7 +352,7 @@ def detect_consumption_anomalies(
|
|
355
352
|
# Get hourly consumption data
|
356
353
|
consumption_data = client.get_metering_data(
|
357
354
|
metering_point_code=metering_point,
|
358
|
-
obis_code=
|
355
|
+
obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
359
356
|
start_date_time=start_date,
|
360
357
|
end_date_time=end_date,
|
361
358
|
)
|
@@ -473,7 +470,7 @@ def main():
|
|
473
470
|
)
|
474
471
|
consumption_data = client.get_metering_data(
|
475
472
|
metering_point_code=metering_point,
|
476
|
-
obis_code=
|
473
|
+
obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
477
474
|
start_date_time=start_date,
|
478
475
|
end_date_time=end_date,
|
479
476
|
)
|
@@ -9,8 +9,7 @@ Environment variables:
|
|
9
9
|
LENEDA_ENERGY_ID: Your Energy ID
|
10
10
|
|
11
11
|
Usage:
|
12
|
-
python basic_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID
|
13
|
-
python basic_usage.py --metering-point LU-METERING_POINT1
|
12
|
+
python basic_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID --metering-point LU-METERING_POINT1
|
14
13
|
"""
|
15
14
|
|
16
15
|
import argparse
|
@@ -19,7 +18,8 @@ import os
|
|
19
18
|
import sys
|
20
19
|
from datetime import datetime, timedelta
|
21
20
|
|
22
|
-
from leneda import
|
21
|
+
from leneda import LenedaClient
|
22
|
+
from leneda.obis_codes import ObisCode
|
23
23
|
|
24
24
|
# Set up logging
|
25
25
|
logging.basicConfig(
|
@@ -45,7 +45,6 @@ def parse_arguments():
|
|
45
45
|
# Other parameters
|
46
46
|
parser.add_argument(
|
47
47
|
"--metering-point",
|
48
|
-
default="LU-METERING_POINT1",
|
49
48
|
help="Metering point code (default: LU-METERING_POINT1)",
|
50
49
|
)
|
51
50
|
parser.add_argument(
|
@@ -107,7 +106,7 @@ def main():
|
|
107
106
|
print(f"\nExample 1: Getting hourly electricity consumption data for the last {days} days")
|
108
107
|
consumption_data = client.get_metering_data(
|
109
108
|
metering_point_code=metering_point,
|
110
|
-
obis_code=
|
109
|
+
obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
111
110
|
start_date_time=start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
112
111
|
end_date_time=end_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
113
112
|
)
|
@@ -136,7 +135,7 @@ def main():
|
|
136
135
|
print(f"\nExample 2: Getting monthly aggregated electricity consumption for {today.year}")
|
137
136
|
monthly_consumption = client.get_aggregated_metering_data(
|
138
137
|
metering_point_code=metering_point,
|
139
|
-
obis_code=
|
138
|
+
obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
140
139
|
start_date=first_day.strftime("%Y-%m-%d"),
|
141
140
|
end_date=last_day.strftime("%Y-%m-%d"),
|
142
141
|
aggregation_level="Month",
|
@@ -17,7 +17,7 @@ from .models import (
|
|
17
17
|
)
|
18
18
|
|
19
19
|
# Import the OBIS code constants
|
20
|
-
from .obis_codes import
|
20
|
+
from .obis_codes import ObisCode
|
21
21
|
|
22
22
|
# Import the version
|
23
23
|
from .version import __version__
|
@@ -25,9 +25,7 @@ from .version import __version__
|
|
25
25
|
# Define what's available when using "from leneda import *"
|
26
26
|
__all__ = [
|
27
27
|
"LenedaClient",
|
28
|
-
"
|
29
|
-
"ElectricityProduction",
|
30
|
-
"GasConsumption",
|
28
|
+
"ObisCode",
|
31
29
|
"MeteringValue",
|
32
30
|
"MeteringData",
|
33
31
|
"AggregatedMeteringValue",
|
@@ -8,11 +8,15 @@ energy consumption and production data for electricity and gas.
|
|
8
8
|
import json
|
9
9
|
import logging
|
10
10
|
from datetime import datetime
|
11
|
-
from typing import Any, Dict, Union
|
11
|
+
from typing import Any, Dict, List, Optional, Union
|
12
12
|
|
13
13
|
import requests
|
14
14
|
|
15
|
-
from .models import
|
15
|
+
from .models import (
|
16
|
+
AggregatedMeteringData,
|
17
|
+
MeteringData,
|
18
|
+
)
|
19
|
+
from .obis_codes import ObisCode
|
16
20
|
|
17
21
|
# Set up logging
|
18
22
|
logger = logging.getLogger("leneda.client")
|
@@ -50,22 +54,22 @@ class LenedaClient:
|
|
50
54
|
|
51
55
|
def _make_request(
|
52
56
|
self,
|
57
|
+
method: str,
|
53
58
|
endpoint: str,
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
) -> Dict[str, Any]:
|
59
|
+
params: Optional[dict] = None,
|
60
|
+
json_data: Optional[dict] = None,
|
61
|
+
) -> dict:
|
58
62
|
"""
|
59
63
|
Make a request to the Leneda API.
|
60
64
|
|
61
65
|
Args:
|
62
|
-
|
63
|
-
|
64
|
-
params:
|
65
|
-
|
66
|
+
method: The HTTP method to use
|
67
|
+
endpoint: The API endpoint to call
|
68
|
+
params: Optional query parameters
|
69
|
+
json_data: Optional JSON data to send in the request body
|
66
70
|
|
67
71
|
Returns:
|
68
|
-
|
72
|
+
The JSON response from the API
|
69
73
|
"""
|
70
74
|
url = f"{self.BASE_URL}/{endpoint}"
|
71
75
|
|
@@ -73,13 +77,13 @@ class LenedaClient:
|
|
73
77
|
logger.debug(f"Making {method} request to {url}")
|
74
78
|
if params:
|
75
79
|
logger.debug(f"Query parameters: {params}")
|
76
|
-
if
|
77
|
-
logger.debug(f"Request data: {
|
80
|
+
if json_data:
|
81
|
+
logger.debug(f"Request data: {json.dumps(json_data, indent=2)}")
|
78
82
|
|
79
83
|
try:
|
80
84
|
# Make the request
|
81
85
|
response = requests.request(
|
82
|
-
method=method, url=url, headers=self.headers, params=params, json=
|
86
|
+
method=method, url=url, headers=self.headers, params=params, json=json_data
|
83
87
|
)
|
84
88
|
|
85
89
|
# Check for HTTP errors
|
@@ -117,7 +121,7 @@ class LenedaClient:
|
|
117
121
|
def get_metering_data(
|
118
122
|
self,
|
119
123
|
metering_point_code: str,
|
120
|
-
obis_code:
|
124
|
+
obis_code: ObisCode,
|
121
125
|
start_date_time: Union[str, datetime],
|
122
126
|
end_date_time: Union[str, datetime],
|
123
127
|
) -> MeteringData:
|
@@ -126,7 +130,7 @@ class LenedaClient:
|
|
126
130
|
|
127
131
|
Args:
|
128
132
|
metering_point_code: The metering point code
|
129
|
-
obis_code: The OBIS code
|
133
|
+
obis_code: The OBIS code (from ElectricityConsumption, ElectricityProduction, or GasConsumption)
|
130
134
|
start_date_time: Start date and time (ISO format string or datetime object)
|
131
135
|
end_date_time: End date and time (ISO format string or datetime object)
|
132
136
|
|
@@ -142,23 +146,21 @@ class LenedaClient:
|
|
142
146
|
# Set up the endpoint and parameters
|
143
147
|
endpoint = f"metering-points/{metering_point_code}/time-series"
|
144
148
|
params = {
|
145
|
-
"obisCode": obis_code,
|
149
|
+
"obisCode": obis_code.value, # Use enum value for API request
|
146
150
|
"startDateTime": start_date_time,
|
147
151
|
"endDateTime": end_date_time,
|
148
152
|
}
|
149
153
|
|
150
154
|
# Make the request
|
151
|
-
response_data = self._make_request(endpoint, params=params)
|
155
|
+
response_data = self._make_request(method="GET", endpoint=endpoint, params=params)
|
152
156
|
|
153
157
|
# Parse the response into a MeteringData object
|
154
|
-
return MeteringData.from_dict(
|
155
|
-
response_data, metering_point_code=metering_point_code, obis_code=obis_code
|
156
|
-
)
|
158
|
+
return MeteringData.from_dict(response_data)
|
157
159
|
|
158
160
|
def get_aggregated_metering_data(
|
159
161
|
self,
|
160
162
|
metering_point_code: str,
|
161
|
-
obis_code:
|
163
|
+
obis_code: ObisCode,
|
162
164
|
start_date: Union[str, datetime],
|
163
165
|
end_date: Union[str, datetime],
|
164
166
|
aggregation_level: str = "Day",
|
@@ -169,11 +171,11 @@ class LenedaClient:
|
|
169
171
|
|
170
172
|
Args:
|
171
173
|
metering_point_code: The metering point code
|
172
|
-
obis_code: The OBIS code
|
174
|
+
obis_code: The OBIS code (from ElectricityConsumption, ElectricityProduction, or GasConsumption)
|
173
175
|
start_date: Start date (ISO format string or datetime object)
|
174
176
|
end_date: End date (ISO format string or datetime object)
|
175
|
-
aggregation_level: Aggregation level (Day, Week, Month,
|
176
|
-
transformation_mode: Transformation mode (Accumulation
|
177
|
+
aggregation_level: Aggregation level (Hour, Day, Week, Month, Infinite)
|
178
|
+
transformation_mode: Transformation mode (Accumulation)
|
177
179
|
|
178
180
|
Returns:
|
179
181
|
AggregatedMeteringData object containing the aggregated time series data
|
@@ -187,7 +189,7 @@ class LenedaClient:
|
|
187
189
|
# Set up the endpoint and parameters
|
188
190
|
endpoint = f"metering-points/{metering_point_code}/time-series/aggregated"
|
189
191
|
params = {
|
190
|
-
"obisCode": obis_code,
|
192
|
+
"obisCode": obis_code.value, # Use enum value for API request
|
191
193
|
"startDate": start_date,
|
192
194
|
"endDate": end_date,
|
193
195
|
"aggregationLevel": aggregation_level,
|
@@ -195,32 +197,40 @@ class LenedaClient:
|
|
195
197
|
}
|
196
198
|
|
197
199
|
# Make the request
|
198
|
-
response_data = self._make_request(endpoint, params=params)
|
200
|
+
response_data = self._make_request(method="GET", endpoint=endpoint, params=params)
|
199
201
|
|
200
202
|
# Parse the response into an AggregatedMeteringData object
|
201
|
-
return AggregatedMeteringData.from_dict(
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
203
|
+
return AggregatedMeteringData.from_dict(response_data)
|
204
|
+
|
205
|
+
def request_metering_data_access(
|
206
|
+
self,
|
207
|
+
from_energy_id: str,
|
208
|
+
from_name: str,
|
209
|
+
metering_point_codes: List[str],
|
210
|
+
obis_codes: List[ObisCode],
|
211
|
+
) -> Dict[str, Any]:
|
210
212
|
"""
|
211
213
|
Request access to metering data for a specific metering point.
|
212
214
|
|
213
215
|
Args:
|
214
|
-
|
216
|
+
from_energy_id: The energy ID of the requester
|
217
|
+
from_name: The name of the requester
|
218
|
+
metering_point_codes: The metering point codes to access
|
219
|
+
obis_point_codes: The OBIS point codes to access (from ElectricityConsumption, ElectricityProduction, or GasConsumption)
|
215
220
|
|
216
221
|
Returns:
|
217
222
|
Response data from the API
|
218
223
|
"""
|
219
224
|
# Set up the endpoint and data
|
220
225
|
endpoint = "metering-data-access-request"
|
221
|
-
data = {
|
226
|
+
data = {
|
227
|
+
"from": from_energy_id,
|
228
|
+
"fromName": from_name,
|
229
|
+
"meteringPointCodes": metering_point_codes,
|
230
|
+
"obisCodes": [code.value for code in obis_codes], # Use enum values for API request
|
231
|
+
}
|
222
232
|
|
223
233
|
# Make the request
|
224
|
-
response_data = self._make_request(
|
234
|
+
response_data = self._make_request(method="POST", endpoint=endpoint, json_data=data)
|
225
235
|
|
226
236
|
return response_data
|
@@ -12,6 +12,8 @@ from typing import Any, Dict, List
|
|
12
12
|
|
13
13
|
from dateutil import parser
|
14
14
|
|
15
|
+
from .obis_codes import ObisCode
|
16
|
+
|
15
17
|
# Set up logging
|
16
18
|
logger = logging.getLogger("leneda.models")
|
17
19
|
|
@@ -64,23 +66,21 @@ class MeteringData:
|
|
64
66
|
"""Metering data for a specific metering point and OBIS code."""
|
65
67
|
|
66
68
|
metering_point_code: str
|
67
|
-
obis_code:
|
69
|
+
obis_code: ObisCode
|
68
70
|
interval_length: str
|
69
71
|
unit: str
|
70
72
|
items: List[MeteringValue] = field(default_factory=list)
|
71
73
|
|
72
74
|
@classmethod
|
73
|
-
def from_dict(
|
74
|
-
cls, data: Dict[str, Any], metering_point_code: str = "", obis_code: str = ""
|
75
|
-
) -> "MeteringData":
|
75
|
+
def from_dict(cls, data: Dict[str, Any]) -> "MeteringData":
|
76
76
|
"""Create a MeteringData from a dictionary."""
|
77
77
|
try:
|
78
78
|
# Log the raw data for debugging
|
79
79
|
logger.debug(f"Creating MeteringData from: {data}")
|
80
80
|
|
81
|
-
# Use values from the response
|
82
|
-
metering_point_code_value = data
|
83
|
-
obis_code_value = data
|
81
|
+
# Use values from the response
|
82
|
+
metering_point_code_value = data["meteringPointCode"]
|
83
|
+
obis_code_value = ObisCode(data["obisCode"])
|
84
84
|
|
85
85
|
# Extract items safely
|
86
86
|
items_data = data.get("items", [])
|
@@ -101,6 +101,10 @@ class MeteringData:
|
|
101
101
|
unit=data.get("unit", ""),
|
102
102
|
items=items,
|
103
103
|
)
|
104
|
+
except KeyError as e:
|
105
|
+
logger.error(f"Missing key in API response: {e}")
|
106
|
+
logger.debug(f"API response data: {data}")
|
107
|
+
raise
|
104
108
|
except Exception as e:
|
105
109
|
logger.error(f"Error creating MeteringData: {e}")
|
106
110
|
logger.debug(f"API response data: {data}")
|
@@ -184,10 +188,6 @@ class AggregatedMeteringData:
|
|
184
188
|
def from_dict(
|
185
189
|
cls,
|
186
190
|
data: Dict[str, Any],
|
187
|
-
metering_point_code: str = "",
|
188
|
-
obis_code: str = "",
|
189
|
-
aggregation_level: str = "",
|
190
|
-
transformation_mode: str = "",
|
191
191
|
) -> "AggregatedMeteringData":
|
192
192
|
"""Create an AggregatedMeteringData from a dictionary."""
|
193
193
|
try:
|
@@ -206,7 +206,11 @@ class AggregatedMeteringData:
|
|
206
206
|
logger.warning(f"Skipping invalid aggregated item: {e}")
|
207
207
|
logger.debug(f"Invalid item data: {item_data}")
|
208
208
|
|
209
|
-
return cls(unit=data
|
209
|
+
return cls(unit=data["unit"], aggregated_time_series=time_series)
|
210
|
+
except KeyError as e:
|
211
|
+
logger.error(f"Missing key in API response: {e}")
|
212
|
+
logger.debug(f"API response data: {data}")
|
213
|
+
raise
|
210
214
|
except Exception as e:
|
211
215
|
logger.error(f"Error creating AggregatedMeteringData: {e}")
|
212
216
|
logger.debug(f"API response data: {data}")
|
@@ -20,139 +20,136 @@ class ObisCodeInfo:
|
|
20
20
|
description: str
|
21
21
|
|
22
22
|
|
23
|
-
class
|
24
|
-
"""OBIS codes for
|
23
|
+
class ObisCode(str, Enum):
|
24
|
+
"""OBIS codes for all energy types."""
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
# Electricity Consumption
|
27
|
+
ELEC_CONSUMPTION_ACTIVE = "1-1:1.29.0" # Measured active consumption (kW)
|
28
|
+
ELEC_CONSUMPTION_REACTIVE = "1-1:3.29.0" # Measured reactive consumption (kVAR)
|
28
29
|
|
29
30
|
# Consumption covered by production sharing groups
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
ELEC_CONSUMPTION_COVERED_LAYER1 = "1-65:1.29.1" # Layer 1 sharing Group (AIR)
|
32
|
+
ELEC_CONSUMPTION_COVERED_LAYER2 = "1-65:1.29.3" # Layer 2 sharing Group (ACR/ACF/AC1)
|
33
|
+
ELEC_CONSUMPTION_COVERED_LAYER3 = "1-65:1.29.2" # Layer 3 sharing Group (CEL)
|
34
|
+
ELEC_CONSUMPTION_COVERED_LAYER4 = "1-65:1.29.4" # Layer 4 sharing Group (APS/CER/CEN)
|
35
|
+
ELEC_CONSUMPTION_REMAINING = (
|
36
|
+
"1-65:1.29.9" # Remaining consumption after sharing invoiced by supplier
|
37
|
+
)
|
36
38
|
|
37
|
-
|
38
|
-
""
|
39
|
-
|
40
|
-
ACTIVE = "1-1:2.29.0" # Measured active production (kW)
|
41
|
-
REACTIVE = "1-1:4.29.0" # Measured reactive production (kVAR)
|
39
|
+
# Electricity Production
|
40
|
+
ELEC_PRODUCTION_ACTIVE = "1-1:2.29.0" # Measured active production (kW)
|
41
|
+
ELEC_PRODUCTION_REACTIVE = "1-1:4.29.0" # Measured reactive production (kVAR)
|
42
42
|
|
43
43
|
# Production shared within sharing groups
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
ELEC_PRODUCTION_SHARED_LAYER1 = "1-65:2.29.1" # Layer 1 sharing Group (AIR)
|
45
|
+
ELEC_PRODUCTION_SHARED_LAYER2 = "1-65:2.29.3" # Layer 2 sharing Group (ACR/ACF/AC1)
|
46
|
+
ELEC_PRODUCTION_SHARED_LAYER3 = "1-65:2.29.2" # Layer 3 sharing Group (CEL)
|
47
|
+
ELEC_PRODUCTION_SHARED_LAYER4 = "1-65:2.29.4" # Layer 4 sharing Group (APS/CER/CEN)
|
48
|
+
ELEC_PRODUCTION_REMAINING = "1-65:2.29.9" # Remaining production after sharing sold to market
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
""
|
53
|
-
|
54
|
-
VOLUME = "7-1:99.23.15" # Measured consumed volume (m³)
|
55
|
-
STANDARD_VOLUME = "7-1:99.23.17" # Measured consumed standard volume (Nm³)
|
56
|
-
ENERGY = "7-20:99.33.17" # Measured consumed energy (kWh)
|
50
|
+
# Gas Consumption
|
51
|
+
GAS_CONSUMPTION_VOLUME = "7-1:99.23.15" # Measured consumed volume (m³)
|
52
|
+
GAS_CONSUMPTION_STANDARD_VOLUME = "7-1:99.23.17" # Measured consumed standard volume (Nm³)
|
53
|
+
GAS_CONSUMPTION_ENERGY = "7-20:99.33.17" # Measured consumed energy (kWh)
|
57
54
|
|
58
55
|
|
59
56
|
# Complete mapping of all OBIS codes to their details
|
60
57
|
OBIS_CODES: Dict[str, ObisCodeInfo] = {
|
61
58
|
# Electricity Consumption
|
62
|
-
|
63
|
-
|
59
|
+
ObisCode.ELEC_CONSUMPTION_ACTIVE: ObisCodeInfo(
|
60
|
+
ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
64
61
|
"kW",
|
65
62
|
"Consumption",
|
66
63
|
"Measured active consumption",
|
67
64
|
),
|
68
|
-
|
69
|
-
|
65
|
+
ObisCode.ELEC_CONSUMPTION_REACTIVE: ObisCodeInfo(
|
66
|
+
ObisCode.ELEC_CONSUMPTION_REACTIVE,
|
70
67
|
"kVAR",
|
71
68
|
"Consumption",
|
72
69
|
"Measured reactive consumption",
|
73
70
|
),
|
74
|
-
|
75
|
-
|
71
|
+
ObisCode.ELEC_CONSUMPTION_COVERED_LAYER1: ObisCodeInfo(
|
72
|
+
ObisCode.ELEC_CONSUMPTION_COVERED_LAYER1,
|
76
73
|
"kW",
|
77
74
|
"Consumption",
|
78
75
|
"Consumption covered by production of layer 1 sharing Group (AIR)",
|
79
76
|
),
|
80
|
-
|
81
|
-
|
77
|
+
ObisCode.ELEC_CONSUMPTION_COVERED_LAYER2: ObisCodeInfo(
|
78
|
+
ObisCode.ELEC_CONSUMPTION_COVERED_LAYER2,
|
82
79
|
"kW",
|
83
80
|
"Consumption",
|
84
81
|
"Consumption covered by production of layer 2 sharing Group (ACR/ACF/AC1)",
|
85
82
|
),
|
86
|
-
|
87
|
-
|
83
|
+
ObisCode.ELEC_CONSUMPTION_COVERED_LAYER3: ObisCodeInfo(
|
84
|
+
ObisCode.ELEC_CONSUMPTION_COVERED_LAYER3,
|
88
85
|
"kW",
|
89
86
|
"Consumption",
|
90
87
|
"Consumption covered by production of layer 3 sharing Group (CEL)",
|
91
88
|
),
|
92
|
-
|
93
|
-
|
89
|
+
ObisCode.ELEC_CONSUMPTION_COVERED_LAYER4: ObisCodeInfo(
|
90
|
+
ObisCode.ELEC_CONSUMPTION_COVERED_LAYER4,
|
94
91
|
"kW",
|
95
92
|
"Consumption",
|
96
93
|
"Consumption covered by production of layer 4 sharing Group (APS/CER/CEN)",
|
97
94
|
),
|
98
|
-
|
99
|
-
|
95
|
+
ObisCode.ELEC_CONSUMPTION_REMAINING: ObisCodeInfo(
|
96
|
+
ObisCode.ELEC_CONSUMPTION_REMAINING,
|
100
97
|
"kW",
|
101
98
|
"Consumption",
|
102
99
|
"Remaining consumption after sharing invoiced by supplier",
|
103
100
|
),
|
104
101
|
# Electricity Production
|
105
|
-
|
106
|
-
|
102
|
+
ObisCode.ELEC_PRODUCTION_ACTIVE: ObisCodeInfo(
|
103
|
+
ObisCode.ELEC_PRODUCTION_ACTIVE, "kW", "Production", "Measured active production"
|
107
104
|
),
|
108
|
-
|
109
|
-
|
105
|
+
ObisCode.ELEC_PRODUCTION_REACTIVE: ObisCodeInfo(
|
106
|
+
ObisCode.ELEC_PRODUCTION_REACTIVE,
|
110
107
|
"kVAR",
|
111
108
|
"Consumption",
|
112
109
|
"Measured reactive production",
|
113
110
|
),
|
114
|
-
|
115
|
-
|
111
|
+
ObisCode.ELEC_PRODUCTION_SHARED_LAYER1: ObisCodeInfo(
|
112
|
+
ObisCode.ELEC_PRODUCTION_SHARED_LAYER1,
|
116
113
|
"kW",
|
117
114
|
"Production",
|
118
115
|
"Production shared within layer 1 sharing Group (AIR)",
|
119
116
|
),
|
120
|
-
|
121
|
-
|
117
|
+
ObisCode.ELEC_PRODUCTION_SHARED_LAYER2: ObisCodeInfo(
|
118
|
+
ObisCode.ELEC_PRODUCTION_SHARED_LAYER2,
|
122
119
|
"kW",
|
123
120
|
"Production",
|
124
121
|
"Production shared within layer 2 sharing Group (ACR/ACF/AC1)",
|
125
122
|
),
|
126
|
-
|
127
|
-
|
123
|
+
ObisCode.ELEC_PRODUCTION_SHARED_LAYER3: ObisCodeInfo(
|
124
|
+
ObisCode.ELEC_PRODUCTION_SHARED_LAYER3,
|
128
125
|
"kW",
|
129
126
|
"Production",
|
130
127
|
"Production shared within layer 3 sharing Group (CEL)",
|
131
128
|
),
|
132
|
-
|
133
|
-
|
129
|
+
ObisCode.ELEC_PRODUCTION_SHARED_LAYER4: ObisCodeInfo(
|
130
|
+
ObisCode.ELEC_PRODUCTION_SHARED_LAYER4,
|
134
131
|
"kW",
|
135
132
|
"Production",
|
136
133
|
"Production shared within layer 4 sharing Group (APS/CER/CEN)",
|
137
134
|
),
|
138
|
-
|
139
|
-
|
135
|
+
ObisCode.ELEC_PRODUCTION_REMAINING: ObisCodeInfo(
|
136
|
+
ObisCode.ELEC_PRODUCTION_REMAINING,
|
140
137
|
"kW",
|
141
138
|
"Production",
|
142
139
|
"Remaining production after sharing sold to market",
|
143
140
|
),
|
144
141
|
# Gas Consumption
|
145
|
-
|
146
|
-
|
142
|
+
ObisCode.GAS_CONSUMPTION_VOLUME: ObisCodeInfo(
|
143
|
+
ObisCode.GAS_CONSUMPTION_VOLUME, "m³", "Consumption", "Measured consumed volume"
|
147
144
|
),
|
148
|
-
|
149
|
-
|
145
|
+
ObisCode.GAS_CONSUMPTION_STANDARD_VOLUME: ObisCodeInfo(
|
146
|
+
ObisCode.GAS_CONSUMPTION_STANDARD_VOLUME,
|
150
147
|
"Nm³",
|
151
148
|
"Consumption",
|
152
149
|
"Measured consumed standard volume",
|
153
150
|
),
|
154
|
-
|
155
|
-
|
151
|
+
ObisCode.GAS_CONSUMPTION_ENERGY: ObisCodeInfo(
|
152
|
+
ObisCode.GAS_CONSUMPTION_ENERGY, "kWh", "Consumption", "Measured consumed energy"
|
156
153
|
),
|
157
154
|
}
|
158
155
|
|
@@ -171,7 +168,7 @@ def get_obis_info(obis_code: str) -> ObisCodeInfo:
|
|
171
168
|
KeyError: If the OBIS code is not found
|
172
169
|
|
173
170
|
Example:
|
174
|
-
>>> info = get_obis_info(
|
171
|
+
>>> info = get_obis_info(ObisCode.ELEC_CONSUMPTION_ACTIVE)
|
175
172
|
>>> print(info.unit)
|
176
173
|
'kW'
|
177
174
|
"""
|
@@ -192,7 +189,7 @@ def get_unit(obis_code: str) -> str:
|
|
192
189
|
KeyError: If the OBIS code is not found
|
193
190
|
|
194
191
|
Example:
|
195
|
-
>>> unit = get_unit(
|
192
|
+
>>> unit = get_unit(ObisCode.ELEC_CONSUMPTION_ACTIVE)
|
196
193
|
>>> print(unit)
|
197
194
|
'kW'
|
198
195
|
"""
|
@@ -20,6 +20,7 @@ from src.leneda.models import (
|
|
20
20
|
MeteringData,
|
21
21
|
MeteringValue,
|
22
22
|
)
|
23
|
+
from src.leneda.obis_codes import ObisCode
|
23
24
|
|
24
25
|
|
25
26
|
class TestLenedaClient(unittest.TestCase):
|
@@ -34,7 +35,7 @@ class TestLenedaClient(unittest.TestCase):
|
|
34
35
|
# Sample response data
|
35
36
|
self.sample_metering_data = {
|
36
37
|
"meteringPointCode": "LU-METERING_POINT1",
|
37
|
-
"obisCode":
|
38
|
+
"obisCode": ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
38
39
|
"intervalLength": "PT15M",
|
39
40
|
"unit": "kWh",
|
40
41
|
"items": [
|
@@ -86,7 +87,7 @@ class TestLenedaClient(unittest.TestCase):
|
|
86
87
|
# Call the method
|
87
88
|
result = self.client.get_metering_data(
|
88
89
|
"LU-METERING_POINT1",
|
89
|
-
|
90
|
+
ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
90
91
|
"2023-01-01T00:00:00Z",
|
91
92
|
"2023-01-02T00:00:00Z",
|
92
93
|
)
|
@@ -94,7 +95,7 @@ class TestLenedaClient(unittest.TestCase):
|
|
94
95
|
# Check the result
|
95
96
|
self.assertIsInstance(result, MeteringData)
|
96
97
|
self.assertEqual(result.metering_point_code, "LU-METERING_POINT1")
|
97
|
-
self.assertEqual(result.obis_code,
|
98
|
+
self.assertEqual(result.obis_code, ObisCode.ELEC_CONSUMPTION_ACTIVE)
|
98
99
|
self.assertEqual(result.unit, "kWh")
|
99
100
|
self.assertEqual(len(result.items), 2)
|
100
101
|
|
@@ -116,7 +117,7 @@ class TestLenedaClient(unittest.TestCase):
|
|
116
117
|
"Content-Type": "application/json",
|
117
118
|
},
|
118
119
|
params={
|
119
|
-
"obisCode":
|
120
|
+
"obisCode": ObisCode.ELEC_CONSUMPTION_ACTIVE.value,
|
120
121
|
"startDateTime": "2023-01-01T00:00:00Z",
|
121
122
|
"endDateTime": "2023-01-02T00:00:00Z",
|
122
123
|
},
|
@@ -136,7 +137,7 @@ class TestLenedaClient(unittest.TestCase):
|
|
136
137
|
# Call the method
|
137
138
|
result = self.client.get_aggregated_metering_data(
|
138
139
|
"LU-METERING_POINT1",
|
139
|
-
|
140
|
+
ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
140
141
|
"2023-01-01",
|
141
142
|
"2023-01-31",
|
142
143
|
"Day",
|
@@ -171,7 +172,7 @@ class TestLenedaClient(unittest.TestCase):
|
|
171
172
|
"Content-Type": "application/json",
|
172
173
|
},
|
173
174
|
params={
|
174
|
-
"obisCode":
|
175
|
+
"obisCode": ObisCode.ELEC_CONSUMPTION_ACTIVE.value,
|
175
176
|
"startDate": "2023-01-01",
|
176
177
|
"endDate": "2023-01-31",
|
177
178
|
"aggregationLevel": "Day",
|
@@ -186,12 +187,20 @@ class TestLenedaClient(unittest.TestCase):
|
|
186
187
|
# Set up the mock response
|
187
188
|
mock_response = MagicMock()
|
188
189
|
mock_response.status_code = 200
|
189
|
-
mock_response.json.return_value =
|
190
|
-
mock_response.content = "{}"
|
190
|
+
mock_response.json.return_value = {"requestId": "test-request-id", "status": "PENDING"}
|
191
191
|
mock_request.return_value = mock_response
|
192
192
|
|
193
193
|
# Call the method
|
194
|
-
self.client.request_metering_data_access(
|
194
|
+
result = self.client.request_metering_data_access(
|
195
|
+
from_energy_id="test_energy_id",
|
196
|
+
from_name="Test User",
|
197
|
+
metering_point_codes=["LU-METERING_POINT1"],
|
198
|
+
obis_codes=[ObisCode.ELEC_CONSUMPTION_ACTIVE],
|
199
|
+
)
|
200
|
+
|
201
|
+
# Check the result
|
202
|
+
self.assertEqual(result["requestId"], "test-request-id")
|
203
|
+
self.assertEqual(result["status"], "PENDING")
|
195
204
|
|
196
205
|
# Check that the request was made correctly
|
197
206
|
mock_request.assert_called_once_with(
|
@@ -203,7 +212,12 @@ class TestLenedaClient(unittest.TestCase):
|
|
203
212
|
"Content-Type": "application/json",
|
204
213
|
},
|
205
214
|
params=None,
|
206
|
-
json={
|
215
|
+
json={
|
216
|
+
"from": "test_energy_id",
|
217
|
+
"fromName": "Test User",
|
218
|
+
"meteringPointCodes": ["LU-METERING_POINT1"],
|
219
|
+
"obisCodes": [ObisCode.ELEC_CONSUMPTION_ACTIVE.value],
|
220
|
+
},
|
207
221
|
)
|
208
222
|
|
209
223
|
@patch("requests.request")
|
@@ -216,7 +230,7 @@ class TestLenedaClient(unittest.TestCase):
|
|
216
230
|
with self.assertRaises(requests.exceptions.HTTPError):
|
217
231
|
self.client.get_metering_data(
|
218
232
|
"LU-METERING_POINT1",
|
219
|
-
|
233
|
+
ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
220
234
|
"2023-01-01T00:00:00Z",
|
221
235
|
"2023-01-02T00:00:00Z",
|
222
236
|
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|