Skip to main content

Advanced recording examples

Guide specification
Guide type:Studio code
Requirements:Android device with an accelerometer
Recommended reading:Basic recording

Introduction

In this guide we take the concepts from the Basic recording guide and see how we can use them to record and replay data from an edge device. We will use an Android phone for this example.

Tip

The steps here are applicable for all types of edge devices. If you do not have an Android phone you can simply connect another edge device with sensors and replace the use of the accelerometer in the examples with any sensor supported by SA Engine.

Connect your edge device

Start by connecting your Android device to the local federation. How to download, install and connect an Android device is described in Connecting edge devices in the SA Studio manual. In this guide we use the name "ANDROID-EDGE" for our Android edge client.

Verify connection

To verify that the edge device is connected we use listening_edges() function. Your edge device should be listed in the result.

listening_edges();
Not connected

To run this code block you must be logged in and your studio instance must be started.

When we have verified that our Android edge client is connected we can use signal_stream('accelerometer') to display the accelerometer stream and verify that our connection is working. We can run queries on the edge device with the edge_cq() function.

//plot: Line plot
edge_cq("android-edge", "signal_stream('accelerometer')");
Not connected

To run this code block you must be logged in and your studio instance must be started.

Recording the data

Now that we have verified that our connection is working and that we can stream the accelerometer data from the device we can start recording the stream.

csv:write_file(sa_home() + "acc-data.csv",
1,
edge_cq("android-edge", "signal_stream('accelerometer')"));
Not connected

To run this code block you must be logged in and your studio instance must be started.

This records the accelerometer data on the default format until you terminate the query (by pushing the stop button).

We can verify that the data was recorded by replaying the first elements of the recorded file:

first_n(csv:file_stream(sa_home() + "acc-data.csv"),10);
Not connected

To run this code block you must be logged in and your studio instance must be started.

The accelerometer has three sensors, so the data in the acc-data.csv will look something like:

-0.258573770523071,0.646434485912323,9.66329765319824
-0.196324542164803,0.636857628822327,9.79976654052734
-0.126892685890198,0.615309834480286,9.90989971160889
-0.117315880954266,0.574608385562897,9.96496677398682
-0.179565131664276,0.574608385562897,10.0631294250488
-0.102950669825077,0.579396784305573,9.96496677398682
-0.136469498276711,0.574608385562897,9.81892013549805
...

Let's say that we want to record the data with timestamps. Then we follow the technique described in Data with timestamps and save a UTC timestamp as first value on each row.

