Message Format

Download all protocol buffer files from this SAF deployment.

common.proto

// NOTE:
// Type "required" is not used in this file intentionally. To be as flexible as
// possible and accommodate unknown future changes, type "required" is being
// avoided. Fields that are currently required by the implementation's data
// checking algorithms are noted in the comments.

package saf;

option java_package = "edu.caltech.saf.messages";
option java_outer_classname = "Common";

// Used in all requests by existing clients to verify message origin.
// The client_id field is sent in the endpoint of the request.
message ClientSignature {
    // REQUIRED: ISO date format in UTC -> yyyy-MM-ddTHH:mm:ss.SSS
    optional string date = 1;
    // REQUIRED: always incrementing index of message id.
    optional uint32 message_id = 2;
    // REQUIRED: SHA256 hash of '{date}{message_id}{secret}'.
    optional string signature = 3;
}

message StatusMessage {
    // REQUIRED: indicates the success or failure of the request.
    optional StatusType type = 1;
    // OPTIONAL: provides more information on the success or failure
    optional string message = 2;
}

// If all response messages contain "StatusMessage status = 1" in the first
// slot, then a GenericResponse message should be parseable for any response
// type. This is useful to return a protocol buffer encoded 404, when we
// wouldn't know what response type to use, or when a critical error makes it
// impossible to return the appropriate response type in the normal flow of
// execution.
// TODO: Useful? Seems best use case is 404, but that can be read by the status
// code. If a blob can be returned when scripts can't be executed, it might be
// nice (return a static GenericResponse/AppEngineDown).
message GenericResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;
}

// Used to indicate success or failure of requests in response messages.
enum StatusType {
    // When no errors exist, SUCCESS is indicated. It may or may not
    // have an accompanying explanatory text.
    SUCCESS = 0;
    // Thrown if no suitable error category is available. A new category
    // can be added; in the interim, fill in the response message when using
    // this type.
    UNKNOWN_ERROR = 1;
    // Thrown if expected fields were missing or contained invalid
    // content. Invalid content should be identified in the response message.
    INVALID_REQUEST = 2;
    // Thrown if provided client identifier was not found in the
    // datastore. This is fatal and cannot be retried.
    UNKNOWN_CLIENT = 3;
    // Thrown if provided sensor identifier was not found in the datastore.
    // This is fatal and cannot be retried.
    UNKNOWN_SENSOR = 4;
    // Thrown if a problem exists persisting data in the datastore.
    // If the submitted data is not transient, then, if possible,
    // it should not be discarded, but retried later. For
    // instance, transient pick messages can be discarded, but
    // waveform data submitted with a heartbeat should be retried
    // for submission.
    DATASTORE_ERROR = 5;
    // Thrown when the App Engine time limit is exceeded. Should be retried.
    DEADLINE_EXCEEDED = 6;
    // These three errors are served using error handlers, not directly by
    // server code. The first two are likely critical and, at the least, result
    // in a period of time where retries will not succeed. The SERVER_TIMEOUT
    // should be retriable.
    OVER_QUOTA_ERROR = 7;
    DOS_API_DENIAL = 8;
    SERVER_TIMEOUT = 9;
    // Protocol buffer formatted 404. Can also just check the response code.
    PATH_NOT_FOUND = 10;
    // Thrown on registration or update if a requested name is unavailable.
    NAME_UNAVAILABLE = 15;
}

// Type of sensor attached to client software.
enum SensorType {
    ACCELEROMETER_3_AXIS = 0;
    ACCELEROMETER_1_AXIS = 1;
    TEMPERATURE = 2;
    PRESSURE = 3;
    HUMIDITY = 4;
    OPTICAL = 5;
    PHOTOVOLTAIC = 6;
    CH4 = 7;
    LPG = 8;
    CO = 9;
    H2 = 10;
    GEIGER = 11;
    // Get type added and update type.
    OTHER = 64;
}

enum LocationSourceType {
    USER_INPUT = 0;
    CELL = 1;
    GPS = 2;
    WIFI = 3;
    IP = 4;
    SERVER_DEFAULT = 64;
}

event.proto

// NOTE:
// Type "required" is not used in this file intentionally. To be as flexible as
// possible and accommodate unknown future changes, type "required" is being
// avoided. Fields that are currently required by the implementation's data
// checking algorithms are noted in the comments.

