Skip to main content

Sending Cozie data

Data flow from Cozie app to database

Overview

The Cozie app calls the AWS API Gateway using the API Gateway Key. The API Write URL and API Write Key are provided in the 'Backend' tab of the Cozie app. The API Gateway checks the API key and then forwards the request to the Lambda function 'cozie-apple-v3-app-write-queue'. This Lambda function splits the payload into chunks of 100 rows each and inserts it into the SQS queue 'cozie-apple-app-write-influx-queue'.

The SQS queue, then triggers the Lambda function 'cozie-apple-v3-app-write-influx-queue' which then processes the payload and inserts it into InfluxDB. The processing includes:

  • checking the datatype of field values
  • adding timestamp_lambda, transmit_trigger, _lambda, _trigger to the row
  • SQL sanitization

Changes in the Cozie app

In order to have Cozie app send the data to your own backend, you need to update API Write URL and the API Write Key in the 'Backend' tab of the Cozie app.

Payload data structure

The Cozie app sends data to the backend in the same payload format for all types of data. The payload format is inspired by the InfluxDB Python client and how it can use dictionaries as input. The example below shows a minimal payload that is sent from the Cozie app to the backend. It represents one row in a InfluxDB table or measurement. All data, i.e., watch survey, HealthKit data, and GPS data follows this structure. The field names listed under fields are listed in the data overview, e.g., instead of example_integer you could use ts_HRV.

Watch survey payload example
[
{
"time":"2022-09-01T07:02:51.578+0800",
"measurement":"dev",
"tags":{
"id_onesignal":"35E2A783-35DA-4C5F-B54E-5DAC30B6E860",
"id_participant":"dev01",
"id_password":"5DAC30B6E86"
},
"fields":{
"example_integer":3,
"example_string":"test",
}
}
]
Field nameComment
timeTimestamp that is save to InfluxDB
measurementThis is similar to a Table in MySQL. See InfluxDB key concepts for more information. Within the Cozie framework data from each experiment is saved into a separate measurement/table. Hence, the id_experiment and measururement are synonym. In the example above the id_experiment is 'dev'.
tags
id_onesignalPlayer ID provided by OneSignal. This ID is needed to send a push notification to this particular participant using the OneSignal API.
id_participantUnique identifier for Participant
id_passwordPassword for participant. This password should be unique for reach participant. It is needed to send push notifications and retrieve Cozie data using our web API for researchers.
fields
example_integerExample for an integer. Any string can be chosen as field key. The datatype of the field value needs to be checked before insertion into InfluxDB.
example_stringExample for a string.

Payload data structure for watch survey

The example below shows the payload from a watch survey response. The fields with the prefix q_ are defined in the JSON file for the watch survey.

Watch survey payload example
[
{
"time":"2022-09-01T07:02:51.578+0800",
"measurement":"dev",
"tags":{
"id_onesignal":"35E2A783-35DA-4C5F-B54E-5DAC30B6E860",
"id_participant":"dev01",
"id_password":"5DAC30B6E86"
},
"fields":{
"ws_survey_count":3,
"ws_timestamp_start":"2022-09-02T05:03:21.066+0800",
"ws_timestamp_location":"2022-09-02T05:01:22.645+0800",
"ws_longitude":103.77041753262827,
"ws_latitude":1.2965471870539595,
"ws_altitude": 73.4,
"ws_location_floor":3,
"ws_location_accuracy_horizontal":5.4,
"ws_location_accuracy_vertical":2.8,
"ws_location_acquisition_method":"GPS",
"ws_location_source_device":"Apple Watch",
"q_preference_thermal":"No Change",
"q_preference_noise":"Quieter",
"q_noise_source":"Appliances",
"q_headphones":"No",
"q_preference":"Cooler",
"transmit_trigger":"watch_survey",
}
}
]

Payload data structure for HealthKit data

HealthKit payload example
[
{
"time":"2022-09-01T07:42:24.471+0800",
"measurement":"dev",
"tags":{
"id_onesignal":"35E2A783-35DA-4C5F-B54E-5DAC30B6E860",
"id_participant":"dev01",
"id_password":"5DAC30B6E86"
},
"fields":{
"heart_rate":74,
"transmit_trigger":"background_task"
}
},
{
"time":"2022-09-01T07:47:24.471+0800",
"measurement":"dev",
"tags":{
"id_onesignal":"35E2A783-35DA-4C5F-B54E-5DAC30B6E860",
"id_participant":"dev01",
"id_password":"5DAC30B6E86"
},
"fields":{
"heart_rate":74,
"transmit_trigger":"background_task"
}
},
{
"time":"2022-09-01T08:46:30.479+0800",
"measurement":"dev",
"tags":{
"id_onesignal":"35E2A783-35DA-4C5F-B54E-5DAC30B6E860",
"id_participant":"dev01",
"id_password":"5DAC30B6E86"
},
"fields":{
"ts_audio_exposure_environment":57,
"transmit_trigger":"background_task"
}
},
{
"time":"2022-09-02T04:22:59.454+0800",
"measurement":"dev",
"tags":{
"id_onesignal":"35E2A783-35DA-4C5F-B54E-5DAC30B6E860",
"id_participant":"dev01",
"id_password":"5DAC30B6E86"
},
"fields":{
"ts_audio_exposure_environment":60,
"transmit_trigger":"background_task"
}
}
]

