The Records API (application program interface) for Advanced Energy System Design (AESD) enables software that serves multidimensional record-oriented data to interoperate with software than uses such data. In the context of the Records API, multidimensional data records are simply tuples of real numbers, integers, and character strings, where each data value is tagged by a variable name, according to a pre-defined schema, and each record is assigned a unique integer identifier. Conceptually, these records are isomorphic to rows in a relational database, JSON objects, or key-value maps. Records servers might supply static datasets, sensor measurements that periodically update as new telemetry become available, or the results of simulations as the simulations generate new output. Records client software might display or analyze the data, but in the case of simulations the client request the creation of new ensembles for specified input parameters. It is also possible to chain records clients and servers together so that a client consuming data from a server might transform that data and serve it to additional clients.
This minimalist API avoids imposing burdensome metadata, structural, or implementation requirements on developers by relying on open-source technologies that are readily available for common programming languages. In particular, the API has been designed to place the least possible burden on services that provide data. This document defines the message format for the Records API, a transport mechanism for communicating the data, and the semantics for interpreting it. The message format is specified as Google Protocol Buffers (Google Developers 2017b) and the transport mechanism uses WebSockets (Internet Engineering Task Force 2017). We discuss three major use cases for serving and consuming records data: (i) static data, (ii) dynamically augmented data, (iii) on-demand simulations, (iv) with filters, and (v) with bookmarks. Separate implementations of the API exist in C++, Haskell, JavaScript, Python, and R.
Client-server communication in the Records API simply consists of clients sending Request
messages to the server and servers asynchronously sending Response
messages to the client. The request and response messages hold the specifics of the request or response and the responses are correlated with the requests; however, it is important to note that multiple responses may occur for a single request, as when record data are chunked into multiple response, or that an error response may be sent at any time. The nested messages within Request
and Response
may in turn contain nested fields and messages providing further details. The table below shows the correspondence between requests and responses, while the figure following that shows the containment relationships between message types.
Request Field | Response Field |
---|---|
models_metadata |
models or error |
records_data |
data or error |
bookmark_meta |
bookmarks or error |
save_bookmark |
bookmarks or error |
cancel |
no response or error |
work |
data or error |
Containment relationships between protocol buffer messages in the Records API.
Metadata messages describe “models”, which are just sources of data, and the variables they contain. Data record messages hold the data itself. Data records are simply tuples of real numbers, integers, and character strings, where each data value is tagged by a variable name, according to a pre-defined schema, and each record is assigned a unique integer identifier. Conceptually, these records are isomorphic to rows in a relational database, JSON objects, or key-value maps. For efficiency and compactness, RecordData
may be provided in list format or tabular format, with the latter format obtained only when the contents of the table all have the same data type. The data records may be provided in toto or filtered using filter messages so that only certain fields or records are returned. The API contains a small embedded language for filtering via set and value operations. Sets of records may be bookmarked for sharing or later retrieval by (i) enumerating their unique record identifiers, (ii) defining a range of unique record identifiers, or (iii) specifying a filtering criterion.
Servers that perform computations or simulations can receive input parameters via a RequestWork
message that contains those input parameters. After the server has completed its computations, it sends the results as RecordData
messages.
In general the response to a request for data records comes in chunks numbered in sequence, where each chunk has an identifier, chunk_id
, and the response specifies the identifier of the next chunk, next_chunk_id
. Thus, the chunks form a linked list. The sending of additional chunks can be cancelled using a RequestCancel
message. If the subscribe
flag is set when making a request, the server will respond indefinitely with additional data as it becomes available, until the subscription is cancelled.
In this section we outline some standard use cases for the Records API. UML Sequence Diagrams (Fowler 2017) illustrate the flow of messages and the messages themselves are printed in the text format output by the Google protoc
tool (Google Developers 2017a).
The retrieval of static data records forms the simplest use case for the Records API. A user chooses a particular data source (a “model” in the parlance of the Records API) and then the data are retrieved and displayed. The visualization client software communicates with a Records server, which in turn accesses the static data. The figure below illustrates the process.
Visualizing data from a static source using the Records API.
A Request
without model_id
specified requests the server to list all models:
version: 4
id: 1
models_metadata {
}
The Response
from the server provides metadata for all of the models:
version: 4
id: 1
models {
models {
model_id: "example-model-1"
model_name: "Example Model #1"
model_uri: "http://esda.nrel.gov/examples/model-1"
variables {
var_id: 0
var_name: "Example Real Variable"
type: REAL
}
variables {
var_id: 1
var_name: "Example Integer Variable"
type: INTEGER
}
variables {
var_id: 2
var_name: "Example String Variable"
type: STRING
}
models {
model_id: "example-model-2"
model_name: "Example Model #2"
model_uri: "http://esda.nrel.gov/examples/model-2"
variables {
var_id: 0
var_name: "POSIX Epoch"
type: INTEGER
}
variables {
var_id: 1
var_name: "Measurement"
type: REAL
}
}
models {
model_id: "example-simulation-3"
model_name: "Example Simulation #3"
model_uri: "http://esda.nrel.gov/examples/simulation-3"
variables {
var_id: 0
var_name: "Input"
type: REAL
}
variables {
var_id: 1
var_name: "Time"
type: REAL
}
variables {
var_id: 2
var_name: "Value"
type: REAL
}
inputs {
var_id: 0
interval {
first_value: 0
second_value: 100
}
}
}
}
Note that the response above is tagged with the same id
as the request: this allows the client to correlate responses with the particular requests it makes. Next the user might request three records from the first model:
version: 4
id: 2
records_data {
model_id: "example-model-1"
max_records: 3
}
The record data might be returned as two chunks, where the first chunk is
version: 4
id: 2
chunk_id: 1
next_chunk_id: 2
data {
list {
records {
record_id: 10
variables {
var_id: 0
value: 10.5
}
variables {
var_id: 1
value: -5
}
variables {
var_id: 2
value: "first"
}
}
records {
record_id: 20
variables {
var_id: 0
value: 99.2
}
variables {
var_id: 1
value: 108
}
variables {
var_id: 2
value: "second"
}
}
}
}
and the last chunk is:
version: 4
id: 2
chunk_id: 2
next_chunk_id: 0
data {
list {
records {
record_id: 30
variables {
var_id: 0
value: -15.7
}
variables {
var_id: 1
value: 30
}
variables {
var_id: 2
value: "third"
}
}
}
}
As shown in the following figure retrieving data from a dynamic source proceeds quite similarly to retrieving data from a static source. The only essential difference is that the server repeatedly sends additional responses containing new data, until a request to cancel is sent.
Visualizing data from a dynamic source using the Records API.
When requesting dynamic data, it is advisable to set the subscribe
flag in the request for data:
version: 4
id: 2
subscribe: true
records_data {
model_id: "example-model-2"
}
The RequestCancel
message is the cancel
field Request
and must include the id
of the request to be cancelled:
version: 4
cancel {
id: 2
}
The model Example Simulation #3
in the Static Data use case is a simulation model, as evidenced by the presence of the inputs
field in its metadata. The following figure shows a typical interaction with a simulation-based model via the Records API.
Steering and visualizing simulation results using the Records API.
The RequestWork
message, which is contained in the work
field of a Request
, specifies the input for a simulation to be run:
version: 4
id: 3
work {
model_id: "example-simulation-3"
inputs {
var_id: 0
value: 50
}
}
The response to this message will be data for the result of the simulation.
Once data from a model is loaded, it may be bookmarked. One simply supplies a description of the data to be bookmarked. Bookmarks can be listed and loaded, as shown in the following figure.
Creating and retrieving a bookmark and its associated data.
To create a bookmark for a specific list of records, simply supply their record identifiers as part of a BookmarkMeta
message in the save_bookmark
field of Request
:
version: 4
id: 4
save_bookmark {
model_id: "example-model-1"
new_bookmark {
bookmark_name: "Sample Bookmark"
set {
record_ids: 10
record_ids: 30
}
}
}
The response will be the same bookmark, but with the bookmark_id
field added:
version: 4
id: 4
bookmarks {
bookmark_metas {
bookmark_id: "bookmark-1"
bookmark_name: "Sample Bookmark"
set {
record_ids: 10
record_ids: 30
}
}
}
The user or another user can retrieve the records corresponding to the bookmark:
version: 4
id: 5
records_data {
model_id: "example-model-1"
bookmark_id: "bookmark-1"
}
This will return precisely the bookmarked records:
version: 4
id: 5
data {
list {
records {
record_id: 10
variables {
var_id: 0
value: 10.5
}
variables {
var_id: 1
value: -5
}
variables {
var_id: 2
value: "first"
}
}
records {
record_id: 30
variables {
var_id: 0
value: -15.7
}
variables {
var_id: 1
value: 30
}
variables {
var_id: 2
value: "third"
}
}
}
}
Filtering records can be used to select particular records for retrieval, via the RequestRecordsData
message, or in defining bookmarks, via the BookmarkMeta
message. Filtering of records is accomplished through expressions, FilterExpression
, combining values for variables, DomainMeta
, and the set operators not, union, and intersection, encoded in the messages FilterNot
, FilterUnion
, and FitlerIntersection
, respectively. For example, the expression x ≤ 20 would be expressed as the following FilterExpression
filter_domain {
interval {
var_id: 0
last_value: 20
}
}
provided that x has var_id = 0
. The expression (10 ≤ x ≤ 20)∪(y ∉ {4, 7}) would be expressed as
filter_union {
filter_expressions {
filter_domain {
var_id: 0
first_value: 10
last_value: 20
}
filter_not {
filter_expression {
filter_domain {
var_id: 1
set {
elements: 4
elements: 7
}
}
}
}
}
provided that x has var_id = 0
and y has var_id = 1
.
The AESD Records API consists of Google Protobuf 3 (Google Developers 2017b) messages used to request and provid data and metadata for record-oriented information. This section contains the complete specification for version 4 of the Records API. Clients send Request
messages and servers send Response
messages, typically transported via WebSockets (Internet Engineering Task Force 2017).
The message types in the Records API are organized into thematic groups below.
Request
messages are sent from client to server and Response
messages are sent from server to client. Request messages contain a specific type of request and response messages contain a corresponding specific type of response.
Metadata messages describe data sources (“models”) and variables.
Data are represented as either lists of records or tables of them.
Records can be filtered by logical operations on conditions for values of variables in the records.
Bookmarks record particular sets or records or conditions for record data.
The following messages wrap data types for the content of records.
All fields are technically optional in ProtoBuf 3, but some fields may be required in each message type in order for the message to be semantically valid. In the following specifications for the messages, fields are annotated as semantically required or semantically optional. Also, the specification notes when field in the protobuf oneof
construct are required or mutually exclusive.
Furthermore, one cannot determine whether an optional value has been set or not if it is just a value, as opposed to a message. That is not true for fields that are messages, where the absence of the field truly indicates that the value is absent, not just a default or unset value. The message OptionalString
, for example, is used in this API to indicate whether a character string value is truly present. Thus RequestModelsMeta
has a model_id
field that indicates whether the request is for all models, when the field has not been set, or for a specific one, when the field has been set.
Throughout this specification, the following types are used for identifiers: * var_id
is int32 * model_id
is string * record_id
is int64
This specification conforms to Protocol Buffers version 3.
A range of record identifiers can specify the content of a bookmark. Bookmark interval content provides a convenient means to bookmark a contiguous selection of records in a model.
Both fields in this message are optional:
first_record
is present, the bookmark interval designates all records starting from that record identifier.last_record
is present, the bookmark interval designates all records ending at that record identifier. For a dynamic model, such a bookmark interval includes all “future” records.Field | Type | Label | Description |
---|---|---|---|
first_record | int64 | optional | [semantically optional] The identifier for the first record in the interval. |
last_record | int64 | optional | [semantically optional] The identifier for the last record in the interval. |
A bookmark is metadata defining a subset of records in a model.
There are three alternatives to specifying a bookmark:
Exactly one of interval
, set
, or filter
must be specified in this message.
Field | Type | Label | Description |
---|---|---|---|
bookmark_id | string | optional | [semantically optional] When creating a new bookmark, this field must be empty: the server will create a unique identifier for the bookmark. This identifier uniquely identifies the bookmark on the particular server. |
bookmark_name | string | optional | [semantically required] A name for the bookmark, which is useful for displaying the bookmark to users. This need not be unique, although it is recommended to be so. |
interval | BookmarkIntervalContent | optional | The range of records in the bookmark. |
set | BookmarkSetContent | optional | The list of records in the bookmark. |
filter | FilterExpression | optional | Logical conditions for defining which records are in the bookmark. |
Bookmarks may be grouped into lists (sets).
Field | Type | Label | Description |
---|---|---|---|
bookmark_metas | BookmarkMeta | repeated | [semantically optional] The bookmarks in the list. |
A list (set) of record identifiers can specify the contents of a bookmark. Bookmark-set content provides a convenient means to bookmark a specific selection of non-continuous records in a model.
Field | Type | Label | Description |
---|---|---|---|
record_ids | int64 | repeated | [semantically optional] The list of record identifiers in the set. |
The domain (set of valid values) for a variable.
There are two alternatives to specifying a domain:
Exactly one of interval
or set
must be specified in the message.
Field | Type | Label | Description |
---|---|---|---|
var_id | int32 | optional | [semantically required] |
interval | VarInterval | optional | The interval of values in the domain. |
set | VarSet | optional | The list of values in the domain. |
A list of real numbers.
Field | Type | Label | Description |
---|---|---|---|
values | double | repeated | [semantically required] The real numbers. |
A filtering expression is a composition of logical conditions on a record. It can be used to filter records. There are four alternatives to specifying a filter expression:
Exactly one of filter_not
, filter_union
, filter_intersection
, or filter_domain
must be specified in this message.
Field | Type | Label | Description |
---|---|---|---|
filter_not | FilterNot | optional | Logical negation of an expression. |
filter_union | FilterUnion | optional | Set union of expressions. |
filter_intersection | FilterIntersection | optional | Set intersection of expressions. |
filter_domain | DomainMeta | optional | Particular values of variables. |
Set intersection of filtering expressions. A record satisfies this expression if it satisfies all filter_expressions
.
Field | Type | Label | Description |
---|---|---|---|
filter_expressions | FilterExpression | repeated | [semantically required] The expressions to be intersected. |
Logically negate a filtering expression. A record satisfies this expression if it does not satisfy filter_expression
.
Field | Type | Label | Description |
---|---|---|---|
filter_expression | FilterExpression | optional | [semantically required] The expression to be negated. |
Set union of filtering expressions. A record satisfies this expression if it satisfies any of filter_expressions
.
Field | Type | Label | Description |
---|---|---|---|
filter_expressions | FilterExpression | repeated | [semantically required] The expressions to be “unioned”. |
A list of integers.
Field | Type | Label | Description |
---|---|---|---|
values | sint64 | repeated | [semantically required] The integers. |
Metadata for a model.
Field | Type | Label | Description |
---|---|---|---|
model_id | string | optional | [semantically required] The unique identifier for the model on the particular server. |
model_name | string | optional | [semantically required] A name for the model, useful for display the model to users. This need not be unique, although it is recommended to be so. |
model_uri | string | optional | [semantically required] The unique URI for the model. Additional metadata may be obtained by dereferencing that URI. |
variables | VarMeta | repeated | [semantically required] Metadata for the variables. |
inputs | DomainMeta | repeated | [semantically optional] Metadata for input values to the model, if any. |
A list of metadata for models.
Field | Type | Label | Description |
---|---|---|---|
models | ModelMeta | repeated | [semantically optional] The metadata for the models. |
Wrapper for an optional signed integer.
Field | Type | Label | Description |
---|---|---|---|
value | int32 | optional | [semantically required] The signed integer value. |
Wrapper for an optional string.
Field | Type | Label | Description |
---|---|---|---|
value | string | optional | [semantically required] The character string value. |
Wrapper for an optional unsigned integer.
Field | Type | Label | Description |
---|---|---|---|
value | uint32 | optional | [semantically required] The unsigned integer value. |
A record is a list of variables and their associated values.
Field | Type | Label | Description |
---|---|---|---|
record_id | int64 | optional | [semantically required] A unique identifier for the record. |
variables | VarValue | repeated | [semantically optional] The values for variables in the record. |
A collection of records.
There are two alternatives to specifying record data:
Exactly one of list
or table
must be present in the message.
Field | Type | Label | Description |
---|---|---|---|
list | RecordList | optional | A heterogeneously typed list of records. |
table | RecordTable | optional | A homogeneously typed table of records. |
A list of records. The list is heterogeneous in the sense that each variable may have a different type.
Field | Type | Label | Description |
---|---|---|---|
records | Record | repeated | [semantically optional] The list of records. |
A homogeneously typed table of records, where each variable has each type, with a row for each record and a column for each variable.
This message represents the following table:
Record Identifier | var_id[0] |
var_id[1] |
. . . | var_id[N] |
---|---|---|---|---|
rec_id[0] |
list[0][0] |
list[0][1] |
. . . | list[0][N] |
rec_id[1] |
list[1][0] |
list[1][1] |
. . . | list[1][N] |
. . . | . . . | . . . | . . . | . . . |
rec_id[M] |
list[M][0] |
list[M][1] |
. . . | list[M][N] |
The underlying list is a single array, addressable using the following row-major index formula list[row][var] = array[var + NY * row] where NX
= length of rec_ids
and NY
= length of var_ids
.
Exacly one of reals
, integers
, or strings
must be specified in the message.
Field | Type | Label | Description |
---|---|---|---|
var_ids | int32 | repeated | [semantically required] The identifiers of the variables (columns) in the table. |
rec_ids | int64 | repeated | [semantically required] The identifiers of the records (rows) in the table. |
reals | DoubleList | optional | The real numbers comprising the values of the variables, in row-major order. |
integers | IntegerList | optional | The integers comprising the values of the variables, in row-major order. |
strings | StringList | optional | The character strings comprising the values of the variables, in row-major order. |
A request. There are six types of requests:
Request | Response |
---|---|
Metadata for model(s) | ModelMetaList |
Data records | RecordData |
Metadata for bookmark(s) | BookmarkMetaList |
Saving a bookmark | BookmarkMetaList |
Canceling a previous request | n/a |
New work, such as a simulation | RecordData |
*Exactly one of models_metadata
, records_data
, bookmark_meta
, save_bookmark
, cancel
, or work
must be specified in the message.
Field | Type | Label | Description |
---|---|---|---|
version | uint32 | optional | [semantically required] The version number for the API. This must be the number four. |
id | OptionalUInt32 | optional | [semantically optional, but recommended] An identifier that will be used to tag responses, so that responses can be correlated with requests. |
subscribe | bool | optional | [semantically optional] Whether to continue receiving responses indefinitely, as new records become available. This is useful, for example, when a sensor is reporting measurements periodically or when simulations are reporting a series or results. Use RequestCancel to end the subscription. |
models_metadata | RequestModelsMeta | optional | Request metadata for model(s). |
records_data | RequestRecordsData | optional | Request data records. |
bookmark_meta | RequestBookmarkMeta | optional | Request metadata for bookmark(s). |
save_bookmark | RequestSaveBookmark | optional | Request save a new bookmark or update an existing one. |
cancel | RequestCancel | optional | Request cancel a previous request). |
work | RequestWork | optional | Request work (e.g., simulation results). |
A request for one or more bookmarks for a model.
The response to this request is BookmarkMetaList
Field | Type | Label | Description |
---|---|---|---|
model_id | string | optional | [semantically required] Which model for which to list bookmarks. |
bookmark_id | OptionalString | optional | [semantically optional] If empty, list all bookmarks for the model. Otherwise, list just the bookmark metadata for this specific bookmark identifier. |
Cancel a previous request.
Field | Type | Label | Description |
---|---|---|---|
id | OptionalUInt32 | optional | [semantically required] Which request to cancel. |
A request for metadata about model(s).
The response to this request is ModelMetaList.
Field | Type | Label | Description |
---|---|---|---|
model_id | OptionalString | optional | [semantically optional] If absent, the request is for metadata for all models. Otherwise the request is for the specifically identified model. |
Request record data for a model.
There are three alternatives to requesting record data.
The response to this request is RecordData.
No more than on of bookmark_id
or expression
may be present in the message.
Field | Type | Label | Description |
---|---|---|---|
model_id | string | optional | [semantically required] The identifier for the model. |
max_records | uint64 | optional | [semantically optional] If specified, this is the maximum number of records to return. Otherwise all records are returned, although they may be returned as multiple responses, each with a chunk of records. |
var_ids | int32 | repeated | [semantically optional] Which variables to include in the response. If this is not specified, all variables will be included. |
bookmark_id | string | optional | [semantically optional] Only respond with records in a specified bookmark. |
expression | FilterExpression | optional | [semantically optional] Only respond with records matching a specified criterion. |
A request to create or update a bookmark.
The response to this request is BookmarkMetaList.
Field | Type | Label | Description |
---|---|---|---|
model_id | string | optional | [semantically required] Which model for which to save the bookmark. |
new_bookmark | BookmarkMeta | optional | [semantically optional] If empty, create a new bookmark. (In which case, leave the bookmark_id empty, so that the server will create a unique identifier for the new bookmark.) Otherwise, update an existing bookmark. |
Request that the server compute new records based on input values.
The response to this request is RecordData.
Field | Type | Label | Description |
---|---|---|---|
model_id | string | optional | [semantically required] The identifier for the model. |
inputs | VarValue | repeated | [semantically optional] Which input variables to set to which values. |
A response to a request.
Note that a server may send multiple responses to a single request, expressed as a linked list of chunks. It is strongly recommended that servers chunk by record_id
so that each record is kept intact. A chunk may be empty.
Field | Type | Label | Description |
---|---|---|---|
version | uint32 | optional | [semantically required] The version number for the API. This must be the number four. |
id | OptionalUInt32 | optional | [semantically optional] A response without an identifier is a notification. Otherwise, the response identifier matches the response identifier for the original request. |
chunk_id | int32 | optional | [semantically optional, but recommended] The identifier for this chunk. It is recommended that chunks are number sequentially starting from then number one. |
next_chunk_id | int32 | optional | [semantically optional] The identifier of the next chunk, or zero if this is the last chunk. |
error | string | optional | An error message. |
models | ModelMetaList | optional | A list of model metadata. |
data | RecordData | optional | A list of record data. |
bookmarks | BookmarkMetaList | optional | A list of bookmark metadata. |
A list of character strings.
Field | Type | Label | Description |
---|---|---|---|
values | string | repeated | [semantically required] The character strings. |
Value that may be a real number, an integer, or a character string
Exactly one of real_value
, integer_value
, or string_value
must be specified in this message.
Field | Type | Label | Description |
---|---|---|---|
real_value | double | optional | The real number. |
integer_value | int64 | optional | The integer. |
string_value | string | optional | The character string. |
A range of values of a variable.
Both fields in this message are optional:
first_value
is present, the interval designates all values starting from that value.last_value
is present, the bookmark interval designates all values ending at that value.Field | Type | Label | Description |
---|---|---|---|
first_value | Value | optional | [semantically optional] The first value in the interval. |
last_value | Value | optional | [semantically optional] The last value in the interval. |
Metadata for a variable.
Field | Type | Label | Description |
---|---|---|---|
var_id | int32 | optional | [semantically required] An integer identifying the variable. |
var_name | string | optional | [semantically required] The name of the variable. |
units | string | optional | [semantically optional] The name of the unit of measure for values of the variable. |
si | sint32 | repeated | [semantically optional] The unit of measure expressed as a list of the exponents for the eight fundamental SI quantities [meter, kilogram, second, ampere, kelvin, mole, calenda, radian]. For example, the unit of acceleration m/s2 would be express as [1, 0, -2, 0, 0, 0, 0, 0] because meters has an exponent of positive one and seconds has an exponent of negative two. |
scale | double | optional | [semantically optional] An overall scale relative to the fundamental SI scale of the unit of measure. For instance, kilometers would have a scale of 1000 because the fundamental unit of distance is meters. |
type | VariableType | optional | [semantically optional] The data type for values of the variable. The default type is real number. |
A set of values for a variable.
Field | Type | Label | Description |
---|---|---|---|
elements | Value | repeated | [semantically optional] The list of values in the set. |
The value of a variable.
Field | Type | Label | Description |
---|---|---|---|
var_id | int32 | optional | [semantically required] The identifier for the variable. |
value | Value | optional | [semantically required] The value of the variable. |
The data type for a value.
Name | Number | Description |
---|---|---|
REAL | 0 | A real number. |
INTEGER | 1 | An integer. |
STRING | 2 | A character string. |
.proto Type | Notes | C++ Type | Java Type | Python Type |
---|---|---|---|---|
double | double | double | float | |
float | float | float | float | |
int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int |
int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long |
uint32 | Uses variable-length encoding. | uint32 | int | int/long |
uint64 | Uses variable-length encoding. | uint64 | long | int/long |
sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int |
sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long |
fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int |
fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long |
sfixed32 | Always four bytes. | int32 | int | int |
sfixed64 | Always eight bytes. | int64 | long | int/long |
bool | bool | boolean | boolean | |
string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode |
bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str |
This section provides an overview of the variety of libraries and applications implementing the Records API (see the table below). In particular, pre-built applications are available for serving text-based data sources, database queries, and sensor data feeds. Application Container Images (ACIs) (CoreOS 2017a) of each have been packed for use with the rkt container engine (CoreOS 2017b).
Client or Server? | Library or Application? | Data Source | Implementation Language | Computing Platforms | URL |
---|---|---|---|---|---|
client | GUI application | any | C++ | Mac, Winodws, Linux | https://github.nrel.gov/d-star/cpp-records |
server | GUI/CLI applications | CSV files | C++ | Mac, Winodws, Linux | https://github.nrel.gov/d-star/cpp-records |
client | library | any | Haskell | Mac, Windows, Linux | https://github.com/NREL/AESD/lib/haskell |
server | CLI application | TSV files | Haskell | Mac, Windows, Linux | https://github.com/NREL/AESD/lib/haskell |
server | CLI application | PostgreSQL | Haskell | Mac, Windows, Linux | https://github.com/NREL/AESD/lib/haskell |
server | CLI application | MySQL | Haskell | Mac, Windows, Linux | https://github.com/NREL/AESD/lib/haskell |
server | CLI application | SQLite3 | Haskell | Mac, Windows, Linux | https://github.com/NREL/AESD/lib/haskell |
server | CLI application | ODBC | Haskell | Mac, Windows, Linux | https://github.com/NREL/AESD/lib/haskell |
server | CLI application | Haystack | Haskell | Mac, Windows, Linux | https://github.com/NREL/AESD/lib/haskell |
client | library, web application | any | JavaScript | Chrome, Firefox | https://github.com/NREL/AESD/lib/javascript |
client | library | any | Python | any | https://github.com/NREL/AESD/lib/python |
client | library | any | R | any | https://github.nrel.gov/d-star/r-records |
Both client and server applications in Haskell are available for the Records API. Full documentation resides at <https://github.com/NREL/AESD/lib/haskell>.
The client library described below provides the basic functions for interacting with any Records API server.
data State
State information for a client.
clientMain
Run a client.
Argument Type | Descrption |
---|---|
:: String |
The WebSocket host address. |
-> Int |
The WebSocket port number. |
-> String |
The WebSocket path. |
-> (State -> IO ()) |
Customize the client. |
-> IO () |
Action for running the client. |
close
Close a client.
Argument Type | Descrption |
---|---|
:: State |
The state of the client. |
-> IO () |
Action for closing the client. |
fetchModels
Fetch model metadata.
Argument Type | Descrption |
---|---|
:: State |
The state of the client. |
-> IO (Either String [ModelMeta]) |
Action returning either an error or the models. |
fetchRecords
Fetch records from the server.
Argument Type | Descrption |
---|---|
:: State |
The state of the client. |
-> ModelIdentifier |
The model identifier. |
-> Maybe Int |
The maximum number of records to request. |
-> IO (Either String [RecordContent]) |
Action returning either an error or the records. |
fetchBookmarks
Fetch bookmark(s).
Argument Type | Descrption |
---|---|
:: State |
The state of the client. |
-> ModelIdentifier |
The model identifier. |
-> Maybe BookmarkIdentifier |
The bookmark identifier, or all bookmarks. |
-> IO (Either String [BookmarkMeta]) |
Action returning either an error or the bookmark(s). |
storeBookmark
Save a bookmark.
Argument Type | Descrption |
---|---|
:: State |
The state of the client. |
-> ModelIdentifier |
The model identifier. |
-> BookmarkMeta |
The bookmark metadata. |
-> IO (Either String BookmarkMeta) |
Action returning either an error or the bookmark. |
The server library provides two options for implementing a Records APIserver. The AESD.Records.Server
module provides a main entry point serverMain
, a type class ModelManager
, and a monad ServiceM
that implement a skeletal server which handles all of the WebSocket communication and Protocol Buffer serialization; an implementer need only create an instance of ModelManager
. Furthermore, the AESD.Records.Server.Manager
module provides such an instance InMemoryManager
of the type class ModelManger
to handle in-memory caching of data and on-disk persistence of bookmarks; here, an implementer just calls the function makeInMemoryManager
and provides several functions that retrieve content:
makeInMemoryManager
Construct an in-memory model manager.
Argument Type | Descrption |
---|---|
:: Maybe FilePath |
The name of the journal file. |
-> a |
The initial state. |
-> (a -> IO ([ModelMeta], a)) |
List models in an action modifying the state. |
-> (a -> ModelMeta -> IO ([RecordContent], a)) |
Load record data in an action modifying the state. |
-> (a -> ModelMeta -> [VarValue] -> IO ([RecordContent], a)) |
Performing work in an action modifying the state. |
-> IO (InMemoryManager a) |
Action constructing the manager. |
As previously mentioned, prebuilt servers have been implemented for standard types of data sources.
Serving tab-separated-value (TSV) files is a simple as placing the TSV files in a directory and starting a server at the command line, with the arguments specified in the table below:
aesd-file-server <host> <port> <directory> <persistence> <chunkSize>
Parameter | Description |
---|---|
host | host address to which to bind the service |
port | port to which to bind the service |
directory | directory with TSV files to be served |
persistence | filename for bookmark data |
chunkSize | number of records return in each chunk |
The Records API servers have been implemented for the most common database backends. Each server takes a single command-line argument specifying a YAML (Oren Ben-Kiki, Clark Evans, Ingy döt Net 2017) configuration file with the parametes in the table below.
Parameter | Description | PostgreSQL | MySQL | SQLite3 | ODBC |
---|---|---|---|---|---|
host | host address to which to bind the service | required | required | required | required |
port | port to which to bind the service | required | required | required | required |
directory | directory with queries to be served | required | required | required | required |
persistence | filename for bookmark data | optional | optional | optional | optional |
chunkSize | number of records return in each chunk | optional | optional | optional | optional |
database | database connection information | required connection string | required connection string | required filename | required connection string |
Furthermore, a server for Project Haystack (Project Haystack 2017) data feeds, typically sensor measurements from devices in the “internet of things”, has been implemented. The server takes a command-line arguments specified in the table below.
aesd-haystack-server <configuration> <host> <port> <startTime> <persistence> <chunkSize>
Parameter | Description |
---|---|
configuration | YAML configuration file for accessing the Haystack service |
host | host address to which to bind the service |
port | port to which to bind the service |
startTime | earliest time to serve, specified in seconds of the POSIX Epoch |
persistence | filename for bookmark data |
chunkSize | number of records return in each chunk |
The parameters in the YAML configuration file like the one below and are described in the following table:
siteAccess :
server : xv11skys01.nrel.gov
root : /api/nrel_wt_V7
authorization: ["my username","my password"]
secure : false
timeZone : [-360, true, Denver]
siteIdentifier : NWTCv4
siteURI : http://aesd.nrel.gov/records/v4/nwtc/
siteName : NREL NWTC
siteDescription: Sensors from NREL National Wind Technology Center
siteTags :
! 'DC.source' : https://xv11skys01.nrel.gov/proj/nrel_wt_v7
! 'DC.creator' : Brian W Bush <brian.bush@nrel.gov>
! 'DC.description': NREL NWTC sensors
siteMeters :
- 1dca834e-c6af46d6 NWTC Alstom Turbine Electricity Meter Turbine-Alstom kW Demand Forward
- 1dca834e-69a3e57e NWTC Alstom Turbine Electricity Meter Turbine-Alstom kW Demand Reverse
- 1dca834e-f56e11f0 NWTC Alstom Turbine Electricity Meter Turbine-Alstom kWh Delivered Forward
Parameter | Description | Required? |
---|---|---|
server | hostname and port for the Haystack server | required |
root | path to the Haystack REST service | required |
authorization | the username and password for accessing the Haystack REST service | optional |
secure | whether to use HTTPS instead of HTTP | optional |
timezone | timezone information: minutes offset from UTC, whether to use daylight savings time, and the geographic location | required |
siteIdentifier | identifier for the Records API server | required |
siteURI | URI for the Records API server metadata | required |
siteName | name of the Records API server | required |
siteTags | key-value pairs tagging the server with additional information | optional |
siteMeters | list of meters to expose on the Records API server: the Haystack ID is followed by a space and textual description | required |
Both client and server applications have been implemented in C++ for the Records API. See <https://github.nrel.gov/d-star/cpp-records> for details. There are GUI and command-line applications for serving comma-separated-value files and a GUI application for browsing Records API data sources.
The client library for JavaScript relies on a few simple functions to interact with a Records API server. Full documentation for the JavaScript client library is available at <http://github.com/NREL/AESD/lib/javascript>. The figure below shows the user interface of the general purpose Records API browser using this JavaScript library.
User interface for the Records API browser.
connect(wsURL)
Here wsURL
is simply the URL of the server (e.g., ws://10.40.9.214:503761
). This returns a connection object.
disconnect(connection)
Here connection
is the connection object returned by the connect
function.
requestModelsMetadata(connection, modelId, notify, notifyError)
Here connection
is the connection object returned by the connect
function and modelId
is either the string identifying the model or null
if metadata for all models is requested. After all of the model metadata have been retrieved, the notify
function is called with the list of model metadata objects as its argument; if an error occurs, notifyError
is called with the error message as its argument. The function requestModelsMetadata
returns a result object that contains a field done
indicating whether all model metadata have been retrieved and a field models
listing the model metadata retrieved so far.
requestRecordsData(connection, modelId, maxRecords, variableIds, bookmarkId, notify, notifyError)
Here connection
is the connection object returned by the connect
function and modelId
is the string identifying the model. After all of the data records have been retrieved, the notify
function is called with the list of data records as its argument; if an error occurs, notifyError
is called with the error message as its argument. The maxRecords
argument specifies the maximum number of records to retrieve, variableIds
may list the variables of interest, and bookmarkId
restricts the results to bookmarked records. The function requestRecordsData
returns a result object that contains a field done
indicating whether all data records have been retrieved and a field data
listing the data records retrieved so far.
requestBookmarkMeta(connection, modelId, bookmarkId, notify, notifyError)
Here connection
is the connection object returned by the connect
function, modelId
is the string identifying the model, and bookmarkId
is either the string identifying the bookmark or null
if metadata for all bookmarks is requested. After all of the bookmark metadata have been retrieved, the notify
function is called with the list of bookmark metadata as its argument; if an error occurs, notifyError
is called with the error message as its argument. The function requestBookmarkMeta
returns a result object that contains a field done
indicating whether all bookmark metadata have been retrieved and a field bookmarks
listing the bookmark metadata retrieved so far.
requestSaveBookmark(connection, modelId, name, filter, notify, notifyError)
Here connection
is the connection object returned by the connect
function, modelId
is the string identifying the model, and bookmarkId
is null for a new bookmark or the identifier for a bookmark being updated. The name
field names the bookmark and the filter
object describing the filtering operation for the bookmark. After the bookmark metadata has been created or updated, the notify
function is called with the list of bookmark metadata as its argument; if an error occurs, then notifyError
is called with the error message as its argument. The function requestSaveBookmark
returns a result object that contains a field done
indicating whether all bookmark metadata have been retrieved and a field bookmarks
listing the bookmark metadata retrieved so far.
Full documentation for the Python client library is available at <http://github.com/NREL/AESD/lib/python>.
new_server(self, server_url)
Change server url to which websocket will connnect
Parameters
----------
server_url : 'string'
server url
Returns
---------
self.url : 'string'
server url
send(self, request)
Closes event_loop
Parameters
----------
request : 'proto.request'
proto request message
timeout : 'int'
timeout in seconds for connection
Returns
---------
response : 'list'
List of responses from the server, each response is a proto message
get_model_info(self, model_id)
Sends request of model metadata and extracts response
Parameters
----------
model_id : 'string'
Id of model for which to requst models_metadata
if None requests all models
Returns
-------
model_info : 'list'|'dict'
List of model's metadata dictionaries for each model in models or
dictionary for model_id
get_data(self, model_id, max_records=1000, variable_ids=None, bookmark_id=None)
Sends request of model metadata and extracts response
Parameters
----------
model_id : 'string'
Id of model for which to requst records_data
max_records : 'int'
Number or records being request (0 will return all records)
variable_ids : 'list'
List of variable ids (ints) to be requested
Will be returned in same order as request
Default=None, all variables will be returned (order?)
bookmark_id : 'int'
Request records_data based on bookmark id
Returns
-------
data : 'pd.DataFrame'
Concatenated data from each response message
Variable ids replaced with names from model_info
do_work(self, model_id, inputs)
Sends request of model metadata and extracts response
Parameters
----------
model_id : 'string'
Id of model for which to requst records_data
inputs : 'dict'
Dictionary of {var_id: value} pairs
Returns
-------
data : 'pd.DataFrame'
Concatenated data from each response message
Variable ids replaced with names from model_info
get_bookmark_info(self, model_id, bookmark_id)
Sends request of model metadata and extracts response
Parameters
----------
model_id : 'string'
Id of model for which to requst bookmark_meta
bookmark_id : 'string'
Id of bookmark for which to request models_metadata
if None request all bookmarks
Returns
-------
model_info : 'list'|'dict'
List of model's metadata dictionaries for each model in models or
dictionary for model_id
save_bookmark(self, model_id, name, content)
Sends request to save new bookmark
Parameters
----------
model_id : 'string'
Id of model for which to requst bookmark_meta
name : 'string'
Name for new bookmark
content : 'list'|'tuple'
Contents of bookmark
list is a bookmark set
tuple is a bookmark interval
Returns
-------
model_info : 'list'|'dict'
List of model's metadata dictionaries for each model in models or
dictionary for model_id
The figure below shows example usage of the Python Records API client.
Example of a Python session using the Records API
syntax = "proto3";
package AesdRecords;
option optimize_for = LITE_RUNTIME;
message OptionalInt32 {
int32 value = 1; /// [semantically required]
}
message OptionalUInt32 {
uint32 value = 1; /// [semantically required]
}
message OptionalString {
string value = 1; /// [semantically required]
}
message Value {
oneof value /// [semantically required]
{
double real_value = 1;
int64 integer_value = 2;
string string_value = 3;
}
}
message DoubleList {
repeated double values = 1; /// [semantically required]
}
message IntegerList {
repeated sint64 values = 1; /// [semantically required]
}
message StringList {
repeated string values = 1; /// [semantically required]
}
message BookmarkIntervalContent {
int64 first_record = 1; /// [semantically optional]
int64 last_record = 2; /// [semantically optional]
}
message BookmarkSetContent {
repeated int64 record_ids = 1; /// [semantically optional]
}
message BookmarkMeta {
string bookmark_id = 1; /// [semantically optional]
string bookmark_name = 2; /// [semantically required]
oneof content /// [semantically required]
{
BookmarkIntervalContent interval = 3;
BookmarkSetContent set = 4;
FilterExpression filter = 5;
}
}
message BookmarkMetaList {
repeated BookmarkMeta bookmark_metas = 1; /// [semantically optional]
}
message RequestBookmarkMeta {
string model_id = 1; /// [semantically required]
OptionalString bookmark_id = 2; /// [semantically optional]
}
message RequestSaveBookmark {
string model_id = 1; /// [semantically required]
BookmarkMeta new_bookmark = 2; /// [semantically optional]
}
message FilterExpression {
oneof expression /// [semantically required]
{
FilterNot filter_not = 1;
FilterUnion filter_union = 2;
FilterIntersection filter_intersection = 3;
DomainMeta filter_domain = 4;
}
}
message FilterNot {
FilterExpression filter_expression = 1; /// [semantically required]
}
message FilterUnion {
repeated FilterExpression filter_expressions = 1; /// [semantically required]
}
message FilterIntersection {
repeated FilterExpression filter_expressions = 1; /// [semantically required]
}
enum VariableType
{
REAL = 0;
INTEGER = 1;
STRING = 2;
}
message VarMeta {
int32 var_id = 1; /// [semantically required]
string var_name = 2; /// [semantically required]
string units = 3; /// [semantically optional]
repeated sint32 si = 4; /// [semantically optional]
double scale = 5; /// [semantically optional]
VariableType type = 6; /// [semantically optional]
}
message ModelMeta {
string model_id = 1; /// [semantically required]
string model_name = 2; /// [semantically required]
string model_uri = 3; /// [semantically required]
repeated VarMeta variables = 4; /// [semantically required]
repeated DomainMeta inputs = 5; /// [semantically optional]
}
message ModelMetaList {
repeated ModelMeta models = 1; /// [semantically optional]
}
message RequestModelsMeta {
OptionalString model_id = 1; /// [semantically optional]
}
message VarInterval {
Value first_value = 1; /// [semantically optional]
Value last_value = 2; /// [semantically optional]
}
message VarSet {
repeated Value elements = 1; /// [semantically optional]
}
message DomainMeta {
int32 var_id = 1; /// [semantically required]
oneof domain /// [semantically required]
{
VarInterval interval = 2;
VarSet set = 3;
}
}
message RequestWork {
string model_id = 1; /// [semantically required]
repeated VarValue inputs = 2; /// [semantically optional]
}
message VarValue {
int32 var_id = 1; /// [semantically required]
Value value = 2; /// [semantically required]
}
message Record {
int64 record_id = 1; /// [semantically required]
repeated VarValue variables = 2; /// [semantically optional]
}
message RecordList {
repeated Record records = 1; /// [semantically optional]
}
message RecordTable {
repeated int32 var_ids = 1; /// [semantically required]
repeated int64 rec_ids = 2; /// [semantically required]
oneof list /// [semantically required]
{
DoubleList reals = 3;
IntegerList integers = 4;
StringList strings = 5;
}
}
message RecordData {
oneof style /// [semantically required]
{
RecordList list = 1;
RecordTable table = 2;
}
}
message RequestRecordsData {
string model_id = 1; /// [semantically required]
uint64 max_records = 2; /// [semantically optional]
repeated int32 var_ids = 3; /// [semantically optional]
oneof filter /// [semantically optional]
{
string bookmark_id = 4; /// [semantically optional]
FilterExpression expression = 5; /// [semantically optional]
}
}
message Response {
uint32 version = 1; /// [semantically required]
OptionalUInt32 id = 2; /// [semantically optional]
int32 chunk_id = 3; /// [semantically optional, but recommended]
int32 next_chunk_id = 4; /// [semantically optional]
oneof type /// [semantically optional]
{
string error = 5;
ModelMetaList models = 6;
RecordData data = 7;
BookmarkMetaList bookmarks = 8;
}
}
message RequestCancel {
OptionalUInt32 id = 1; /// [semantically required]
}
message Request {
uint32 version = 1; /// [semantically required]
OptionalUInt32 id = 2; /// [semantically optional, but recommended]
bool subscribe = 3; /// [semantically optional]
oneof type /// [semantically required]
{
RequestModelsMeta models_metadata = 4;
RequestRecordsData records_data = 5;
RequestBookmarkMeta bookmark_meta = 6;
RequestSaveBookmark save_bookmark = 7;
RequestCancel cancel = 8;
RequestWork work = 9;
}
}
Term | Definition |
---|---|
ACI | Application Container Image |
AESD | Advanced Energy System Design |
API | Application Programming Interface |
C++ | a programming language |
CSV | comma-separated-value file |
Chrome | a web browser product |
Firefox | a web browser product |
Google Protocol Buffers | a serialization specification |
HTTP | Hypertext Transfer Protocol |
HTTPS | Hypertext Transfer Protocol Secure |
Haskell | a programming language |
IoT | the Internet of Thinks |
JSON | JavaScript Object Notation |
JavaScript | a programming language |
MySQL | a database server product |
NREL | National Renewable Energy Laboratory |
ODBC | Open Database Connectivity |
POSIX Epoch | seconds since midnight 1 January 1970 UTC |
PostgreSQL | a database server product |
Project Haystack | a specification for data feeds from the Internet of Thinks (IoT) |
Python | a programming language |
R | a programming language |
REST | Representational State Transfer |
rkt | a containger engine (CoreOS 2017b) |
SQLite3 | a database server product |
TSV | tab-separate-value file |
URI | uniform resource identifier |
UTC | Coordinate Universal Time |
WebSockets | a communication protocol |
YAML | YAML Ain’t Markup Language |
CoreOS. 2017a. “App Container Basics - Coreos.” Accessed September 6. https://coreos.com/rkt/docs/latest/app-container.html.
———. 2017b. “Rkt Container Engine with Coreos.” Accessed September 6. https://coreos.com/rkt.
Fowler, Martin. 2017. “UML Distilled.” Accessed April 11. http://my.safaribooksonline.com/book/software-engineering-and-development/uml/0321193687/sequence-diagrams/ch04.
Google Developers. 2017a. “Protocol Buffers - Google’s Data Interchange Format.” Accessed April 11. https://github.com/google/protobuf/blob/master/README.md.
———. 2017b. “Protocol Buffers | Google Developers.” Accessed April 11. https://developers.google.com/protocol-buffers/.
Internet Engineering Task Force. 2017. “RFC 6455 - the Websocket Protocol.” Accessed April 11. https://tools.ietf.org/html/rfc6455.
Oren Ben-Kiki, Clark Evans, Ingy döt Net. 2017. “YAML Specification Index.” Accessed September 6. http://www.yaml.org/spec/.
Project Haystack. 2017. “Home - Project Haystack.” Accessed September 6. http://project-haystack.org/.