Sensor Device Driver
--------------------

A Mynewt sensor device driver uses the sensor framework abstraction and
API to enable applications to access sensor data from any Mynewt sensor
device using a common interface. The sensor device driver must also use
the Mynewt HAL interface to communicate with and control a sensor
device.

This guide describes what a sensor device driver must implement to
enable a sensor device within the sensor framework. For information on
using the HAL API to communicate with a sensor device, see the :doc:`Hardware
Layer Abstraction Guide <../hal/hal>`.

The ``hw/drivers/sensors/<sensorname>`` package implements the device
driver for the sensor named ``SENSORNAME``.

**Note:** All example excerpts are from the BNO055 sensor device driver
package.

Initializing and Configuring a Sensor Device
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


A driver package for a sensor named ``SENSORNAME`` must define and
export the following data structures and functions to initialize and
configure a device:

-  ``struct <sensorname>``: This data structure represents a sensor
   device. The structure must include a ``dev`` field of type
   ``struct os_dev`` and a ``sensor`` field of type ``struct sensor``.
   For example:

   ::

       struct bno055 {
           struct os_dev dev;
           struct sensor sensor;
           struct bno055_cfg cfg;
           os_time_t last_read_time;
       };

-  ``struct <sensorname>_cfg``: This data structure defines the
   configuration for a sensor device. The structure fields are specific
   to the device. This is the data structure that a BSP, the sensor
   creator package, or an application sets to configure a sensor device.
   For example:

   ::

       struct bno055_cfg {
           uint8_t bc_opr_mode;
           uint8_t bc_pwr_mode;
           uint8_t bc_units;
           uint8_t bc_placement;
           uint8_t bc_acc_range;
           uint8_t bc_acc_bw;

                ...

           uint32_t bc_mask;
       };

-  ``<sensorname>_init()``: This is the os device initialization
   callback of type
   ``int (*os_dev_init_func_t)(struct os_dev *, void *)`` that the
   ``os_dev_create()`` function calls to initialize the device. For
   example, the bno055 device driver package defines the following
   function:

   ::

       int bno055_init(struct os_dev *dev, void *arg)

   The BSP, which creates a device for an onboard sensor, and the sensor
   creator package, which creates a device for an off-board sensor,
   calls the ``os_dev_create()`` function and passes:

   -  A pointer to a ``struct <sensorname>`` variable.
   -  The ``<sensorname>_init()`` function pointer.
   -  A pointer to a ``struct sensor_itf`` variable that specifies the
      interface the driver uses to communicate with the sensor device.

   See the :doc:`Creating Sensor
   Devices <sensor_create>` page for
   more details.

   The ``os_dev_create()`` function calls the ``<sensorname>_init()``
   function with a pointer to the ``struct <sensorname>`` for the
   ``dev`` parameter and a pointer to the ``struct sensor_itf`` for the
   ``arg`` parameter.

-  ``<sensorname>_config()``: This is the sensor configuration function
   that the BSP, sensor creator package, or an application calls to
   configure the sensor device. For example:

   ::

       int bno055_config(struct bno055 *bno055, struct bno055_cfg *cfg)

Defining Functions to Read Sensor Data and Get Sensor Value Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A device driver must implement the following functions that the sensor
API uses to read sensor data and to get the configuration value type for
a sensor:

-  A function of type
   ``int (*sensor_read_func_t)(struct sensor *, sensor_type_t, sensor_data_func_t, void *, uint32_t)``
   that the sensor framework uses to read a single value from a sensor
   for the specified sensor types. The device driver must implement this
   function such that it reads, for each sensor type set in the bit
   mask, a single value from the sensor and calls the
   ``sensor_data_funct_t`` callback with the opaque callback argument ,
   the sensor data, and the sensor type.

-  A function of type
   ``int (*sensor_get_config_func_t)(struct sensor *, sensor_type_t, struct sensor_cfg *)``
   that returns the value type for the specified sensor type. For
   example, the value type for a ``SENSOR_VALUE_TYPE_TEMPERATURE``
   sensor might be ``SENSOR_VALUE_TYPE_FLOAT`` and the value type for a
   ``SENSOR_TYPE_ACCELEROMETER`` sensor might be
   ``SENSOR_VALUE_TYPE_FLOAT_TRIPLET``.

The driver initializes a ``sensor_driver`` structure, shown below, with
the pointers to these functions:

.. code:: c


    struct sensor_driver {
        sensor_read_func_t sd_read;
        sensor_get_config_func_t sd_get_config;
    };

For example:

.. code:: c


    static int bno055_sensor_read(struct sensor *, sensor_type_t,
            sensor_data_func_t, void *, uint32_t);
    static int bno055_sensor_get_config(struct sensor *, sensor_type_t,
            struct sensor_cfg *);

    static const struct sensor_driver g_bno055_sensor_driver = {
        bno055_sensor_read,
        bno055_sensor_get_config
    };

