class TempoIQ::Client

TempoIQ::Client is the main interface to your TempoIQ backend.

The client is broken down into two main sections:

Device Provisioning
DataPoint Reading / Writing

Key Concepts:

Selection - A way to describe a grouping of related objects. Used primarily in Device / Sensor queries.

Attributes

host[R]

TempoIQ backend host, found on your TempoIQ backend dashboard (String)

key[R]

Your TempoIQ backend key (String)

remoter[R]

Makes the backend calls (Remoter, default: LiveRemoter)

secret[R]

TempoIQ backend secret (String)

secure[R]

Whether to use SSL or not. Defaults to true (Boolean, default: true)

Public Class Methods

new(key, secret, host, port = 443, opts = {}) click to toggle source

Create a TempoIQ API Client

  • key [String] - Your TempoIQ backend key

  • secret [String] - TempoIQ backend secret

  • host [String] - TempoIQ backend host, found on your TempoIQ backend dashboard

  • port (optional) [Integer] - TempoIQ backend port

  • opts (optional) [Hash] - Optional client parameters

Options

  • :secure [Boolean] - Whether to use SSL or not. Defaults to true

  • :remoter [Remoter] - Which backend to issue calls with. Defaults to LiveRemoter

# File lib/tempoiq/client.rb, line 74
def initialize(key, secret, host, port = 443, opts = {})
  @key = key
  @secret = secret
  @host = host
  @port = port
  @secure = opts.has_key?(:secure) ? opts[:secure] : true
  @remoter = opts[:remoter] || LiveRemoter.new(key, secret, host, port, secure)
end

Public Instance Methods

create_device(key, name = "", attributes = {}, *sensors) click to toggle source

Create a Device in your TempoIQ backend

  • key [String] - Device key

  • name (optional) [String] - Human readable device name

  • attributes (optional) [Hash] - A hash of device attributes. Keys / values are strings.

  • sensors (optional) [Array] - An array of Sensor objects to attach to the device

On success:

On failure:

Example

# Create a device keyed 'heatpump4789' with 2 attached sensors
device = client.create_device('heatpump4789', 'Basement Heat Pump',
                              'building' => '445 W Erie', 'model' => '75ZX',
                              TempoIQ::Sensor.new('temp-1'), TempoIQ::Sensor.new('pressure-1'))
# File lib/tempoiq/client.rb, line 101
def create_device(key, name = "", attributes = {}, *sensors)
  device = Device.new(key, name, attributes, *sensors)
  remoter.post("/v2/devices", JSON.dump(device.to_hash)).on_success do |result|
    json = JSON.parse(result.body)
    Device.from_hash(json)
  end
end
delete_datapoints(device_key, sensor_key, start, stop) click to toggle source

Delete datapoints by device and sensor key, start and stop date

+ device_key [String] - Device key to read from + sensor_key [String] - Sensor key to read from

  • start [Time] - Read start interval

  • stop [Time] - Read stop interval

On success: _ Return a DeleteSummary describing the number of points deleted On failure:

Example

# Delete data from 'device1', 'temp' from 2013
start = Time.utc(2013, 1, 1)
stop = Time.utc(2013, 12, 31)
summary = client.delete_datapoints('device1', 'temp', start, stop)
puts "Deleted #{summary.deleted} points"
# File lib/tempoiq/client.rb, line 433
def delete_datapoints(device_key, sensor_key, start, stop)
  delete_range = {:start => start.iso8601(3), :stop => stop.iso8601(3)}
  result = remoter.delete("/v2/devices/#{URI.escape(device_key)}/sensors/#{URI.escape(sensor_key)}/datapoints", JSON.dump(delete_range))
  case result.code
  when HttpResult::OK
    json = JSON.parse(result.body)
    DeleteSummary.new(json['deleted'])
  else
    raise HttpException.new(result)
  end
end
delete_device(device_key) click to toggle source

Delete a device by key

  • device_key [String] - The device key to delete by

On succces:

On failure:

Example

