This tutorial is about the Senaps Sensor Data API (Application Programming Interface) (formerly referred to as the 'Sensorcloud API'). It assumes some knowledge of REST APIs, HTTP, and having necessary skills to develop software applications that consume APIs.
The Sensor Data API is a RESTful service adopting the HAL specification. The API endpoint can be found here: https://senaps.io/api/sensor/v2.
For an interactive API interface you can explore the API docs (https://senaps.io/api-docs/).
The API lets you create, query and manage resources via:
/organisations |
An Organisation represents an institution, association, business or government department for example. It provides the mechanism to keep tenancies in Senaps secure from other users, most resources in Senaps sit within a single Organisation.
/collections |
A Collection is a placeholder within one Senaps Organisation that provides read only access to data that is Shared to that Collection from another Organisations in Senaps. They are used in conjunction with Shares
/groups |
A Group provides a way to link Platforms, Locations, Datastreams, and other Groups within an Organisation. Groups are the main mechanism used to manage permissions through the use of Roles. Groups can have an arbitrary arrangements (not strictly hierarchical), i.e. a Group can have multiple Parent Groups and Child Groups. Permissions propagate from parents to children.
/invitations |
An Invitation provides a way to invite new or existing Users to have Roles in the organisation
/locations |
A Location is a named physical location that can be identified by its latitude/longitude coordinate and optionally an elevation. Streams and Platforms can be associated with a Location.
/platforms |
A Platform can represent a set of Streams and can hold information on Deployments. Typically platforms are used to represent sets of sensors physically linked together on one device (different sensors on an automatic weather station is an example).
They provide a way to simulate Deployments of sensors or measuring devices by defining a time window that a Stream and/or Location is active. Streams added to platforms inherit the Group memberships of the Platform, so permissions on platforms propagate to Streams in the Platform.
/users |
A User is a user of the API and has associated Roles.
/roles |
A Role defines permissions. A Role applies to either a Group or an Organisation and can have zero or more of the available system permissions for Groups/Organisations. Roles are assigned to Users and control how those users can interact with the API.
/shares |
A Share is a mechanism to provide read only access to data sitting in the tenancy of one Senaps Organisation (source) to a different Senaps Organisation (destination) tenancy. Applying the Share mechanism to a Group or Organisation with a link to a Collection that was set up in a destination Senaps Organisation allows that destination Organisation to access the data held within the sharing Organisation.
/streams |
A Stream represents a time-series of data. It can be one of four types:
A Stream describes / includes some system and optionally user described metadata for the associated Observation data.
/observations |
An Observation is a (timestamp, value) pair where the value depends on the type of Stream i.e. it needs to be a Scalar value, a Geolocation value, an array of scalars (Vector) or an image. Streams consist of zero or more observations with unique timestamps. Maximum timestamp resolution is one millisecond.
/procedures |
A Procedure represents a 'Sensing Procedure' that describes the method used for collecting a Stream of Observation data.
/aggregations |
An Aggregation is an aggregated view of a Data Stream's Observations such as min/max, average and spatial median (Spatial only) for a given period and sampling window. The aggregation is calculated on demand.
/vocabulary |
A query interface for linked data registries to find terms matching a keyword search used with the system metadata for Streams
/vocabularyProxy |
A query interface for white listed linked data registries returning results in a JSON-LD format.
It provides a proxy interface to overcome mixed http/https content problems when resolving terms over https.
Registering new accounts can be done via the Senaps data portal web site. To do this go to: https://senaps.io/dashboard/#/access/signup. Email addresses are used as usernames and provide a way to inform users of important announcements.
Initially new users don't have any specific authorisation, they will only be able to see data sets which have been shared publicly (which does change over time).
There are two ways of gaining a role in the platform:
All API requests must be authenticated using HTTP Basic Authentication or API Key Authentication. Most HTTP client libraries provide support for basic authentication and passing an API key as a header.
The API is secured over HTTPS using SHA-256 with RSA Encryption.
Postman is a useful program that lets you construct API requests and generate code examples for consuming the APIs in popular programming languages.
Never share your API key with others; with your API key they will have all of the same permissions in the system as you do until the key is deleted. |
You can generate a Senaps API key for use in requests by visiting My Account in the menu that is accessible under your username in the Senaps dashboard. Alternatively, you can click this link to navigate directly: https://senaps.io/dashboard/#/app/account
All API requests must include the following headers.
Content-Type: application/json Authorization: Basic <<username:password base-64 encoded>> |
Content-Type: application/json apikey: <<user's API key retrieved from the Senaps dashboard>> |
For basic authentication to work you need provide a base-64 encoded username and password, separated by a colon ':'. Most programming languages provide support for base-64 encoding. There are online tools that can convert plain text to base-64, however submitting sensitive details to non-secure or untrusted websites is not recommended.
As a concrete example: given a user with username user@example.com and with password test, first we concatenate the username, a colon, and the password together to form user@example.com.au:test, which is then base-64 encoded to result in dXNlckBleGFtcGxlLmNvbS5hdTp0ZXN0.
This value is then added to the HTTP Authorization header:
Content-Type: application/json Authorization: Basic dXNlckBleGFtcGxlLmNvbS5hdTp0ZXN0 |
A stream can be created by issuing a PUT request to the API /streams endpoint.
For example:
PUT /api/sensor/v2/streams/tutorial.1.example.sea_water_stream HTTP/1.1
Host: senaps.io
Content-Type: application/json
Authorization: Basic <<username:password base64 encoded>>
{
"id": "tutorial.1.example.sea_water_stream",
"resulttype": "scalarvalue",
"organisationid": "sandbox",
"groupids": [],
"samplePeriod": "PT15M",
"reportingPeriod": "PT15M",
"streamMetadata": {
"type": ".ScalarStreamMetaData",
"observedProperty": "http://registry.it.csiro.au/def/environment/property/sea_water_temperature",
"unitOfMeasure": "http://registry.it.csiro.au/def/qudt/1.1/qudt-unit/DegreeCelsius",
"interpolationType": "http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous",
"cummulative": false
}
} |
A partial metadata record may also be provided when a system has minimal metadata known prior to stream creation.
PUT /api/sensor/v2/streams/my.test.scalar.stream HTTP/1.1
Host: senaps.io
Content-Type: application/json
Authorization: Basic <<username:password base64 encoded>>
{
"id": "my.test.scalar.stream",
"resulttype": "scalarvalue",
"organisationid": "sandbox",
"groupids": ["senaps_workshop"],
"streamMetadata": {
"type": ".ScalarStreamMetaData",
"interpolationType": "http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous"
}
} |
To create a data stream the registered user must have the CreateStreamPermission as part of one of their roles. The API accepts partial metadata records for fields that are not critical for data storage and retrieval. The stream object contains a number of properties and the meta-data object. The properties are outlined below.
Stream identifier: The API will allow the client to specify a string stream identifier, this identifier must be unique across the platform, if you conflict with another identifier where you don't have permissions you will receive an unauthorised response.
"id": "tutorial.1.example.stream" |
Result type: This can either be scalarvalue, geolocationvalue, vectorvalue or imagevalue. "scalar values" are used for numerical data streams, "geolocations" are used to manage trajectory information (example below) and "vectors" are used to store arrays of numbers like histograms or spectrums. For this example we will use scalarvalue.
"resultype" : "scalarvalue" |
Organisation: All data stored in Senaps must be associated with one organisation. This is typically the organisation responsible for the data, the entity which is responsible for managing who has access to the data. This tutorial assumes the user has access to create data in the sandbox organisation, if not please change to a suitable organisation ID.
"organisationid" : "sandbox" |
Groups: Streams can optionally be members of various groups. In this example we don't need to list any groups.
"groupids" : [] |
Sample period: Each stream must have a nominal sample period - i.e. the typical period between successive samples. The sample rate is only used as a hint for clients, it allows clients to estimate the volume of data that could be returned in a observations request. The sample rate must be specified in ISO8601 duration format. This means you can specify the duration is the most suitable calendar units. Some examples:
P1D = 1 dayPT10M = 10 minutesP1M3DT4M = 1 month, 3 days and 4 minutesFor our example we will use 15 minutes.
"samplePeriod" : "PT15M" |
Reporting rate: Similarly, reporting rate specifies the nominal duration between updates to the server. Many systems will cache data and insert/upload it at a slower rate than the sample rate. The duration is specified in the same format as the sample rate.
"reportingPeriod" : "PT15M" |
A stream meta-data object is now mandatory for all streams. There are five types, a scalar type, a geolocation type, two vector types and an image type. We will focus on the scalar version in this tutorial.
Type: .ScalarStreamMetaData , .GeoLocationMetaData , .VectorStreamMetaData , .RegularlyBinnedVectorStreamMetaData or .ImageStreamMetaData . Note the preceding decimal point all values.
"type" : ".ScalarStreamMetaData" |
Observed property: The observed property provides a controlled vocabulary term for the property being measured. For environmental sensing this is usually pretty straight forward, for some engineering and diagnostics data is can be less clear. Senaps is integrated with the CSIRO hosted environment registry.
"observedproperty" : "http://registry.it.csiro.au/def/environment/property/sea_water_temperature" |
Unit of measure: Similarly, the unit of measure is defined by a URI discovered via the vocabulary service.
"unitofmeasure" : "http://registry.it.csiro.au/def/qudt/1.1/qudt-unit/DegreeCelsius" |
The observed property and unit of measure are from controlled vocabularies. For environmental sensing this is usually pretty straight forward, for some engineering and diagnostics data it can be less clear. Senaps is integrated with the CSIRO hosted environment registry and also hosts its own registry for terms not available in the CSIRO hosted registry.
Senaps provides an API which will query the linked data registries to find term matching a keyword search.
When searching for the observed properties, use the value ScaledQuantityKind type reference for the type parameter:
type=http://environment.data.gov.au/def/op#ScaledQuantityKind |
When searching for units of measure use the QUDT Unit type reference for the type parameter:
type=http://qudt.org/schema/qudt#Unit |
Don't forget to URL encode this parameter.
The query parameter is used to provide a search keyword, wild cards are accepted.
An example request looking for temperature related properties:
GET /vocabulary?query=*temp*&type=http%253A%252F%252Fenvironment.data.gov.au%252Fdef%252Fop%2523ScaledQuantityKind |
Example response:
{
"_links": {
"self": {
"href": "https://senaps.io/api/sensor/v2/vocabulary"
}
},
"count": 6,
"_embedded": {
"results": [
{
"_links": {
"self": {
"href": "http://registry.it.csiro.au/def/environment/property/air_temperature"
}
},
"label": "air temperature"
},
{
"_links": {
"self": {
"href": "http://registry.it.csiro.au/def/environment/property/dew_point_temperature"
}
},
"label": "dew point temperature"
},
{
"_links": {
"self": {
"href": "http://senaps.io/registry/def/sop/AT"
}
},
"label": "apparent temperature"
},
{
"_links": {
"self": {
"href": "http://senaps.io/registry/def/sop/InternalTemperature"
}
},
"label": "internal temperature"
},
{
"_links": {
"self": {
"href": "http://registry.it.csiro.au/def/environment/property/water_temperature"
}
},
"label": "water temperature"
},
{
"_links": {
"self": {
"href": "http://registry.it.csiro.au/def/environment/property/sea_water_temperature"
}
},
"label": "sea water temperature"
}
]
}
} |
The response will include a list of possible matches. More details can be found by opening an example in your browser (eg. http://registry.it.csiro.au/def/environment/property/sea_water_temperature).
Interpolation type: The interpolation type is used to describe relationship between the time instant and the sampled value. An existing standard, WaterML2.0, provides a definition of a number of interpolation types to better describe the measurement procedure. They are identified by persistent URIs. More information can be found in the WaterML2.0 part 1 document.
The defined interpolation types are:
* http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous
* http://www.opengis.net/def/waterml/2.0/interpolationType/Discontinuous
* http://www.opengis.net/def/waterml/2.0/interpolationType/InstantTotal
* http://www.opengis.net/def/waterml/2.0/interpolationType/AveragePrec
* http://www.opengis.net/def/waterml/2.0/interpolationType/MaxPrec
* http://www.opengis.net/def/waterml/2.0/interpolationType/MinPrec
* http://www.opengis.net/def/waterml/2.0/interpolationType/TotalPrec
* http://www.opengis.net/def/waterml/2.0/interpolationType/ConstPrec
* http://www.opengis.net/def/waterml/2.0/interpolationType/AverageSucc
* http://www.opengis.net/def/waterml/2.0/interpolationType/TotalSucc
* http://www.opengis.net/def/waterml/2.0/interpolationType/MinSucc
* http://www.opengis.net/def/waterml/2.0/interpolationType/MaxSucc
* http://www.opengis.net/def/waterml/2.0/interpolationType/ConstSucc
For this tutorial we will use the continuous type:
"interpolationtype": "http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous" |
Cummulative: Used for data streams that accumulate then reset at regular intervals. For this tutorial we will leave this as false.
A successful request should return a response code of 200.
Example of a PUT stream response:
{
"_links": {
"self": {
"href": "https://senaps.io/api/sensor/v2/streams/tutorial.1.example.sea_water_stream"
},
"observations": [
{
"href": "https://senaps.io/api/sensor/v2/observations?streamid=tutorial.1.example.sea_water_stream"
}
]
},
"id": "tutorial.1.example.sea_water_stream",
"reportingPeriod": "PT15M",
"resulttype": "scalarvalue",
"samplePeriod": "PT15M",
"_embedded": {
"metadata": [
{
"cummulative": false,
"_embedded": {
"unitOfMeasure": [
{
"_links": {
"self": {
"href": "http://registry.it.csiro.au/def/qudt/1.1/qudt-unit/DegreeCelsius"
}
}
}
],
"observedProperty": [
{
"_links": {
"self": {
"href": "http://registry.it.csiro.au/def/environment/property/sea_water_temperature"
}
}
}
],
"interpolationType": [
{
"_links": {
"self": {
"href": "http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous"
}
}
}
]
}
}
],
"organisation": [
{
"_links": {
"self": {
"href": "https://senaps.io/api/sensor/v2/organisations/sandbox"
}
},
"id": "sandbox"
}
]
}
} |
Observations can be created by issuing a POST request to the API /observations endpoint.
The timestamp must be formatted as ISO8601, and can include a maximum of millisecond resolution. Timestamps are both stored internally and returned as UTC. It is highly recommended timezone information is included when providing timestamps. Timestamps provided with no timezone information are interpreted as UTC. |
For example:
POST /api/sensor/v2/observations?streamid=tutorial.1.example.sea_water_stream HTTP/1.1
Host: senaps.io
Content-Type: application/json
Authorization: Basic <<username:password base64 encoded>>
{
"results": [
{
"t": "2015-11-12T00:00:00.000Z",
"v": {
"v": 1.0
}
},
{
"t": "2015-11-13T01:00:00.000Z",
"v": {
"v": 2.0
}
},
{
"t": "2015-11-14T02:00:00.000Z",
"v": {
"v": 1.5
}
},
{
"t": "2015-11-15T03:00:00.000Z",
"v": {
"v": 3.0
}
},
{
"t": "2015-11-16T04:00:00.000Z",
"v": {
"v": 2.5
}
}
]
} |
If the upload is successful the status code will be 201 Created.
Example of a POST observations response:
{
"message": "Observations uploaded",
"status": 201,
"_embedded": {
"user": [
{
"_links": {
"self": {
"href": "https://senaps.io/api/sensor/v2/users/user@example.com.au"
}
},
"id": "user@example.com.au"
}
]
}
} |
A stream can be created by issuing a PUT request to the API /streams endpoint.
For example:
PUT /api/sensor/v2/streams/testglstream HTTP/1.1
Host: senaps.io
Content-Type: application/json
Authorization: Basic <<username:password base64 encoded>>
{
"id": "testglstream",
"resulttype": "geolocationvalue",
"organisationid": "test",
"groupids": [],
"samplePeriod": "PT1S",
"streamMetadata": {
"type": ".GeoLocationStreamMetaData",
"interpolationType": "http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous"
}
} |
To create a data stream the registered user must have the .CreateStreamPermission as part of one of their roles. The API accepts partial metadata records for fields that are not critical for data storage and retrieval. The stream object contains a number of properties and the meta-data object. The properties are outlined below.
Stream identifier: The API will allow the client to specify a string stream identifier, this identifier must be unique across the platform, if you conflict with another identifier where you don't have permissions you will receive an unauthorised response.
"id": "tutorial.1.example.stream" |
Result type: This can either be scalarvalue or geolocationvalue . "Scalar values" are used for numerical data streams, and "geolocations" are used to manage trajectory information (example below). For this example we will use scalarvalue.
"resultype" : "scalarvalue" |
Organisation: All data stored in Senaps must be associated with one organisation. This is typically the organisation responsible for the data, the entity which is responsible for managing who has access to the data. This tutorial assumes the user has access to create data in the sandbox organisation, if not please change to a suitable organisation ID.
"organisationid" : "sandbox" |
Groups: Streams can optionally be members of various groups. In this example we don't need to list any groups.
"groupids" : [] |
Sample period: Each stream must have a nominal sample period - i.e. the typical period between successive samples. The sample rate is only used as a hint for clients, it allows clients to estimate the volume of data that could be returned in a observations request. The sample rate must be specified in ISO8601 Format (https://en.wikipedia.org/wiki/ISO_8601#Durations) format. This means you can specify the duration is the most suitable calendar units. Some examples:
P1D = 1 dayPT10M = 10 minutesP1M3DT4M = 1 month, 3 days and 4 minutesFor our example we will use 15 minutes.
"samplePeriod" : "PT15M" |
Reporting rate: Similarly, reporting rate specifies the nominal duration between updates to the server. Many systems will insert data at a much slower rate than the sample rate. The duration is specified in the same format as the sample rate.
"reportingPeriod" : "PT15M" |
A stream meta-data object is now mandatory for all streams. There are currently two types, the "scalar" version and the "geolocation" version. We will focus on the scalar version here.
Type: .ScalarStreamMetaData or .GeoLocationMetaData. Note the preceding decimal point on both values.
"type" : ".GeoLocationStreamMetaData" |
Interpolation type: The interpolation type is used to describe relationship between the time instant and the sampled value. An existing standard, WaterML2.0, provides a definition of a number of interpolation types to better describe the measurement procedure. They are identified by persistent URIs. More information can be found in the WaterML2.0 part 1 document.
The defined interpolation types are:
For this tutorial we will use the continuous type:
"interpolationtype": "http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous" |
A successful request should return a response code of 200.
Example of a PUT stream response:
{
"_links": {
"self": {
"href": "https://senaps.io/api/sensor/v2/streams/testglstream"
},
"observations": [
{
"href": "https://senaps.io/api/sensor/v2/observations?streamid=testglstream"
}
]
},
"id": "testglstream",
"resulttype": "geolocationvalue",
"samplePeriod": "PT1S",
"_embedded": {
"metadata": [
{
"_embedded": {
"interpolationType": [
{
"_links": {
"self": {
"href": "http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous"
}
}
}
]
}
}
],
"organisation": [
{
"_links": {
"self": {
"href": "https://senaps.io/api/sensor/v2/organisations/test"
}
},
"id": "test"
}
]
}
} |
Observations can be created by issuing a POST request to the API /observations endpoint.
The timestamp must be formatted as ISO8601, and can include a maximum of millisecond resolution. Timestamps are both stored internally and returned as UTC. It is highly recommended timezone information is included when providing timestamps. Timestamps provided with no timezone information are interpreted as UTC. |
For example:
POST /api/sensor/v2/observations?streamid=testglstream HTTP/1.1
Host: senaps.io
Content-Type: application/json
Authorization: Basic <<username:password base64 encoded>>
{
"results": [
{
"t": "2014-10-07T00:47:03.000Z",
"v": {
"p": {
"type": "Point",
"coordinates": [
150.369105,
-31.742179,
415.8
]
}
}
},
{
"t": "2014-10-07T00:47:04.000Z",
"v": {
"p": {
"type": "Point",
"coordinates": [
150.469105,
-31.742179,
415.78
]
}
}
},
{
"t": "2014-10-07T00:47:05.000Z",
"v": {
"p": {
"type": "Point",
"coordinates": [
150.469105,
-31.842179,
415.77
]
}
}
},
{
"t": "2014-10-07T00:47:06.000Z",
"v": {
"p": {
"type": "Point",
"coordinates": [
150.469105,
-31.942179,
415.78
]
}
}
},
{
"t": "2014-10-07T00:47:07.000Z",
"v": {
"p": {
"type": "Point",
"coordinates": [
150.569105,
-31.842179,
415.77
]
}
}
},
{
"t": "2014-10-07T00:47:08.000Z",
"v": {
"p": {
"type": "Point",
"coordinates": [
150.669105,
-31.842179,
415.77
]
}
}
}
]
} |
If the upload is successful the status code will be 201 Created.
Example of a POST observations response:
{
"message": "Observations uploaded",
"status": 201,
"_embedded": {
"user": [
{
"_links": {
"self": {
"href": "https://senaps.io/api/sensor/v2/users/user@example.com.au"
}
},
"id": "user@example.com.au"
}
]
}
} |
#!/usr/bin/env Rscript
library(httr)
library(rjson)
get_stream_id <- function(stream) {
stream$id
}
base_url="https://senaps.io/api/sensor/v2"
username="MY_USERNAME"
password="MY_PASSWORD"
credentials <- authenticate(username, password) |
# tailor or your url and query depending on how you would like to filter streams
url <- paste(base_url, "streams", sep="/")
query <- list(groupids="acri", limit=3)
response <- GET(url, credentials, query=query)
stop_for_status(response) # stop if the response is an error
response_data <- fromJSON(content(response, as="text"));
# this shows the first stream id in a list of streams. Note that the underscore needs to be escaped with the grave accent.
response_data$`_embedded`$streams[[1]]$id
cat("\n\n")
list_of_streamids <- list()
list_of_streamids <- lapply(response_data$`_embedded`$streams, get_stream_id)
list_of_streamids |
# tailor or your url and query depending on how you would like to filter streams
url <- paste(base_url, "streams", sep="/")
query <- list(id="waterwise*ac*temp*")
response <- GET(url, credentials, query=query)
stop_for_status(response) # stop if the response is an error
response_data <- fromJSON(content(response, as="text"));
# this shows the first stream id in a list of streams. Note that the underscore needs to be escaped with the grave accent.
response_data$`_embedded`$streams[[1]]$id
cat("\n\n")
list_of_streamids <- list()
list_of_streamids <- lapply(response_data$`_embedded`$streams, get_stream_id)
list_of_streamids |
url <- paste(base_url, "observations", sep="/") query <- list(streamid=paste(list_of_streamids, collapse=","), limit=5) response <- GET(url, credentials, query=query) stop_for_status(response) # stop if the response is an error response_data <- fromJSON(content(response, as="text")); response_data$results |
new_stream_id <- "test.dummy"
url <- paste(base_url, "streams", new_stream_id, sep="/")
query <- list(streamid=new_stream_id)
body_json <- '
{
"id": "NEW_STREAM_ID",
"resulttype": "scalarvalue",
"organisationid": "csiro",
"groupids": [
"waterwise"
],
"samplePeriod": "PT5M",
"reportingPeriod": "PT5M",
"streamMetadata": {
"type": ".ScalarStreamMetaData",
"observedProperty": "http://registry.it.csiro.au/def/environment/property/air_temperature",
"unitOfMeasure": "http://registry.it.csiro.au/def/qudt/1.1/qudt-unit/DegreeCelsius",
"interpolationType": "http://www.opengis.net/def/waterml/2.0/interpolationType/Continuous"
}
}
'
body_json <- gsub("NEW_STREAM_ID", new_stream_id, body_json)
body_json <- strwrap(body_json, width=10000, simplify=TRUE) # remove new line characters (cosmetic but useful when debugging)
response <- PUT(url, credentials, query=query, body=body_json)
stop_for_status(response) # stop if the response is an error |
url <- paste(base_url, "observations", sep="/")
query <- list(streamid=new_stream_id)
body_json <- '
{
"results": [
{
"t": "2012-11-04T21:00:00.000Z",
"v": {"v": 1.23}
}
]
}
'
body_json <- strwrap(body_json, width=10000, simplify=TRUE) # remove new line characters (cosmetic but useful when debugging)
response <- POST(url, credentials, query=query, body=body_json)
stop_for_status(response) # stop if the response is an error |
body_r <- list(results = c())
unix_datetime <- 1352068001
i <- 1
for (value in seq(0, 2*pi, length.out=100)) {
datetime <- as.POSIXct(unix_datetime, origin="1970-01-01", tz="UTC")
datetime <- format(datetime,"%Y-%m-%dT%H:%M:%OS3Z")
body_r$results[[i]] <- list(t=datetime, v=list(v=sin(value)))
i <- i + 1
unix_datetime = unix_datetime + 60
}
response <- POST(url, credentials, query=query, body=toJSON(body_r))
stop_for_status(response) # stop if the response is an error
paste("created sine wave starting at ", datetime) |