package saf;

option java_package = "edu.caltech.saf.messages";
option java_outer_classname = "Event";

import "common.proto";

// Sent whenever an event occurs.
// Endpoint: /api/v1/{namespace}/event/create/{client_id}
message EventMessage {
    // REQUIRED: used to verify message origin.
    optional ClientSignature client_signature = 1;

    // OPTIONAL: location if changed since last heartbeat.
    optional double latitude = 2;
    optional double longitude = 3;
    optional LocationSourceType location_type = 4;

    message SensorEvent {
        // REQUIRED: id of sensor.
        optional uint64 sensor_id = 1;

        // REQUIRED: type of sensor (avoid datastore trip).
        optional SensorType sensor_type = 2;

        // REQUIRED: sensor reading/s at time of event.
        repeated double readings = 3 [packed=true];

        // OPTIONAL: time if different from envelope.
        optional string date = 4;

        // REQUIRED: event count over last window seconds time window.
        // Used to determine noisiness of sensor.
        optional int32 event_count = 5;
        optional int32 time_window = 6;
    }
    // REQUIRED: readings from the sensor detecting an event.
    optional SensorEvent event = 5;

    // OPTIONAL: readings from other related sensors.
    repeated SensorEvent other_readings = 6;
}

// Indicates success or failure of event messages.
message EventResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;
}

heartbeat.proto

// NOTE:
// Type "required" is not used in this file intentionally. To be as flexible as
// possible and accommodate unknown future changes, type "required" is being
// avoided. Fields that are currently required by the implementation's data
// checking algorithms are noted in the comments.

package saf;

option java_package = "edu.caltech.saf.messages";
option java_outer_classname = "Heartbeat";

import "common.proto";
import "client_messages.proto";

// Sent once every 10 minutes per client.
// Endpoint: /api/v1/{namespace}/heartbeat/{client-id}
message HeartbeatMessage {
    // REQUIRED: used to verify message origin.
    optional ClientSignature client_signature = 1;

    // OPTIONAL: location if changed since last heartbeat.
    optional double latitude = 2;
    optional double longitude = 3;
    optional LocationSourceType location_source_type = 4;

    // Used to notify the server that the state of the client software has
    // changed.  When not present, the client is presumed alive. When this value
    // is populated, the client is presumed to be going offline for some reason.
    // These types provide an attempt to classify the reason.
    enum StatusChange {
        // Software update, should be temporary.
        UPDATE = 0;
        // Client computer being restarted, put to sleep, or shut down.
        RESTART = 1;
        SLEEP = 2;
        SHUTDOWN = 3;
        // Backend process was killed by a nice kill signal.
        KILLED = 4;
        // Backend process was explicitly quit, e.g. telling the service to stop.
        QUIT = 5;
    }
    // OPTIONAL: indication that client is going offline.
    optional StatusChange status_change = 5;

    // OPTIONAL: ids of sensors that have valid data.
    repeated uint64 connected_sensors = 6;
    // OPTIONAL: ids of sensors that are unavailable.
    repeated uint64 disconnected_sensors = 7;

    // OPTIONAL: id of last received data request.
    optional uint64 latest_request_id = 8;

    // OPTIONAL: data offer for current data. URL is returned if current data
    // is requested. Offer can also be submitted to data/offer, but, if it is
    // known in advance that data will be requested, then making the offer in
    // the heartbeat saves a roundtrip. Clients should continue to offer data
    // in the heartbeat for sensors that continue to receive data requests for
    // ongoing data.
    repeated SensorReadingDescription current_data_offers = 9;

    message SensorEventRate {
        // REQUIRED: id of sensor.
        optional uint64 sensor_id = 1;

        // REQUIRED: event count over last window seconds time window.
        // Used to determine noisiness of sensor.
        optional int32 event_count = 2;
        optional int32 time_window = 3;
    }
    // REQUIRED: current event rate of client's sensors.
    repeated SensorEventRate event_rates = 10;
}