Payload data structure for push notification meta data

This payload is currently being developed

The example below shows the payload for meta data from a received push notifications.

Push notification meta data payload example
[
{
"time":"2022-09-01T07:02:51.578+0800",
"measurement":"dev",
"tags":{
"id_onesignal":"35E2A783-35DA-4C5F-B54E-5DAC30B6E860",
"id_participant":"dev01",
"id_password":"5DAC30B6E86"
},
"fields":{
"notification_title":"Test Title",
"notification_subtitle":"Test Subtitle",
"notification_text":"This a notification test text.",
"action_button_shown": "First button label, Second button label, Third button label",
"transmit_trigger":"push_notification_reception",
}
}
]

The field action_button_shown contains and empty string "", if there are no action buttons to be shown.

When an action button is pressed the same payload is submitted again with the following modifications:

  • time field is updated to the time of the button press
  • action_button with the value set to the label of the action button that has been pressed.
  • transmit_trigger field is set to the value "push_notification_action_button"

When the 'Dismiss' button is pressed at the bottom of the push notification, or the push notification is swiped away, the payload is also submitted again with following modifications:

  • time field is updated to the time of the button press
  • transmit_trigger field is set to the value "push_notification_dismiss_button"

Lambda function code

Lambda configuration

cozie-apple-v3-app-write-queue

ConfigurationValueComment
General configuration
Memory2048 MB
Ephemeral storage512 MBdefault value
Timeout0 min 29 secTimeout limit for API Gateway is 30 seconds
Triggers
API Gatewaycozie-apple-v3-researcher-api
Environment variables
SQS_URLhttps:​//sqs.ap-southeast-1.amazonaws.com/XXX/cozie-apple-app-write-influx-queue(replace 'XXX')
Layers
No layers required

cozie-apple-v3-app-write-influx-queue

ConfigurationValueComment
General configuration
Memory2047 MB
Ephemeral storage512 MBdefault value
Timeout4 min 0 sec
Triggers
SQScozie-apple-app-write-influx-queue
Environment variables
DB_HOSTXXX.influxcloud.net(replace 'XXX')
DB_NAMEcozie-apple
DB_PASSWORDXXX(replace 'XXX')
DB_PORT8086
DB_USERCozie-Apple-Lambda-Writer-App-API
Layers
AWSSDKPandas-Python311AWS Layer for Pandas
InfluxCustom layer for InfluxDB client

SQS configuration

ConfigurationValueComment
Details
TypeStandard
Namecozie-apple-app-write-influx-queue
Configuration
Visibility timeout30 seconds
Message retention period4 days
Maximum Message size256KBmaximum value
Delivery delay0 seconds
Receive message wait timeo seconds
Encryption
Server-side encryptionenabled
Encryption key typeAmazon SQS key (SSE-SQS)
Access policy
MethodBasic
Define who can send messages to the queueOnly the queue owner
Define who can receive messages from the queueOnly the queue owner
Redrive allow policyDisabled
Dead-letter queueDisabled

API Gateway configuration

ConfigurationValueComment
Proxy resourcedisableddefault
Resource path/
Resource namewrite-queue
CORS (Cross Origin Resource Sharing)enabled
Method details
Method typeANY
Integration typeLambda function
Lambda proxy integrationenabled
Lambda functionarn:aws:lambda:[region]:[Accound ID]:function:cozie-apple-v3-app-write-queuecheck dropdown menu
Default timeoutenabled29 seconds (default)
Method request settings
AuthorizationNonedefault
Request validatorNonedefault
API key requiredenabled
URL query string parameters
leave default
HTTP request headers
leave default
Request body
leave default

Notes

  • Initially, the write chain was shorter, only including the AWS API Gateway and one lambda function. Overtime, the amount of data logged by the Cozie app increased and the duration for the Lambda function to process the data increased as well. Eventually, it could take 20-30 seconds to process the request. The participant needed to wait this amount of time for the watch app to submit the survey. Python Lambda functions don't support streaming. Hence, Python Lambda functions cannot return a response early. Hence, we decided to make the data transfer from the app to the backend independent from the data insertion into the database.

  • Currently, it the datatype of field values needs to be checked on the backend. Otherwise, one risks loosing data if the datatype varies. This can happen in unfortunate circumstances, e.g., a field value that is normally a float (50.56), but also can happen to be an integer (51). Whatever datatype is inserted first into InfluxDB defines the datatype of the column of a particular experiment ID (or measurement).

    This could be avoided if the datatype is encoded in the payload. This might be addressed in the near future, e.g.,

    Watch survey payload example
     [
    {
    "time":"2022-09-01T07:02:51.578+0800",
    "measurement":"dev",
    "tags":{
    "id_onesignal":"35E2A783-35DA-4C5F-B54E-5DAC30B6E860",
    "id_participant":"dev01",
    "id_password":"5DAC30B6E86"
    },
    "fields_int":{
    "example_integer_1":3,
    "example_integer_2":5,
    },
    "fields_float":{
    "example_float_1":1.23,
    "example_float_2":90.45,
    },
    "fields_str":{
    "example_string_1":"test",
    "example_string_2":"asdf",
    }
    }
    ]