# Delete device keyed 'heatpump4576'
deleted = client.delete_device('heatpump4576')
if deleted
  puts "Device was deleted"
end
# File lib/tempoiq/client.rb, line 170
def delete_device(device_key)
  result = remoter.delete("/v2/devices/#{URI.escape(device_key)}")
  case result.code
  when HttpResult::OK
    true
  when HttpResult::NOT_FOUND
    false
  else
    raise HttpException.new(result)
  end
end
delete_devices(selection) click to toggle source

Delete a set of devices by Selection criteria

On success:

On failure:

Example

# Delete all devices in building 'b4346'
summary = client.delete_devices(:devices => {:attributes => {'building' => 'b4346'}})
puts "Number of devices deleted: #{summary.deleted}"
# File lib/tempoiq/client.rb, line 195
def delete_devices(selection)
  query = Query.new(Search.new("devices", selection),
                    Find.new,
                    nil)

  remoter.delete("/v2/devices", JSON.dump(query.to_hash)).on_success do |result|
    json = JSON.parse(result.body)
    DeleteSummary.new(json['deleted'])
  end
end
get_device(device_key) click to toggle source

Fetch a device by key

  • device_key [String] - The device key to fetch by

On success:

  • Returns the Device found, nil when not found

On failure:

Example

# Lookup the device keyed 'heatpump4789'
device = client.get_device('heatpump4789')
device.sensors.each { |sensor| puts sensor.key }
# File lib/tempoiq/client.rb, line 122
def get_device(device_key)
  result = remoter.get("/v2/devices/#{URI.escape(device_key)}")
  case result.code
  when HttpResult::OK
    json = JSON.parse(result.body)
    Device.from_hash(json)
  when HttpResult::NOT_FOUND
    nil
  else
    raise HttpException.new(result)
  end
end
latest(selection, pipeline = Pipeline.new) { |pipeline| ... } click to toggle source

Read the latest point from a set of Devices / Sensors, with an optional functional pipeline to transform the values.

  • selection [Selection] - Device selection, describes which Devices / Sensors we should operate on

  • pipeline [Pipeline] (optional)- Functional pipeline transformation. Supports analytic computation on a stream of DataPoints.

On success:

On failure:

Example

# Find the latest DataPoints from Device 'bulding4567' Sensor 'temp1'
rows = client.latest({:devices => {:key => 'building4567'}, :sensors => {:key => 'temp1'}})
rows.each do |row|
  puts "Data at timestamp: #{row.ts}, value: #{row.value('building4567', 'temp1')}"
end
# File lib/tempoiq/client.rb, line 365
def latest(selection, pipeline = Pipeline.new, &block)
  if block_given?
    yield pipeline
  end

  query = Query.new(Search.new("devices", selection),
                    Single.new(:latest),
                    pipeline)

  Cursor.new(Row, remoter, "/v2/single", query)
end
list_devices(selection = {:devices => "all"}, opts = {}) click to toggle source

Search for a set of devices based on Selection criteria

On success:

On failure:

Example

# Select devices in building in the Evanston region
client.list_devices(:devices => {:and => [{:attribute_key => 'building'}, {:attributes => {'region' => 'Evanston'}}]})
# File lib/tempoiq/client.rb, line 147
def list_devices(selection = {:devices => "all"}, opts = {})
  query = Query.new(Search.new("devices", selection),
                    Find.new(opts[:limit]),
                    nil)
  Cursor.new(Device, remoter, "/v2/devices", query, media_types(:accept => [media_type("device-collection", "v2"), media_type("error", "v1")],
                                                                :content => media_type("query", "v1")))
end
read(selection, start, stop, pipeline = Pipeline.new, opts = {}) { |pipeline| ... } click to toggle source

Read from a set of Devices / Sensors, with an optional functional pipeline to transform the values.

  • selection [Selection] - Device selection, describes which Devices / Sensors we should operate on

  • start [Time] - Read start interval

  • stop [Time] - Read stop interval

  • pipeline [Pipeline] (optional)- Functional pipeline transformation. Supports analytic computation on a stream of DataPoints.

