reporting.rst 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. .. _reporting:
  2. =========
  3. Reporting
  4. =========
  5. Your VEN can provide periodic reports to the VTN. These reports usually contain metering data or status information.
  6. A brief overview of reports in OpenADR
  7. --------------------------------------
  8. Reports can describe many measurable things. A report description contains:
  9. - A ReportName, of which the following are predefined in OpenADR:
  10. - ``TELEMETRY_USAGE``: for providing meter reading or other instantanious values
  11. - ``TELEMETRY_STATUS``: for providing real-time status updates of availability
  12. - ``HISTORY_USAGE``: for providing a historic metering series
  13. - ``HISTORY_GREENBUTTON``: for providing historic data according to the GreenButton format
  14. - 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.
  15. - 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.
  16. - 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.
  17. - A so-called ItemBase, which indicates the quantity you are reporting, like Voltage, Current or Energy. In OpenLEADR, this is called ``measurement``.
  18. - A unit of measure, usually linked to the ``measurement``, like ``A`` for ampere, or ``V`` for volt.
  19. - A Resource ID, which indicates which of the resources this report applies to. It's probably one of your controllable devices.
  20. 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.
  21. Offering reports (VEN to VTN)
  22. -----------------------------
  23. Basic telemetry reports
  24. ~~~~~~~~~~~~~~~~~~~~~~~
  25. 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:
  26. .. code-block:: python3
  27. import openleadr
  28. from functools import partial
  29. async def main():
  30. client = openleadr.OpenADRClient()
  31. # Add reports
  32. client.add_report(callback=partial(read_current, device='Device001'),
  33. report_specifier_id='AmpereReport',
  34. resource_id='Device001',
  35. measurement='Current',
  36. sampling_rate=timedelta(seconds=10),
  37. unit='A')
  38. client.add_report(callback=partial(read_current, device='Device002'),
  39. report_specifier_id='AmpereReport',
  40. resource_id='Device002',
  41. measurement='Current',
  42. sampling_rate=timedelta(seconds=10),
  43. unit='A')
  44. client.add_report(callback=partial(read_voltage, device='Device001'),
  45. report_specifier_id='VoltageReport',
  46. resource_id='Device001',
  47. measurement='Voltage',
  48. sampling_rate=timedelta(seconds=10),
  49. unit='V')
  50. client.add_report(callback=partial(read_voltage, device='Device002'),
  51. report_specifier_id='VoltageReport',
  52. resource_id='Device002',
  53. measurement='Voltage',
  54. sampling_rate=timedelta(seconds=10),
  55. unit='V')
  56. await client.run()
  57. async def read_voltage(device):
  58. """
  59. Retrieve the voltage value from the given 'device'.
  60. """
  61. v = await interface.read(device, 'voltage') # Dummy code
  62. return v
  63. async def read_current(device):
  64. """
  65. Retrieve the current value from the given 'device'.
  66. """
  67. a = await interface.read(device, 'current') # Dummy code
  68. return a
  69. 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.
  70. 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.
  71. The two requirements for this kind of data collection are:
  72. 1. Your callback must accept arguments named ``date_from``, ``date_to`` and ``sampling_interval``
  73. 2. You must specify ``data_collection_mode='full'`` when adding the report.
  74. Here's an example:
  75. .. code-block:: python3
  76. import openleadr
  77. async def main():
  78. client = openleadr.OpenADRClient(ven_name='myven', vtn_url='http://some-vtn.com')
  79. client.add_report(callback=load_data,
  80. data_collection_mode='full',
  81. report_specifier_id='AmpereReport',
  82. resource_id='Device001',
  83. measurement='current',
  84. sampling_rate=timedelta(seconds=10),
  85. unit='A')
  86. async def load_data(date_from, date_to, sampling_rate):
  87. """
  88. Function that loads data between date_from and date_to, sampled at sampling_rate.
  89. """
  90. # Load data from a backend system
  91. result = await database.get("""SELECT time_bucket('15 minutes', datetime) as dt, AVG(value)
  92. FROM metervalues
  93. WHERE datetime BETWEEN %s AND %s
  94. GROUP BY dt
  95. ORDER BY dt""")
  96. # Pack the data into a list of (datetime, value) tuples
  97. data = result.fetchall()
  98. # data should look like:
  99. # [(datetime.datetime(2020, 1, 1, 12, 0, 0), 10.0),
  100. # (datetime.datetime(2020, 1, 1, 12, 15, 0), 9.0),
  101. # (datetime.datetime(2020, 1, 1, 12, 30, 0), 11.0),
  102. # (datetime.datetime(2020, 1, 1, 12, 45, 0), 12.0)]
  103. return data
  104. Historic data reports
  105. ~~~~~~~~~~~~~~~~~~~~~
  106. .. note::
  107. Historic reports are not yet implemented into OpenLEADR. Please follow updates in `this issue on GitHub <https://github.com/OpenLEADR/openleadr-python/issues/18>`_.
  108. 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:
  109. .. code-block:: python3
  110. async def get_historic_data(date_from, date_to, sampling_interval)
  111. An example for configuring historic reports:
  112. .. code-block:: python3
  113. import openleadr
  114. from functools import partial
  115. async def main():
  116. client = openleadr.OpenADRClient(ven_name='myven', vtn_url='http://some-vtn.com')
  117. client.add_report(callback=partial(get_historic_data, device_id='Device001'),
  118. report_name='HISTORY_USAGE',
  119. report_specifier_id='AmpereHistory',
  120. resource_id='Device001',
  121. measurement='current'
  122. sampling_rate=timedelta(seconds=10),
  123. unit='A')
  124. Note that you have to override the default ``report_name`` compared to the previous examples.
  125. Requesting Reports (VTN to VEN)
  126. -------------------------------
  127. 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.
  128. Using the compact format
  129. ~~~~~~~~~~~~~~~~~~~~~~~~
  130. 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:
  131. .. code-block:: python3
  132. async def on_register_report(ven_id, resource_id, measurement, unit, scale,
  133. min_sampling_interval, max_sampling_interval):
  134. if want_report:
  135. return (callback, sampling_interval, report_interval)
  136. else:
  137. return None
  138. The ``callback`` refers to a function or coroutine that will be called when data is received.
  139. The ``sampling_interval`` is a ``timedelta`` object that contains the interval at which data is sampled by the client.
  140. 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.
  141. This mechanism allows you to specify, for instance, that you want to receive 15-minute sampled values every 24 hours.
  142. For more information on the design of your callback function, see the :ref:`receiving_reports` section below.
  143. Using the full format
  144. ~~~~~~~~~~~~~~~~~~~~~
  145. If you want full control over the reporting specification, you implement an ``on_register_report`` handler with the following signature:
  146. .. code-block:: python3
  147. async def on_register_report(report):
  148. # For each report description (identified by their r_id)
  149. # you want to received, return a callback and sampling rate
  150. return [(callback_1, r_id_1, sampling_rate_1),
  151. (callback_2, r_id_2, sampling_rate_2)]
  152. The Report object that you have to inspect looks like this:
  153. .. code-block:: python3
  154. {'report_specifier_id': 'AmpereHistory',
  155. 'report_name': 'METADATA_TELEMETRY_USAGE',
  156. 'report_descriptions': [
  157. {'r_id': 'abc346-de6255-2345',
  158. 'report_type': 'READING',
  159. 'reading_type': 'DIRECT_READ',
  160. 'report_subject': {'resource_ids': ['Device001']},
  161. 'report_data_source': {'resource_ids': ['Device001']},
  162. 'sampling_rate': {'min_period': timedelta(seconds=10),
  163. 'max_period': timedelta(minutes=15),
  164. 'on_change': False},
  165. 'measurement': {'item_name': 'current',
  166. 'item_description': 'Current',
  167. 'item_units': 'A',
  168. 'si_scale_code': None},
  169. {'r_id': 'd2e352-126ae2-1723',
  170. 'report_type': 'READING',
  171. 'reading_type': 'DIRECT_READ',
  172. 'report_subject': {'resource_ids': ['Device002']},
  173. 'report_data_source': {'resource_ids': ['Device002']},
  174. 'sampling_rate': {'min_period': timedelta(seconds=10),
  175. 'max_period': timedelta(minutes=15),
  176. 'on_change': False},
  177. 'measurement': {'item_name': 'current',
  178. 'item_description': 'Current',
  179. 'item_units': 'A',
  180. 'si_scale_code': None}
  181. ]
  182. }
  183. .. 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.
  184. Your handler should read this, and make the following choices:
  185. - Which of these reports, specified by their ``r_id``, do I want?
  186. - At which sampling rate? In other words: how often should the data be sampled from the device?
  187. - At which reporting interval? In other words: how ofter should the collected data be packed up and sent to me?
  188. - Which callback should be called when I receive a new report?
  189. Your ``on_register_report`` handler thus looks something like this:
  190. .. code-block:: python3
  191. import openleadr
  192. async def store_data(data):
  193. """
  194. Function that stores data from the report.
  195. """
  196. async def on_register_report(resource_id, measurement, unit, scale, min_sampling_period, max_sampling_period):
  197. """
  198. This is called for every measurement that the VEN is offering as a telemetry report.
  199. """
  200. if measurement == 'Voltage':
  201. return store_data, min_sampling_period
  202. async def main():
  203. server = openleadr.OpenADRServer(vtn_id='myvtn')
  204. server.add_handler('on_register_report', on_register_report)
  205. Your ``store_data`` handler will be called with the contents of each report as it comes in.
  206. You have two options for receiving the data:
  207. 1. Receive the entire oadrReport dict that contains the values as we receive it.
  208. 2. Receive only the r_id and an iterable of ``(datetime.datetime, value)`` tuples for you to deal with.
  209. Delivering Reports (VEN to VTN)
  210. -------------------------------
  211. 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.
  212. Your callbacks should return either a single value, representing the most up-to-date reading,
  213. or a list of ``(datetime.datetime, value)`` tuples. This last type is useful when providing historic reports.
  214. This was already described in the previous section on this page.
  215. .. receiving_reports::
  216. Receiving Reports (VTN)
  217. -----------------------
  218. When the VEN delivers a report that you asked for, your handlers will be called to deal with it.
  219. Instead of giving you the full Report object, your handler will receive the iterable of ``(datetime.datetime, value)`` tuples.
  220. 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:
  221. .. code-block:: python3
  222. from functools import partial
  223. async def receive_data(data, resource_id, measurement):
  224. for timestamp, value in data:
  225. await database.execute("""INSERT INTO metervalues (resource_id, measurement, timestamp, value)
  226. VALUES (%s, %s, %s, %s)""", (resource_id, measurement, dt, value))
  227. async def on_register_report(resource_id, measurement, unit, scale, min_sampling_rate, max_sampling_rate):
  228. prepared_callback = partial(receive_data, resource_id=resource_id, measurement=measurement)
  229. return (callback, min_sampling_rate)
  230. The ``partial`` function creates a version of your callback with default parameters filled in.
  231. Identifying a data stream
  232. -------------------------
  233. Reports in OpenADR carry with them the following identifiers:
  234. - ``reportSpecifierID``: the id that the VEN assigns to this report
  235. - ``rID``: the id that the VEN assigns to a specific data stream in the report
  236. - ``reportRequestID``: the id that the VTN assigns to its request of a report
  237. - ``reportID``: the id that the VEN assigns to a single copy of a report
  238. 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``.