# journey.sensors

{% hint style="info" %}
**Version compatibility**

* `journey.sensors` was introduced in version **4.85.0** of the JourneyApps Runtime and version **22.4.1** of the JourneyApps Container.&#x20;
* Currently only supported on Android and iOS.
  {% endhint %}

Returns information about the device's accelerometer, compass and orientation data.

### Supported fields

#### `getCapabilities()`

To retrieve a device's supported sensor capabilities.

{% code title="main.ts" %}

```typescript
var capabilities = await journey.sensors.getCapabilities();
view.accelerometer_supported = capabilities.accelerometerIsSupported;
view.compass_supported = capabilities.compassIsSupported;
view.orientation_supported = capabilities.orientationIsSupported;
```

{% endcode %}

#### `accelerometer.read()`

Returns accelerometer data captured at a specific point in time.&#x20;

Acceleration values include the effect of gravity (9.81 m/s^2), so that when a device lies flat and facing up, *x*, *y*, and *z* values returned should be `0`, `0`, and `9.81`.

**Properties returned:**

* `x`: Number value
* `y`: Number value
* `z`: Number value
* `timestamp`: Unix Timestamp when sample was taken

#### `accelerometer.registerListener()`

Listeners supply an `onData` callback which will be invoked with the data argument set to the same format as a `read` function call. The `onError` callback will be called on an error condition, if called, no subsequent `onData` callbacks are executed. The argument provided to the `onError` function is a string error message.&#x20;

**Listener options:**&#x20;

Currently the only option supported for all sensors is the sampling period in milliseconds.&#x20;

```typescript
export interface ListenerOptions {
    periodMs: number;
}
```

**Example**:

{% code title="main.js" %}

```typescript
watchRemover = journey.sensors.accelerometer.registerListener(
    function onData(result) {
        // Do something with result here, e.g.   
        var listenerTimestamp = result.timestamp.toString();
    },
    function onError(error) {
        console.log('Received error', error);
    },
    {
        periodMs: 1000
    }
);
```

{% endcode %}

#### `compass.read()`

Returns compass data captured at a specific point in time.&#x20;

**Properties returned:**

* `magneticHeading`: The heading in degrees from 0-359.99 at a single moment in time. *(Number)*
* `trueHeading`: The heading relative to the geographic North Pole in degrees 0-359.99 at a single moment in time. A negative value indicates that the true heading can't be determined. *(Number)*
* `headingAccuracy`: The deviation in degrees between the reported heading and the true heading. *(Number)*
* `timestamp`: Unix Timestamp when sample was taken

#### `compass.registerListener()`