Registering the Sensor in the Sensor Framework
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


The device driver must initialize and register a ``struct sensor``
object with the sensor manager. See the :doc:`Sensor
API <sensor_api>` and the :doc:`Sensor
Manager API <sensor_mgr_api>`
pages for more details.

The device driver ``<sensorname>_init()`` function initializes and
registers a sensor object as follows:

-  Calls the ``sensor_init()`` function to initialize the
   ``struct sensor`` object.

-  Calls the ``sensor_set_driver()`` function to specify the sensor
   types that the sensor device supports, and the pointer to the
   ``struct sensor_driver`` variable that specifies the driver functions
   to read the sensor data and to get the value type for a sensor.

-  Calls the ``sensor_set_interface()`` function to set the interface
   that the device driver uses to communicate with the sensor device.
   The BSP, or sensor creator package for an off-board sensors, sets up
   the ``sensor_itf`` and passes it to the ``<sensorname>_init()``
   function. The ``sensor_set_interface()`` functions saves this
   information in the sensor object. The device driver uses the
   ``SENSOR_GET_ITF()`` macro to retrieve the sensor\_itf when it needs
   to communicate with the sensor device.

-  Calls the ``sensor_mgr_register()`` function to register the sensor
   with the sensor manager.

For example:

.. code:: c

    int 
    bno055_init(struct os_dev *dev, void *arg) 
    { 
        struct bno055 *bno055; 
        struct sensor *sensor; 
        int rc;

        if (!arg || !dev) {
            rc = SYS_ENODEV;
            goto err;
        }
    
        bno055 = (struct bno055 *) dev;
    
        rc = bno055_default_cfg(&bno055->cfg);
        if (rc) {
            goto err;
        }
    
        sensor = &bno055->sensor;
    
        /* Code to setup logging and stats may go here */
        .... 
    
        rc = sensor_init(sensor, dev);
        if (rc != 0) {
            goto err;
        }
    
        /* Add the accelerometer/magnetometer driver */
        rc = sensor_set_driver(sensor, SENSOR_TYPE_ACCELEROMETER         |
                SENSOR_TYPE_MAGNETIC_FIELD | SENSOR_TYPE_GYROSCOPE       |
                SENSOR_TYPE_TEMPERATURE    | SENSOR_TYPE_ROTATION_VECTOR |
                SENSOR_TYPE_GRAVITY        | SENSOR_TYPE_LINEAR_ACCEL    |
                SENSOR_TYPE_EULER, (struct sensor_driver *) &g_bno055_sensor_driver);
        if (rc != 0) {
            goto err;
        }
    
        /* Set the interface */
        rc = sensor_set_interface(sensor, arg);
        if (rc) {
            goto err;
        }
    
        rc = sensor_mgr_register(sensor);
        if (rc != 0) {
            goto err;
        }
    
        return (0);
    
    err: 
        return (rc); 
    }


Configuring the Sensor Device and Setting the Configured Sensor Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

After the BSP, or the sensor creator package for an off-board sensor,
creates the OS device for a sensor, it calls the
``<sensorname>_config()`` function to configure sensor device
information such as mode, power mode, and to set the configured sensor
types. The ``<sensorname>_config()`` function configures the settings on
the sensor device. It must also call the ``sensor_set_type_mask()``
function to set the configured sensor types in the sensor object. The
configured sensor types are a subset of the sensor types that the sensor
device supports and the sensor framework only reads sensor data for
configured sensor types.

**Notes:**

-  The device driver uses the ``SENSOR_GET_ITF()`` macro to retrieve the
   sensor interface to communicate with the sensor.

-  If a sensor device has a chip ID that can be queried, we recommend
   that the device driver read and verify the chip ID with the data
   sheet.

-  An application may call the ``<sensorname>_config()`` function to
   configure the sensor device.

For example:

.. code:: c

    int 
    bno055_config(struct bno055 *bno055, struct bno055_cfg *cfg) 
    {
        int rc; 
        uint8_t id; 
        uint8_t mode; 
        struct sensor_itf *itf;
    
        itf = SENSOR_GET_ITF(&(bno055->sensor));
    
        /* Check if we can read the chip address */
        rc = bno055_get_chip_id(itf, &id);
        if (rc) {
            goto err;
        }
    
        if (id != BNO055_ID) {
            os_time_delay((OS_TICKS_PER_SEC * 100)/1000 + 1);
    
            rc = bno055_get_chip_id(itf, &id);
            if (rc) {
                goto err;
            }
    
            if(id != BNO055_ID) {
                rc = SYS_EINVAL;
                goto err;
            }
        }
    
           ....

    /* Other code to set the configuration on the sensor device. */
    
           .... 
    
        rc = sensor_set_type_mask(&(bno055->sensor), cfg->bc_mask);
        if (rc) {
            goto err;
        }
    
        bno055->cfg.bc_mask = cfg->bc_mask;
    
        return 0;
    
    err: 
        return rc; 
    }