On success:

On failure:

Examples

# Read raw datapoints from Device 'bulding4567' Sensor 'temp1'
start = Time.utc(2014, 1, 1)
stop = Time.utc(2014, 1, 2)
rows = client.read({:devices => {:key => 'building4567'}, :sensors => {:key => 'temp1'}}, start, stop)
rows.each do |row|
  puts "Data at timestamp: #{row.ts}, value: #{row.value('building4567', 'temp1')}"
end

# Find the daily mean temperature in Device 'building4567' across sensors 'temp1' and 'temp2'
start = Time.utc(2014, 1, 1)
stop = Time.utc(2014, 2, 2)
rows = client.read({:devices => {:key => 'building4567'}, :sensors => {:key => 'temp1'}}, start, stop) do |pipeline|
  pipeline.rollup("1day", :mean, start)
  pipeline.aggregate(:mean)
end

rows.each do |row|
  puts "Data at timestamp: #{row.ts}, value: #{row.value('building4567', 'temp1')}"
end
# File lib/tempoiq/client.rb, line 335
def read(selection, start, stop, pipeline = Pipeline.new, opts = {}, &block)
  if block_given?
    yield pipeline
  end

  query = Query.new(Search.new("devices", selection),
                    Read.new(start, stop, opts[:limit]),
                    pipeline)

  Cursor.new(Row, remoter, "/v2/read", query, media_types(:accept => [media_type("datapoint-collection", "v2"), media_type("error", "v1")],
                                                          :content => media_type("query", "v1")))
end
read_device_sensor(device_key, sensor_key, start, stop, pipeline = nil, &block) click to toggle source

Convenience function to read from a single Device, and single Sensor

+ device_key [String] - Device key to read from + sensor_key [String] - Sensor key to read from

  • start [Time] - Read start interval

  • stop [Time] - Read stop interval

  • pipeline [Pipeline] (optional)- Functional pipeline transformation. Supports analytic computation on a stream of DataPoints.

On success:

On failure:

Example

# Read from 'device1', 'temp1'
start = Time.utc(2014, 1, 1)
stop = Time.utc(2014, 1, 2)
datapoints = client.read_device_sensor('device1', 'temp1', start, stop)
datapoints.each do |point|
  puts "DataPoint ts: #{point.ts}, value: #{point.value}"
end
# File lib/tempoiq/client.rb, line 466
def read_device_sensor(device_key, sensor_key, start, stop, pipeline = nil, &block)
  selection = {:devices => {:key => device_key}, :sensors => {:key => sensor_key}}
  read(selection, start, stop, pipeline).map do |row|
    sub_key = row.values.map { |device_key, sensors| sensors.keys.first }.first || sensor_key
    DataPoint.new(row.ts, row.value(device_key, sub_key))
  end
end
single(selection, function, timestamp = nil, pipeline = Pipeline.new) { |pipeline| ... } click to toggle source

Read a single point from a set of Devices / Sensors, with an optional functional pipeline to transform the values.

  • selection [Selection] - Device selection, describes which Devices / Sensors we should operate on

  • function [Symbol] - The type of single point query to perform. One of:

    • :earliest - get the earliest points from the selection

    • :latest - get the latest points from the selection

    • :before - get the nearest points before the timestamp

    • :after - get the nearest points after the timestamp

    • :exact - get the points exactly at the timestamp if any

    • :nearest - get the nearest points to the timestamp

  • timestamp [Time] (optional)- Time, if any to apply the function to. Not necessary for earliest or latest.

  • pipeline [Pipeline] (optional)- Functional pipeline transformation. Supports analytic computation on a stream of DataPoints.

On success:

On failure:

Example

# Find the last DataPoint from Device 'bulding4567' Sensor 'temp1' before January 1, 2013
rows = client.single({:devices => {:key => 'building4567'}, :sensors => {:key => 'temp1'}}, :before, Time.utc(2013, 1, 1))
rows.each do |row|
  puts "Data at timestamp: #{row.ts}, value: #{row.value('building4567', 'temp1')}"