// Indicates the success of the heartbeat and provides requests / updates to
// the client.
message HeartbeatResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // Used to request that the client upload data to the server.
    message DataRequest {
        // REQUIRED: sensor from which data is requested.
        optional uint64 sensor_id = 1;

        // REQUIRED: indicates whether request is date based or ongoing.
        optional bool provide_current_data = 2;
        // REQUIRED if provide_current_data is false: id of the data request.
        optional uint64 request_id = 3;

        // REQUIRED if provide_current_data is false: date range of request.
        // Dates are ISO date format in UTC -> yyyy-MM-ddTHH:mm:ss.SSS
        optional string start_date = 4;
        optional string end_date = 5;

        // OPTIONAL: if specified, defines a distance from a central point or
        // points that a client must be within in order to answer the request.
        // If multiple points are provided, they define line segments. Clients
        // can optionally compute their distance to the closest segment, if
        // possible, or, lacking that, their distance to the closest vertex.
        repeated double latitude = 6;
        repeated double longitude = 7;
        optional uint32 threshold_distance = 8;
    }
    // OPTIONAL: used to request timeframes of data from the client.
    repeated DataRequest data_requests = 2;

    // REQUIRED: used to identify the most recent data request in the system.
    // Data requests will not be returned to clients that do not have
    // sensors that match the request, so this field marks the most recent
    // request in the system and should be replayed in heartbeats to determine
    // whether or not newer requests exist.
    optional uint64 latest_request_id = 3;

    // OPTIONAL: if current data was offered, the upload URL will be returned
    // here. If current data was requested, but not offered, the offer will
    // have to be provided to data/offer.
    repeated string current_data_upload_urls = 4;

    // OPTIONAL: when set to true, implies that client should access the
    // metadata handler to see the client's new metadata. Functions as a dirty
    // bit.
    optional bool metadata_changed = 5;
}

message SensorReadingDescription {
    // REQUIRED: id of sensor responding to the request.
    optional uint64 sensor_id = 1;

    // REQUIRED one of current_data or request_id.
    optional bool current_data = 2;
    optional uint64 request_id = 3;

    // REQUIRED: starting and ending time of the provided data.
    // Dates are ISO date format in UTC -> yyyy-MM-ddTHH:mm:ss.SSS
    optional string start_date = 4;
    optional string end_date = 5;

    // OPTIONAL: required for mobile sensors. Starting location for sensor.
    // Required for any sensor that had a different position at the time of
    // data acquisition than it does now.
    optional double latitude = 6;
    optional double longitude = 7;

    enum DataFormat {
        CSNV2 = 0;
        DATA_RECORD = 1;
    }
    // REQUIRED: describes the format of the data that will be uploaded.
    optional DataFormat data_format = 8;
}

// Used to offer data to the server; if the data is desired, an upload URL will
// be returned in response.
// Endpoint: /api/v1/{namespace}/data/offer/{client-id}
message SensorReadingOfferMessage {
    // REQUIRED: used to verify message origin.
    optional ClientSignature client_signature = 1;

    // REQUIRED: one or more descriptions of data being offered.
    repeated SensorReadingDescription sensor_readings = 2;
}

// Returns upload URLs for offered data.
message SensorReadingOfferResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // REQUIRED on success: list of strings of length equal to the number of
    // readings described and corresponding to the offers exactly. The correct
    // URL for each reading must be used, as the URL embeds the metadata
    // provided in the offer. An upload URL is provided if the data is desired,
    // otherwise the empty string is returned.
    // For instance, if 4 sensors are attached, and 4 offers for current data
    // are made, but only data from the 1st sensor is desired, if the offer
    // looks like [sensor_id=0, sensor_id=1, sensor_id=2, sensor_id=3], then
    // the response will look like [url, '', '', ''].
    repeated string data_upload_urls = 2;
}

// Returned after a file is uploaded.
message SensorReadingUploadResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // REQUIRED on success: the datastore id of the metadata for the uploaded
    // file. Can be used to redownload the file.
    optional uint64 sensor_reading_id = 2;
}

// Used to encode a large sequence of readings.
message DataStream {
    // REQUIRED: used to indicate values from the entire data interval.
    repeated double values = 1 [packed=true];
}

