1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
|
.. _reporting:
=========
Reporting
=========
Your VEN can provide periodic reports to the VTN. These reports usually contain metering data or status information.
A brief overview of reports in OpenADR
--------------------------------------
Reports can describe many measurable things. A report description contains:
- A ReportName, of which the following are predefined in OpenADR:
- ``TELEMETRY_USAGE``: for providing meter reading or other instantanious values
- ``TELEMETRY_STATUS``: for providing real-time status updates of availability
- ``HISTORY_USAGE``: for providing a historic metering series
- ``HISTORY_GREENBUTTON``: for providing historic data according to the GreenButton format
- A ReportType, which indicates what the data represents. There are many options predefined, like ``READING``, ``USAGE``, ``DEMAND``, ``STORED_ENERGY``, et cetera. You can find all of them in the ``enums.REPORT_TYPE`` enumeration.
- A ReadingType, which indicates the way in which the data is collected or possibly downsampled. For instance, ``DIRECT_READ``, ``NET``, ``ALLOCATED``, ``SUMMED``, ``MEAN``, ``PEAK``, et cetera.
- A Sampling Rate, which defines the rate at which data can or should be collected. When configuring / offering reports from the VEN, you can set a minimum and maximum sampling rate if you wish, to let the VTN choose its preferred sampling rate.
- A so-called ItemBase, which indicates the quantity you are reporting, like Voltage, Current or Energy. In OpenLEADR, this is called ``measurement``.
- A unit of measure, usually linked to the ``measurement``, like ``A`` for ampere, or ``V`` for volt.
- A Resource ID, which indicates which of the resources this report applies to. It's probably one of your controllable devices.
You configure which reports you can deliver, and the VEN offers these to the VTN. The VTN makes a selection and requests reports from the VEN to be sent at regular intervals. The VEN then starts collecting data and sending reports.
Offering reports (VEN to VTN)
-----------------------------
Basic telemetry reports
~~~~~~~~~~~~~~~~~~~~~~~
Say you have two devices, 'Device001' and 'Device002', which can each offer measurments of Voltage and Current at a samplerate of 10 seconds. You would register these reports as follows:
.. code-block:: python3
import openleadr
from functools import partial
async def main():
client = openleadr.OpenADRClient()
# Add reports
client.add_report(callback=partial(read_current, device='Device001'),
report_specifier_id='AmpereReport',
resource_id='Device001',
measurement='Current',
sampling_rate=timedelta(seconds=10),
unit='A')
client.add_report(callback=partial(read_current, device='Device002'),
report_specifier_id='AmpereReport',
resource_id='Device002',
measurement='Current',
sampling_rate=timedelta(seconds=10),
unit='A')
client.add_report(callback=partial(read_voltage, device='Device001'),
report_specifier_id='VoltageReport',
resource_id='Device001',
measurement='Voltage',
sampling_rate=timedelta(seconds=10),
unit='V')
client.add_report(callback=partial(read_voltage, device='Device002'),
report_specifier_id='VoltageReport',
resource_id='Device002',
measurement='Voltage',
sampling_rate=timedelta(seconds=10),
unit='V')
await client.run()
async def read_voltage(device):
"""
Retrieve the voltage value from the given 'device'.
"""
v = await interface.read(device, 'voltage') # Dummy code
return v
async def read_current(device):
"""
Retrieve the current value from the given 'device'.
"""
a = await interface.read(device, 'current') # Dummy code
return a
The VTN can request TELEMETRY_USAGE reports to be delivered at the sampling rate, or it can request them at a lower rate. For instance, it can request 15-minute readings, delivered every 24 hours. By default, openLEADR handles this case by incrementally building up the report during the day, calling your callback every 15 minutes and sending the report once it has 24 hours worth of values.
If, instead, you already have a system where historic data can be extracted, and prefer to use that method instead, you can configure that as well.
The two requirements for this kind of data collection are:
1. Your callback must accept arguments named ``date_from``, ``date_to`` and ``sampling_interval``
2. You must specify ``data_collection_mode='full'`` when adding the report.
Here's an example:
.. code-block:: python3
import openleadr
async def main():
client = openleadr.OpenADRClient(ven_name='myven', vtn_url='http://some-vtn.com')
client.add_report(callback=load_data,
data_collection_mode='full',
report_specifier_id='AmpereReport',
resource_id='Device001',
measurement='current',
sampling_rate=timedelta(seconds=10),
unit='A')
async def load_data(date_from, date_to, sampling_interval):
"""
Function that loads data between date_from and date_to, sampled at sampling_interval.
"""
# Load data from a backend system
result = await database.get("""SELECT time_bucket('15 minutes', datetime) as dt, AVG(value)
FROM metervalues
WHERE datetime BETWEEN %s AND %s
GROUP BY dt
ORDER BY dt""")
# Pack the data into a list of (datetime, value) tuples
data = result.fetchall()
# data should look like:
# [(datetime.datetime(2020, 1, 1, 12, 0, 0), 10.0),
# (datetime.datetime(2020, 1, 1, 12, 15, 0), 9.0),
# (datetime.datetime(2020, 1, 1, 12, 30, 0), 11.0),
# (datetime.datetime(2020, 1, 1, 12, 45, 0), 12.0)]
return data
Historic data reports
~~~~~~~~~~~~~~~~~~~~~
.. note::
Historic reports are not yet implemented into OpenLEADR. Please follow updates in `this issue on GitHub <https://github.com/OpenLEADR/openleadr-python/issues/18>`_.
You can also configure historic reports, where the VTN can at any time request data from a specified time interval and granularity. For historic reports, you must have your own data collection system and the provided callback must have the signature:
.. code-block:: python3
async def get_historic_data(date_from, date_to, sampling_interval)
An example for configuring historic reports:
.. code-block:: python3
import openleadr
from functools import partial
async def main():
client = openleadr.OpenADRClient(ven_name='myven', vtn_url='http://some-vtn.com')
client.add_report(callback=partial(get_historic_data, device_id='Device001'),
report_name='HISTORY_USAGE',
report_specifier_id='AmpereHistory',
resource_id='Device001',
measurement='current'
sampling_rate=timedelta(seconds=10),
unit='A')
Note that you have to override the default ``report_name`` compared to the previous examples.
Requesting Reports (VTN to VEN)
-------------------------------
The VTN will receive an ``oadrRegisterReport`` message. Your handler ``on_register_report`` will be called for each report that is offered. You inspect the report description and decide which elements from the report you wish to receive.
Using the compact format
~~~~~~~~~~~~~~~~~~~~~~~~
The compact format provides an abstraction over the actual encapsulation of reports. If your ``on_register_report`` handler has the following signature, if will be called using the simple format:
.. code-block:: python3
async def on_register_report(ven_id, resource_id, measurement, unit, scale,
min_sampling_interval, max_sampling_interval):
if want_report:
return (callback, sampling_interval, report_interval)
else:
return None
The ``callback`` refers to a function or coroutine that will be called when data is received.
The ``sampling_interval`` is a ``timedelta`` object that contains the interval at which data is sampled by the client.
The ``report_interval`` is optional, and contains a ``timedelta`` object that indicates how often you want to receive a report. If you don't specify a ``report_interval``, you will receive each report immediately after it is sampled.
This mechanism allows you to specify, for instance, that you want to receive 15-minute sampled values every 24 hours.
For more information on the design of your callback function, see the :ref:`receiving_reports` section below.
Using the full format
~~~~~~~~~~~~~~~~~~~~~
If you want full control over the reporting specification, you implement an ``on_register_report`` handler with the following signature:
.. code-block:: python3
async def on_register_report(report):
# For each report description (identified by their r_id)
# you want to received, return a callback and sampling rate
return [(callback_1, r_id_1, sampling_rate_1),
(callback_2, r_id_2, sampling_rate_2)]
The Report object that you have to inspect looks like this:
.. code-block:: python3
{'report_specifier_id': 'AmpereHistory',
'report_name': 'METADATA_TELEMETRY_USAGE',
'report_descriptions': [
{'r_id': 'abc346-de6255-2345',
'report_type': 'READING',
'reading_type': 'DIRECT_READ',
'report_subject': {'resource_ids': ['Device001']},
'report_data_source': {'resource_ids': ['Device001']},
'sampling_rate': {'min_period': timedelta(seconds=10),
'max_period': timedelta(minutes=15),
'on_change': False},
'measurement': {'item_name': 'current',
'item_description': 'Current',
'item_units': 'A',
'si_scale_code': None},
{'r_id': 'd2e352-126ae2-1723',
'report_type': 'READING',
'reading_type': 'DIRECT_READ',
'report_subject': {'resource_ids': ['Device002']},
'report_data_source': {'resource_ids': ['Device002']},
'sampling_rate': {'min_period': timedelta(seconds=10),
'max_period': timedelta(minutes=15),
'on_change': False},
'measurement': {'item_name': 'current',
'item_description': 'Current',
'item_units': 'A',
'si_scale_code': None}
]
}
.. note:: The ``report_name`` property of a report gets prefixed with ``METADATA_`` during the ``register_report`` step. This indicates that it is a report without any data. Once you get the actual reports, the ``METADATA_`` prefix will not be there.
Your handler should read this, and make the following choices:
- Which of these reports, specified by their ``r_id``, do I want?
- At which sampling rate? In other words: how often should the data be sampled from the device?
- At which reporting interval? In other words: how ofter should the collected data be packed up and sent to me?
- Which callback should be called when I receive a new report?
Your ``on_register_report`` handler thus looks something like this:
.. code-block:: python3
import openleadr
async def store_data(data):
"""
Function that stores data from the report.
"""
async def on_register_report(resource_id, measurement, unit, scale, min_sampling_period, max_sampling_period):
"""
This is called for every measurement that the VEN is offering as a telemetry report.
"""
if measurement == 'Voltage':
return store_data, min_sampling_period
async def main():
server = openleadr.OpenADRServer(vtn_id='myvtn')
server.add_handler('on_register_report', on_register_report)
Your ``store_data`` handler will be called with the contents of each report as it comes in.
You have two options for receiving the data:
1. Receive the entire oadrReport dict that contains the values as we receive it.
2. Receive only the r_id and an iterable of ``(datetime.datetime, value)`` tuples for you to deal with.
Delivering Reports (VEN to VTN)
-------------------------------
Report values will be automatically collected by running your provided callbacks. They are automatically packed up and sent to the VTN at the requested interval.
Your callbacks should return either a single value, representing the most up-to-date reading,
or a list of ``(datetime.datetime, value)`` tuples. This last type is useful when providing historic reports.
This was already described in the previous section on this page.
.. receiving_reports::
Receiving Reports (VTN)
-----------------------
When the VEN delivers a report that you asked for, your handlers will be called to deal with it.
Instead of giving you the full Report object, your handler will receive the iterable of ``(datetime.datetime, value)`` tuples.
If your callback needs to know other metadata properties at runtime, you should add those as default arguments during the request report phase. For instance:
.. code-block:: python3
from functools import partial
async def receive_data(data, resource_id, measurement):
for timestamp, value in data:
await database.execute("""INSERT INTO metervalues (resource_id, measurement, timestamp, value)
VALUES (%s, %s, %s, %s)""", (resource_id, measurement, dt, value))
async def on_register_report(resource_id, measurement, unit, scale, min_sampling_rate, max_sampling_rate):
prepared_callback = partial(receive_data, resource_id=resource_id, measurement=measurement)
return (callback, min_sampling_rate)
The ``partial`` function creates a version of your callback with default parameters filled in.
Identifying a data stream
-------------------------
Reports in OpenADR carry with them the following identifiers:
- ``reportSpecifierID``: the id that the VEN assigns to this report
- ``rID``: the id that the VEN assigns to a specific data stream in the report
- ``reportRequestID``: the id that the VTN assigns to its request of a report
- ``reportID``: the id that the VEN assigns to a single copy of a report
The ``rID`` is the most specific identifier of a data stream. The ``rID`` is part of the ``oadrReportDescription``, along with information like the measurement type, unit, and the ``resourceID``.
|