Implementing a Sensor Device Shell Command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


A sensor device driver package may optionally implement a sensor device
shell command that retrieves and sets sensor device information to aid
in testing and debugging. While the sensor framework :doc:`sensor shell
command <sensor_shell>` reads sensor
data for configured sensor types and is useful for testing an
application, it does not access low level device information, such as
reading register values and setting hardware configurations, that might
be needed to test a sensor device or to debug the sensor device driver
code. A sensor device shell command implementation is device specific
but should minimally support reading sensor data for all the sensor
types that the device supports because the sensor framework ``sensor``
shell command only reads sensor data for configured sensor types.

The package should:

-  Name the sensor device shell command ``<sensorname>``. For example,
   the sensor device shell command for the BNO055 sensor device is
   ``bno055``.

-  Define a ``<SENSORNAME>_CLI`` syscfg setting to specify whether the
   shell command is enabled and disable the setting by default.

-  Export a ``<sensorname>_shell_init()`` function that an application
   calls to initialize the sensor shell command when the
   ``SENSORNAME_CLI`` setting is enabled.

For an example on how to implement a sensor device shell command, see
the `bno055 shell
command <https://github.com/apache/mynewt-core/blob/master/hw/drivers/sensors/bno055/src/bno055_shell.c>`__
source code. See the :doc:`Enabling an Off-Board Sensor in an Existing
Application Tutorial <../../../tutorials/sensors/sensor_nrf52_bno055>`
for examples of the bno055 shell command.

Defining Logs
~~~~~~~~~~~~~

A sensor device driver should define logs for testing purposes. See the
:doc:`Log OS Guide <../logs/logs>` for more details on how to
add logs. The driver should define a ``<SENSORNAME>_LOG`` syscfg setting
to specify whether logging is enabled and disable the setting by
default.

Here is an example from the BNO055 sensor driver package:

.. code:: c
    
    #if MYNEWT_VAL(BNO055_LOG)
    #include "log/log.h"
    #endif
    
    #if MYNEWT_VAL(BNO055_LOG) 
    #define LOG_MODULE_BNO055 (305) 
    #define BNO055_INFO(...) LOG_INFO(&_log, LOG_MODULE_BNO055, _VA_ARGS_)
    #define BNO055_ERR(...) LOG_ERROR(&_log, LOG_MODULE_BNO055,_VA_ARGS_) 
    static struct log _log; 
    #else 
    #define BNO055_INFO(...)
    #define BNO055_ERR(...) 
    #endif

     ...

    int 
    bno055_init(struct os_dev *dev, void *arg) 
    {
          
     ...
    
        rc = bno055_default_cfg(&bno055->cfg);
        if (rc) {
            goto err;
        }

    #if MYNEWT_VAL(BNO055_LOG)
        log_register(dev->od_name, &_log, &log_console_handler, NULL, LOG_SYSLEVEL);
    #endif
    
      ...
    
    }


Defining Stats
~~~~~~~~~~~~~~~


A sensor device driver may also define stats for the sensor. See the
:doc:`Stats OS Guide <../stats/stats>` for more details on how
to add stats. The driver should define a ``<SENSORNAME>_STATS`` syscfg
setting to specify whether stats is enabled and disable the setting by
default.

Here is an example from the BNO055 sensor driver package:

.. code:: c
    
    #if MYNEWT_VAL(BNO055_STATS)
    #include "stats/stats.h"
    #endif
    
    #if MYNEWT_VAL(BNO055_STATS)
    
    /* Define the stats section and records */
    STATS_SECT_START(bno055_stat_section) 
    STATS_SECT_ENTRY(errors)
    STATS_SECT_END
    
    /* Define stat names for querying */
    STATS_NAME_START(bno055_stat_section)
    STATS_NAME(bno055_stat_section, errors)
    STATS_NAME_END(bno055_stat_section)
    
    /* Global variable used to hold stats data */
    STATS_SECT_DECL(bno055_stat_section) g_bno055stats; 
    #endif
    
    ...
    
    int 
    bno055_init(struct os_dev *dev, void *arg) 
    {

      ...
    
    #if MYNEWT\_VAL(BNO055\_STATS)
    
        /* Initialise the stats entry */
        rc = stats_init(
            STATS_HDR(g_bno055stats),
            STATS_SIZE_INIT_PARMS(g_bno055stats, STATS_SIZE_32),
            STATS_NAME_INIT_PARMS(bno055_stat_section));
        SYSINIT_PANIC_ASSERT(rc == 0);
        /* Register the entry with the stats registry */
        rc = stats_register(dev->od_name, STATS_HDR(g_bno055stats));
        SYSINIT_PANIC_ASSERT(rc == 0);
    
    #endif

      ...

    }