// One possible format for submitting a sequence of readings.
message DataRecord {
    // REQUIRED: Describes data point spacing in milliseconds. At 50 samples per
    // second, this would be 20. At 1 sample per minute, it would be 60,000.
    // NOTE: int32 milliseconds is ~25 days, which should be enough.
    optional uint32 datapoint_spacing = 1;

    // REQUIRED: each record occurs at record_spacing distance apart, with the
    // first record occurring at start_date and the second record occurring at
    // start_date + datapoint_spacing milliseconds. Each DataStream contains the
    // set of values for the entire data interval. That is, a 3-axis
    // accelerometer would send 3 DataStream objects, each of which would be of
    // the exact same length and record a different axis.
    repeated DataStream streams = 2;

    // OPTIONAL: used by mobile clients to indicate different positions during
    // data stream. Location changes are indicated as millisecond offsets from
    // the data start time and are packed into uint32 and double arrays to save
    // space.
    message LocationChanges {
        repeated uint32 time_offsets = 1 [packed=true];
        repeated double latitudes = 2 [packed=true];
        repeated double longitudes = 3 [packed=true];
    }
    optional LocationChanges locations = 3;
}

client_messages.proto

// NOTE:
// Type "required" is not used in this file intentionally. To be as flexible as
// possible and accommodate unknown future changes, type "required" is being
// avoided. Fields that are currently required by the implementation's data
// checking algorithms are noted in the comments.

package saf;

option java_package = "edu.caltech.saf.messages";
option java_outer_classname = "ClientMessages";

import "common.proto";

// Create a new client in the system.
// Endpoint: /api/v1/{namespace}/client/create
message ClientCreateMessage {
    // OPTIONAL: data describing client device.
    optional ClientMetadata client_data = 1;
    // OPTIONAL: data describing sensors attached to device (ids returned).
    repeated SensorMetadata attached_sensors = 2;
}

// Indicates success in creating a new client and provides the new client
// id and secret along with the sensor ids.
message ClientCreateResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // REQUIRED: new client id.
    optional uint64 client_id = 2;
    // REQUIRED: used to salt signatures.
    optional string client_secret = 3;

    // OPTIONAL: ids of attached sensors in the order submitted.
    repeated uint64 sensor_ids = 4;

    // REQUIRED: id of most recent data request. New clients do not need to be
    // informted of old requests; if the client is truly new, there is no way
    // that it could provide data for an old request, and it should set its
    // last_data_request field to this value to prevent receiving old data
    // requests that are not applicable.
    // If no requests exist in the system, this value will be 0, which is not
    // a valid request id.
    optional uint64 latest_request_id = 5;
}

// Update an existing clients data.
// Endpoint: /api/v1/{namespace}/client/update/{client_id}
message ClientUpdateMessage {
    // REQUIRED: used to verify message origin.
    optional ClientSignature client_signature = 1;

    // OPTIONAL: updated client metadata.
    optional ClientMetadata client_data = 2;
    // OPTIONAL: newly attached sensors (ids returned in response).
    repeated SensorMetadata new_sensors = 3;
    // OPTIONAL: updated sensors (no ids returned).
    repeated SensorMetadata updated_sensors = 4;
    // OPTIONAL: sensors no longer available to client.
    repeated uint64 removed_sensors = 5 [packed=true];
}

// Indicates success in creating a new client and provides the new client
// id and secret along with the sensor ids.
message ClientUpdateResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // OPTIONAL: ids of newly attached sensors in the order submitted.
    repeated uint64 sensor_ids = 2;
}

// Contains all of the client level metadata.
message ClientMetadata {
    // OPTIONAL: location; defaults to 0, 0, which is not particularly useful.
    optional double latitude = 1;
    optional double longitude = 2;
    // OPTIONAL: source of location data.
    optional LocationSourceType location_source_type = 3;

    // OPTIONAL: name to describe client with. Used with numeric id for
    // presentation / differentiation purposes.
    optional string name = 4;

    // OPTIONAL: location description, freeform text field.
    optional string location_description = 5;

    // OPTIONAL: floor of the building client is located on.
    optional string floor = 6;

    // OPTIONAL: name of building client is located in.
    // Useful for identifying groups of sensors located in the same building.
    optional string building = 7;

    enum MobilityType {
        // Orange box and other dedicated sensor platforms.
        STANDALONE = 0;
        // Sensor/s piggyback on a client desktop.
        DESKTOP = 1;
        // Semimobile sensor platform.
        LAPTOP = 2;
        TABLET = 3;
        PHONE = 4;
        OTHER = 15;
    }
    // OPTIONAL: extent to which client may move around.
    optional MobilityType mobility_type = 8;

    // OPTIONAL: version of software client is running.
    optional string software_version = 9;

    // OPTIONAL: operating system client is running on.
    optional string operating_system = 10;

    // OPTIONAL: client provided and generated UUID used to fetch an old
    // registration if the client id is lost due to an uninstall or crash.
    optional string uuid = 14;

    // OPTIONAL: other key/value pairs to store in the datastore.
    repeated KeyValue other_metadata = 15;

    // OPTIONAL: describes legacy identifier from old system.
    optional string legacy_identifier = 128;
}