csv:write_file(sa_home() + "acc-ts-data.csv",
1,
edge_cq("android-edge",
"select [t, v1, v2, v3]
from Vector v, Number v1, Number v2,
Number v3, Charstring t
where v in signal_stream('accelerometer')
and [v1, v2, v3] = v
and t = utc_time(timestamp(ts(v)));"));
Not connected

To run this code block you must be logged in and your studio instance must be started.

We can once again verify that the data was recorded by replaying the first elements of the recorded file:

first_n(csv:file_stream(sa_home() + "acc-ts-data.csv"),10);
Not connected

To run this code block you must be logged in and your studio instance must be started.

Now the recorded data should timestamp as the first value on each row, so the acc-ts-data.csv will look something like this:

"2022-03-29T11:56:34.889Z",-2.29603934288025,0.0814028605818748,15.8071184158325
"2022-03-29T11:56:34.916Z",-3.44046783447266,-0.263362199068069,18.1701946258545
"2022-03-29T11:56:34.926Z",-3.47638082504272,-0.158017307519913,19.747974395752
"2022-03-29T11:56:34.957Z",-5.55245304107666,0.957680702209473,23.4350452423096
"2022-03-29T11:56:34.967Z",-4.80755710601807,0.325611442327499,22.9394454956055
"2022-03-29T11:56:34.990Z",-2.36068296432495,-3.89776039123535,15.6586780548096
"2022-03-29T11:56:35.008Z",-4.17548799514771,-5.26754283905029,3.36145925521851
...

Replaying the recorded data

We saw earlier that can replay the data as a stream of vectors with the csv:file_stream() function. This is convenient if we want to look at the data or to make statistical analysis of the data.

csv:file_stream(sa_home() + "acc-data.csv");
Not connected

To run this code block you must be logged in and your studio instance must be started.

The output rendering is set now to Text but you can change it Line plot to get a better visualization of the data, but Line plot will only show the last 200 measurements as default.

Replaying data with specific pace

To better simulate the recording we can provide the parameters "read" (for "read once") and pace to csv:file_stream() that specifies how fast we want to stream the values from the file.

csv:file_stream(sa_home() + "acc-data.csv", "read", 0.05);
Not connected

To run this code block you must be logged in and your studio instance must be started.

We can also accomplish the same thing by using heartbeat_wrap() which works for all streams.

heartbeat_wrap(csv:file_stream(sa_home() + "acc-data.csv"), 0.05);
Not connected

To run this code block you must be logged in and your studio instance must be started.

Replaying data at original speed

In the previous chapter we showed how you can replay recorded data at some specific pace. This can be useful when you want to examine the data in slow motion or speed up long streams. But it can be difficult to find the right pace if you want to replay the data at its original speed.

To be able to replay data at its original speed the recorded data needs to have timestamps. Then you can see if the data was emitted at regular intervals, e.g., every tenth of a second. Should this be the case then you can just replay the stream with cvs:file_stream() and set the pace to match that interval.

An example with values emitted at regular time intervals:

"2022-03-29T11:56:34.400Z",-2.29603934288025,0.0814028605818748,15.8071184158325
"2022-03-29T11:56:34.500Z",-3.44046783447266,-0.263362199068069,18.1701946258545
"2022-03-29T11:56:34.600Z",-3.47638082504272,-0.158017307519913,19.747974395752
"2022-03-29T11:56:34.700Z",-5.55245304107666,0.957680702209473,23.4350452423096
"2022-03-29T11:56:34.800Z",-4.80755710601807,0.325611442327499,22.9394454956055
"2022-03-29T11:56:34.900Z",-2.36068296432495,-3.89776039123535,15.6586780548096
...

However, this is rarely the case for real sensor streams. Here we see data recorded by an Android accelerometer in the file acc-ts-data.cvs:

"2022-03-29T11:56:34.889Z",-2.29603934288025,0.0814028605818748,15.8071184158325
"2022-03-29T11:56:34.916Z",-3.44046783447266,-0.263362199068069,18.1701946258545
"2022-03-29T11:56:34.926Z",-3.47638082504272,-0.158017307519913,19.747974395752
"2022-03-29T11:56:34.957Z",-5.55245304107666,0.957680702209473,23.4350452423096
"2022-03-29T11:56:34.967Z",-4.80755710601807,0.325611442327499,22.9394454956055
"2022-03-29T11:56:34.990Z",-2.36068296432495,-3.89776039123535,15.6586780548096
...

It is clear that the timestamps do not represent even time intervals. Instead the values are emitted every ~1-3 hundreds of a second.

To remedy this variation and stream the values with the original speed we can

  1. replay the data as a stream of time values,
  2. use the time values to downsample the stream to some specific time interval (but no faster than original stream),
  3. replay the downsampled stream at the sample pace, thereby achieving the original speed.

This will replay a downsampled version of the original data stream but at the same speed the original stream was recorded.

When downsampling a stream there is always the risk of losing vital information, but if the interesting features of a stream span over a large time compared to the original frequency then the signal can be downsampled with minimal risk as long as the sample interval is kept close to the original pace.

Let's illustrate this with the recorded data provided in the file acc-ts-data.csv. First we create a function that replays the data from the file as a stream of timevalues.

create function replay_recorded_ts_stream()
-> Stream of Timeval of Vector
as select stream of t
from Timeval t, Vector v,
Number v1, Number v2, Number v3, Charstring tim
where v in csv:file_stream(sa_home() + "acc-ts-data.csv")
and [tim,v1,v2,v3] = v
and t = ts(parse_iso_timestamp(tim),[v1,v2,v3]);
Not connected

To run this code block you must be logged in and your studio instance must be started.

We run it and see that all rows in the file are emitted as timevals (i.e., all values are on the form ts(<timestamp>, <vector>)).

replay_recorded_ts_stream();
Not connected

To run this code block you must be logged in and your studio instance must be started.

Now we create a wrapper function sampled_stream() that samples the stream from replay_recorded_ts_stream() at specific time intervals using twinagg(). Twinagg collects values (from a stream of timevalues) in a window and emits the window when a specific time has lapsed. The sampled_stream() function emits the last vector in the window emitted by twinagg and puts it in a timeval.

create function sampled_stream(Real sample_rate)
-> Stream of Timeval of Vector
as select Stream of ts(timestamp(tv), v)
from timeval of vector tv, vector win, Vector v
where tv in twinagg(replay_recorded_ts_stream(),
sample_rate, sample_rate)
and win = value(tv)
and v = win[dim(win)];
Not connected

To run this code block you must be logged in and your studio instance must be started.

Run the function with some sample rate higher than the original (i.e., at least 0.03 since the original pace seemed to vary between 1-3 hundreds of a second) and see that the timestamps are now evenly spread at the sample rate you used.

sampled_stream(0.1);
Not connected

To run this code block you must be logged in and your studio instance must be started.

Now we can create a function that uses heartbeat_wrap() to replay the stream at the same pace as the stream was sampled. This will give us the original speed.

create function orig_speed_stream(Real sample_rate)
-> Stream of Timeval of Vector
as select Stream of tv
from Timeval of Vector tv
where tv in heartbeat_wrap(sampled_stream(sample_rate), sample_rate);
Not connected

To run this code block you must be logged in and your studio instance must be started.

Try the function to replay the stream with its original speed. The duration of the recording was ~10s, so the query should take approximately 10 seconds to finish.

orig_speed_stream(0.03);
Not connected

To run this code block you must be logged in and your studio instance must be started.

Note that you can adjust the granularity of the stream by changing the sample rate. But remember to not use values lower than the original pace to prevent the stream from runnning faster than the original.