end
# File lib/tempoiq/client.rb, line 402
def single(selection, function, timestamp = nil, pipeline = Pipeline.new, &block)
  if block_given?
    yield pipeline
  end

  query = Query.new(Search.new("devices", selection),
                    Single.new(function, timestamp),
                    pipeline)

  Cursor.new(Row, remoter, "/v2/single", query, media_types(:accept => [media_type("datapoint-collection", "v1"), media_type("error", "v1")],
                                                            :content => media_type("query", "v1")))
end
update_device(device) click to toggle source

Update a device

  • device - Updated Device object.

On success:

On failure:

Example

# Get a device and update it's name
device = client.get_device('building1234')
if device
  device.name = "Updated name"
  client.update_device(device)
end
# File lib/tempoiq/client.rb, line 223
def update_device(device)
  remoter.put("/v2/devices/#{URI.escape(device.key)}", JSON.dump(device.to_hash)).on_success do |result|
    json = JSON.parse(result.body)
    Device.from_hash(json)
  end
end
write_bulk(bulk_write = nil) { |bulk| ... } click to toggle source

Write multiple datapoints to multiple device sensors. This function is generally useful for importing data to many devices at once.

  • bulk_write - The write request to send to the backend. Yielded to the block.

On success:

On partial success:

On failure:

Example

# Write to 'device1' and 'device2' with different sensor readings
status = client.write_bulk do |write|
  ts = Time.now
  write.add('device1', 'temp1', TempoIQ::DataPoint.new(ts, 1.23))
  write.add('device2', 'temp1', TempoIQ::DataPoint.new(ts, 2.34))
end

if status.succes?
  puts "All datapoints written successfully"
elsif status.partial_success?
  status.failures.each do |device_key, message|
    puts "Failed to write #{device_key}, message: #{message}"
  end
end
# File lib/tempoiq/client.rb, line 257
def write_bulk(bulk_write = nil, &block)
  bulk = bulk_write || BulkWrite.new
  if block_given?
    yield bulk
  elsif bulk_write.nil?
    raise ClientError.new("You must pass either a bulk write object, or provide a block")
  end

  result = remoter.post("/v2/write", JSON.dump(bulk.to_hash))
  if result.code == HttpResult::OK || result.code == HttpResult::MULTI
    body = result.body.empty? ? "{}" : result.body
    json = JSON.parse(body)
    WriteResponse.new(json)
  else
    raise HttpException.new(result)
  end
end
write_device(device_key, ts, values) click to toggle source

Write to multiple sensors in a single device, at the same timestamp. Useful for 'sampling' from all the sensors on a device and ensuring that the timestamps align.

  • device_key [String] - Device key to write to

  • ts [Time] - Timestamp that datapoints will be written at

  • values [Hash] - Hash from sensor_key => value

On success:

  • Return true

On failure:

Example

ts = Time.now
status = client.write_device('device1', ts, 'temp1' => 4.0, 'temp2' => 4.2)
if status.succes?
  puts "All datapoints written successfully"
end
# File lib/tempoiq/client.rb, line 294
def write_device(device_key, ts, values)
  bulk = BulkWrite.new
  values.each do |sensor_key, value|
    bulk.add(device_key, sensor_key, DataPoint.new(ts, value))
  end
  write_bulk(bulk).success?
end

Private Instance Methods

media_type(media_resource, media_version, suffix = "json") click to toggle source
# File lib/tempoiq/client.rb, line 483
def media_type(media_resource, media_version, suffix = "json")
  "#{MEDIA_PREFIX}.#{media_resource}.#{media_version}+#{suffix}"
end
media_types(types) click to toggle source
# File lib/tempoiq/client.rb, line 476
def media_types(types)
  {
    "Accept" => types[:accept],
    "Content-Type" => types[:content]
  }
end