// Contains data relevant to a particular sensor.
message SensorMetadata {
    // REQUIRED for update: numeric id of sensor.
    optional uint64 sensor_id = 1;

    // REQUIRED: type of sensor.
    optional SensorType sensor_type = 2;

    // OPTIONAL: model of sensor, e.g. Phidget v2.3 (12-bit).
    optional string model = 3;

    // OPTIONAL: serial of sensor - should be unique in combination with model.
    optional string serial = 4;

    // OPTIONAL: is sensor calibrated?
    optional bool calibrated = 5;

    // OPTIONAL: units sensor reports (if calibrated).
    optional string units = 6;

    // Sample rate of unit expressed as quantity per unit time
    // 50 samples per second would send (50, 1) while 1 sample per hour would
    // send (1, 3600).
    // OPTIONAL: raw number of samples.
    optional int32 num_samples = 7;
    // OPTIONAL: in time window seconds.
    optional int32 sample_window_size = 8;

    // OPTIONAL: other key/value pairs to store in the datastore.
    // Disabled for now. As a StructuredProperty of Client, all sensor metadata
    // would be indexed, even if dynamically allocated. We want to avoid that.
    //repeated KeyValue other_metadata = 15;
}

// Client software specific parameters can be submitted this way.
message KeyValue {
    // REQUIRED: key to store value under.
    optional string key = 1;
    // REQUIRED: one of string, double, long, or bool value.
    // Presence of values is checked in this order.
    optional string string_value = 2;
    optional double double_value = 3;
    optional int64 long_value = 4;
    optional bool bool_value = 5;
    // OPTIONAL: set on a value request if the specified field is unset.
    optional bool unset = 15;
}

// Work in progress.
message NotificationRegistrationMessage {
    // REQUIRED: how will the client be notified.
    enum RegistrationType {
        GCM = 0;
    }
    // Topics to listen for notifications on. One possible topic is 'csn'.
    optional string topic = 1;
}

message NotificationRegistrationResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;
}

// Used to authenticate local user to web editor.
// Endpoint: /api/v1/{namespace}/client/edit-token/{client_id}
message ClientEditorTokenRequest {
    // REQUIRED: used to verify message origin.
    optional ClientSignature client_signature = 1;
}

// Returns a token that can be supplied to the client editor to permit viewing
// and editing a sensor's data without authentication.
message ClientEditorTokenResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // REQUIRED on success: url to provide for user to edit their client
    // details.
    optional string editor_url = 2;
    // REQUIRED on success: raw token value.
    optional string token = 3;
}

// Used to request the value of specific metadata keys.
// Endpoint: /api/v1/{namespace}/client/metadata/{client_id}
message ClientMetadataRequest {
    // REQUIRED: used to verify message origin.
    optional ClientSignature client_signature = 1;

    optional bool include_sensor_data = 2;
}

// Returns the values of requested keys or indicates that they are unset.
message ClientMetadataResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // OPTIONAL: client keys checked and their values.
    optional ClientMetadata client_data = 2;
    // OPTIONAL: sensor keys checked and their values (in the order provided).
    repeated SensorMetadata sensor_data = 3;
}

// Used to request the value of specific metadata keys.
// Endpoint: /api/v1/{namespace}/client/get-keys/{client_id}
message ClientKeyValueRequest {
    // REQUIRED: used to verify message origin.
    optional ClientSignature client_signature = 1;

    // OPTIONAL: list of client keys to check.
    repeated string client_keys = 2;
}