See [`accelerometer.registerListener()`](#accelerometer.registerlistener)

#### `orientation.read()`

Returns the orientation of a device with angles measured along a specific reference frame. See [here](https://www.w3.org/TR/orientation-event/) for good depictions of the angles.

**Properties returned:**

* `alpha`: Number value
* `beta`: Number value
* `gamma`: Number value
* `timestamp`: Unix Timestamp when sample was taken

#### `orientation.registerListener()`

See [`accelerometer.registerListener()`](#accelerometer.registerlistener)

### Example Code

The below example code shows a view that allows a user to read and start/stop watching accelerometer data. The returned data saved to `LocalDB` objects and is presented in tables.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="schema.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<data-model>
    ...
    <model name="data_row" label="Local: Data Row">
        <field name="key" label="Key" type="text" />
        <field name="value" label="Value" type="text" />
        
        <display>{key}: {value}</display>
    </model>

</data-model>
```

{% endcode %}

{% code title="main.view\.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<view title="Accelerometer" on-back="stopWatch">
    <var name="read_timestamp" type="text" />
    <var name="listener_timestamp" type="text" />
    
    <var name="read_results" type="array:data_row" />
    <var name="watch_results" type="array:data_row" />
    
    <var name="watch_period_ms" type="number" />
    <var name="is_watching" type="boolean" />

    <button label="Read Sensor" on-press="read" validate="false" />
    <object-table show-if="read_results" label="Single Result {read_timestamp}" query="read_results" empty-message="Your items will appear here">
        <column heading="Field" fit="shrink">{key}</column>
        <column heading="Value m/s^2">{value}</column>
    </object-table>

    <heading>Watch Sensor</heading>
    <text-input label="Sample period (ms)" bind="watch_period_ms" required="true" />
    <button hide-if="is_watching" label="Watch Acceleration" on-press="watch" validate="true" />
    <button show-if="is_watching" label="Stop watching" on-press="stopWatch" validate="false" />
    <object-table show-if="watch_results" label="Watched Result {listener_timestamp}" query="watch_results" empty-message="Your items will appear here">
        <column heading="Field" fit="shrink">{key}</column>
        <column heading="Value m/s^2">{value}</column>
    </object-table>
</view>
```

{% endcode %}

{% code title="main.js" %}

```javascript
// This function is called when the app navigates to this view (using a link)
function init() {
    // initialize any data here that should be available when the view is shown
    view.watch_period_ms = 1000;
}

// This function is called when the user returns to this view from another view
function resume(from) { }
function read() {
    // For accelerometer data use journey.sensors.accelerometer
    // For compass data use journey.sensors.compass
    // For orientation data use journey.sensors.orientation
    var result = journey.sensors.accelerometer.read();
    view.read_results = Object.keys(result).map(function (key) {
        return LocalDB.data_row.create({
            key: key,
            value: result[key]
        })
    });
    view.read_timestamp = result.timestamp.toString();
}

var watchRemover;
function watch() {
    // For accelerometer data use journey.sensors.accelerometer
    // For compass data use journey.sensors.compass
    // For orientation data use journey.sensors.orientation
    watchRemover = journey.sensors.accelerometer.registerListener(
        function onData(result) {
            if (view.watch_results && view.watch_results.length) {
                view.watch_results.map(function (item) { item.destroy() });
            }
            view.watch_results = Object.keys(result).map(function (key) {
                return LocalDB.data_row.create({
                    key: key,
                    value: result[key]
                })
            });
            view.listener_timestamp = result.timestamp.toString();
            journey.forceDigest();
        },
        function onError(error) {
            console.log('Received error', error);
        },
        {
            periodMs: view.watch_period_ms
        });
    view.is_watching = true;
}

function stopWatch() {
    if (watchRemover) {
        watchRemover();
        watchRemover = null;
    };
    view.is_watching = false;
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% hint style="info" %}
This feature requires version **2.0.5** or greater of the [`@journeyapps/runtime-build` package](https://docs.journeyapps.com/reference/build/syntax/typescript-apps/runtime-build-package).
{% endhint %}

{% code title="schema.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<data-model>
    ...
    <model name="data_row" label="Local: Data Row">
        <field name="key" label="Key" type="text" />
        <field name="value" label="Value" type="text" />
        
        <display>{key}: {value}</display>
    </model>

</data-model>
```

{% endcode %}

{% code title="main.view\.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<view title="Accelerometer" on-back="stopWatch">
    <var name="read_timestamp" type="text" />
    <var name="listener_timestamp" type="text" />

    <var name="read_results" type="array:data_row" />
    <var name="watch_results" type="array:data_row" />

    <var name="watch_period_ms" type="number" />
    <var name="is_watching" type="boolean" />

    <button label="Read Sensor" on-press="read" validate="false" />
    <object-table show-if="read_results" label="Single Result {read_timestamp}" query="read_results" empty-message="Your items will appear here">
        <column heading="Field" fit="shrink">{key}</column>
        <column heading="Value m/s^2">{value}</column>
    </object-table>

    <heading>Watch Sensor</heading>
    <text-input label="Sample period (ms)" bind="watch_period_ms" required="true" />
    <button hide-if="is_watching" label="Watch Acceleration" on-press="watch" validate="true" />
    <button show-if="is_watching" label="Stop watching" on-press="stopWatch" validate="false" />
    <object-table show-if="watch_results" label="Watched Result {listener_timestamp}" query="watch_results" empty-message="Your items will appear here">
        <column heading="Field" fit="shrink">{key}</column>
        <column heading="Value m/s^2">{value}</column>
    </object-table>

</view>
```

{% endcode %}

{% code title="main.js" %}

```javascript
// This function is called when the app navigates to this view (using a link)
async function init() {
    // initialize any data here that should be available when the view is shown
    view.watch_period_ms = 1000;
}

// This function is called when the user returns to this view from another view
async function resume(from: ResumeFrom) { }

async function read() {
    const result = await journey.sensors.accelerometer.read();

    view.read_results = Object.keys(result).map(key => LocalDB.data_row.create({
        key: key,
        value: result[key]
    }));

    view.read_timestamp = result.timestamp.toString();
}

let watchRemover: () => void;
async function watch() {
    watchRemover = await journey.sensors.accelerometer.registerListener(
        result => {
            if (view.watch_results?.length) {
                view.watch_results.map(item => item.destroy());
            }

            view.watch_results = Object.keys(result).map(key => LocalDB.data_row.create({
                key: key,
                value: result[key]
            }));

            view.listener_timestamp = result.timestamp.toString();

            journey.forceDigest();
        }, 
        console.log,
        {
            periodMs: view.watch_period_ms
        });

    view.is_watching = true;
}

async function stopWatch() {
    if (watchRemover) {
        await watchRemover();
        watchRemover = null;
    }
    view.is_watching = false;
}
```

{% endcode %}
{% endtab %}
{% endtabs %}