// Returns the values of requested keys or indicates that they are unset.
message ClientKeyValueResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // OPTIONAL: client keys checked and their values.
    repeated KeyValue client_data = 2;
}

query_messages.proto

// NOTE:
// Type "required" is not used in this file intentionally. To be as flexible as
// possible and accommodate unknown future changes, type "required" is being
// avoided. Fields that are currently required by the implementation's data
// checking algorithms are noted in the comments.

package saf;

option java_package = "edu.caltech.saf.messages";
option java_outer_classname = "QueryMessages";

import "common.proto";
import "client_messages.proto";

// Used to check if a client name is available.
// Endpoint: /api/v1/{namespace}/client/check-name
message NameAvailableQuery {
    // REQUIRED: name to check.
    optional string name = 1;
}

// Used to indicate if the specified name is available.
message NameAvailableResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // REQUIRED: name checked.
    optional string name = 2;
    // REQUIRED: whether or not name is available.
    optional bool available = 3;
}

// Used to fetch an old registration from the datastore by a client provided
// UUID.
// Endpoint: /api/v1/{namespace}/client/find-uuid
message ExistingUuidQuery {
    // Search for an old registration with this client UUID.
    optional string client_uuid = 1;
}

// Returns whether or not a matching registration was found and the client id
// if the registration was found.
message ExistingUuidResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // REQUIRED on success: true if an existing registration was found.
    optional bool registration_found = 2;

    // REQUIRED if registration_found: client_id of old registration.
    optional uint64 client_id = 3;
    // REQUIRED if registration_found: secret for old registration.
    optional string client_secret = 4;
    // OPTIONAL: all sensors attached to the old registration.
    repeated SensorMetadata sensors = 5;
}

// Return the new identifier of a legacy client id.
message ClientFromLegacyIdQuery {
    // REQUIRED: one of the old id or old key.
    optional string legacy_client_key = 1;
    optional uint64 legacy_client_id = 2;
}

// Return the current identifier for the requested legacy identifier.
message ClientFromLegacyIdResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // REQUIRED on success: supplies the client with the new identifier.
    optional uint64 client_id = 2;
}

message RecentEventQuery {
    // OPTIONAL: if not specified, defined by the server. Maximum of 1000. If
    // limit events are not available, all available events will be returned.
    optional uint32 limit = 1;
    // OPTIONAL: if specified, only return events from sensors of this type.
    optional SensorType sensor_type = 2;

    // OPTIONAL: if set to true, ignore after_event_id and return most recent
    // events (determined by limit).
    optional bool most_recent_events = 3;
    // OPTIONAL: return all events after the specified event id.
    optional uint64 after_event_id = 4;

    // OPTIONAL: set to true if the bounding box for each geocell returned is
    // needed by the client.
    optional bool provide_polygons = 15;
}

message RecentEventResponse {
    // REQUIRED: indicates success of submission or its error status.
    optional StatusMessage status = 1;

    // REQUIRED on success: used to fetch newer events.
    optional uint64 most_recent_event_id = 2;

    message SensorEvent {
        // REQUIRED if no type specified in query: type of sensor
        // If sensor_type was set in the query message, then all events are
        // guaranteed to be of that type and this field is not populated.
        optional SensorType sensor_type = 1;

        // REQUIRED: values provided by sensor when submitting the event.
        repeated double readings = 2;

        // REQUIRED: time of sensor event.
        // Dates are ISO date format in UTC -> yyyy-MM-ddTHH:mm:ss.SSS
        optional string date = 3;

        // REQUIRED: geocell that event was reported in.
        // Provided as a geocell string.
        optional string geocell = 4;
    }
    repeated SensorEvent events = 3;

    // REQUIRED if provide_polygons == true. Provides the bounding box that
    // describes each geocell included in events to facilitate plotting the
    // geocells on a map even for clients that can't construct geocells.
    repeated string geocells_included = 14;
    message BoundingBox {
        optional double west_longitude = 1;
        optional double east_longitude = 2;
        optional double north_latitude = 3;
        optional double south_latitude = 4;
    }
    repeated BoundingBox geocell_bounds = 15;
}

Table Of Contents

Previous topic

Developer Guide

Next topic

saf

This Page