Altimeter(W) Sample Overview

Wearable Web

Related Info

The Altimeter sample application demonstrates how you can allow the user to measure the current altitude and pressure using the Gear device.

The following figure illustrates the main screens of the Altimeter.

Figure: Altimeter screens

Altimeter screens

When the application opens, the calibration prompt popup asks you to perform a calibration to set up a reference pressure. The calibration is important because it affects the correctness of the calculated altitude.

To perform the calibration, click Yes. In the calibration confirmation pop-up, click Yes again. After the calibration is ready, the main screen is shown with the reference pressure. The current pressure and altitude are displayed too.

If you skip the calibration at the beginning and want to perform it later on, you can click Calibrate on the main screen at any time.

Source Files

You can create and view the sample application project including the source files in the IDE.

File name Description
config.xml This file contains the application information for the platform to install and launch the application, including the view mode and the icon to be used in the device menu.
css/style.css This file contains the CSS styling for the application UI.
images/ This directory contains the images used to create the user interface.
index.html This is a starting file from which the application starts loading. It contains the layout of the application screens.
js/app.js This file initializes the application.
js/core/ This directory contains the core modules that are used in other parts of application code. The core.js file implements a simple AMD (Asynchronous Module Definition) and specifies module defining. The application uses a simple MV (Model View) architecture.
js/helpers/ This directory contains the helper function that returns the parent node with a class name given as a second parameter.
js/models/ This directory contain the code for the main application object and its methods, the object that stores calibrated pressure data, and the modelPressure object that is used to calibrate, get results received from the pressure sensor, and handle UI events for all pages.
js/views/ This directory contains files for implementing the application views.
lib/tau/ This directory contains the external libraries (TAU library).

Implementation

The application code is separated into modules. Each module specifies its dependent modules.

The entry point for the application is the js/app.js module. It is loaded first by the js/core/core.js library based on the data-main attribute:

<!--index.html-->
<script src="./js/core/core.js" data-main="./js/app.js"></script>

The js/app.js module loads the views/init view initialization module by adding it as a dependent module in the required array:

/* js/app.js */
define(
{
   name: 'app',
   requires: ['views/init'],
   def: function appInit() 
   {
      'use strict';

      function init() 
      {
         console.log('APP: init()');
      }

      return 
      {
         init: init
      };
   }
});

The views/init module loads the views/main view module as a dependency to let the views initialize. It also handles the Tizen hardware keys, checks the battery state, and fires the device.ready event that allows the application to start obtaining information from the Sensor API.

/* js/views/init.js */
define(
{
   name: 'views/init',
   requires: ['core/event', 'core/application', 'core/systeminfo', 'views/main'],
   def: function viewsInitPage(req) 
   {
      'use strict';

      var e = req.core.event,
          app = req.core.application,
          sysInfo = req.core.systeminfo;

      /* Register the view event listeners */
      function bindEvents() 
      {
         document.addEventListener('tizenhwkey', function onTizenhwkey(e) 
         {
            if (e.keyName === 'back') 
            {
               app.exit();
            }
         });
         sysInfo.listenBatteryLowState();
      }

      /* Handle core.battery.low event */
      function onLowBattery() 
      {
         app.exit();
      }

      /* Handle the core.battery.checked state */
      function onBatteryChecked() 
      {
         e.fire('device.ready');
      }

      /* Initialize the module */
      function init() 
      {
         bindEvents();
         sysInfo.checkBatteryLowState();
      }

      e.listeners(
      {
         'core.systeminfo.battery.low': onLowBattery,
         'core.systeminfo.battery.checked': onBatteryChecked
      });

      return 
      {
         init: init
      };
   }
});

The views/main module has several responsibilities:

  • It loads the other modules as its dependencies. These include 2 important modules, models/settings and models/pressure, that manage the application data model.
  • It handles most of the application UI interaction. For this purpose, it registers the event handlers that cover all user touch behaviors.
  • It listens for the important events triggered by the views/init and models/pressure modules:
    • When the views.init.device.ready event occurs, it calls the onDeviceReady() method. This method uses methods from the models/pressure module to obtain the sensor object from the Sensor API and to start listening to its data.

    • When the models.pressure.start event occurs, the application displays a calibration popup to ask the user whether the device's reference pressure is updated. When the models.pressure.error event occurs, the application displays an error popup message, and in case of the models.pressure.change event, the application updates the current pressure value by calling the updatePressureValue() method and the calculates the current altitude by calling the updateAltitudeValue() method.

/* js/views/main.js */
define(
{
   name: 'views/main',
   requires: ['core/event', 'models/settings', 'models/pressure', 'core/application'],
   def: function viewsMainPage(req) 
   {
      'use strict';

      var e = req.core.event,
          sensor = req.models.pressure,
          settings = req.models.settings,
          app = req.core.application,

          ARE_YOU_SURE_MSG = 'Are you sure you want to calibrate device?',
          ON_TARGET_ONLY_MSG = 'App works only on Target. ' + 'Please run this on Target.',
          SENSOR_NOT_AVAILABLE_MSG = 'Pressure sensor is not available.',
          SENSOR_NOT_SUPPORTED_MSG = 'Pressure sensor is not supported ' + 'on this device.',
          SENSOR_UNKNOWN_ERROR_MSG = 'Unknown sensor error occurs.',

          calibrationMessage = null,
          calibrationBtn = null,
          yesBtn = null,
          noBtn = null,
          referenceValue = null,
          pressureValue = null,
          altitudeValue = null,
          alertElement = null,
          alertMessage = null,
          alertOk = null,
          popupCalibration = null;

      /* Update the reference pressure value */
      function updateReferenceValue() 
      {
         referenceValue.innerText = settings.get('pressure').toFixed(2);
      }

      /* Update the current pressure value */
      /* @param {number} value */
      function updatePressureValue(value) 
      {
         pressureValue.innerText = value.toFixed(2);
      }

      /* Update the altitude value */
      /* @param {number} value */
      function updateAltitudeValue(value) 
      {
         var reference = settings.get('pressure'),
         text = '',
         altitude = -8727 * Math.log(value / reference);

         text = altitude.toFixed(0);
         if (text === '-0') 
         {
            text = '0';
         }
         altitudeValue.innerText = text;
      }

      /* Reset the altitude value */
      function resetAltitudeValue() 
      {
         altitudeValue.innerText = '0';
      }

      /* Show the application working space */
      function showWorkingSpace() 
      {
         updateReferenceValue();
      }

      /* Show the application start popup */
      function showCalibrationMonit() 
      {
         tau.openPopup(popupCalibration);
      }

      /* Calibrate the pressure */
      function calibratePressure() 
      {
         settings.set('pressure', sensor.getAverageSensorValue());
         resetAltitudeValue();
         updateReferenceValue();
      }

      /* Show the alert popup */
      /* @param {string} message Message */
      function openAlert(message) 
      {
         alertMessage.innerHTML = message;
         tau.openPopup(alertElement);
      }

      /* Handle the click event on the calibration button */
      function onCalibrationBtnClick() 
      {
         calibrationMessage.innerHTML = ARE_YOU_SURE_MSG;
         showCalibrationMonit();
      }

      /* Handle the click event on the yes button */
      function onYesBtnClick() 
      {
         tau.closePopup();
         showWorkingSpace();
         calibratePressure();
      }

      /* Handle the click event on the no button */
      function onNoBtnClick() 
      {
         tau.closePopup();
         showWorkingSpace();
      }

      /* Handle the sensor.start event */
      function onSensorStart() 
      {
         showCalibrationMonit();
      }

      /* Handle the models.pressure.error event */
      /* @param {object} data */
      function onSensorError(data) 
      {
         var type = data.detail.type;

         if (type === 'notavailable') 
         {
            openAlert(SENSOR_NOT_AVAILABLE_MSG);
         } 
         else if (type === 'notsupported') 
         {
            openAlert(SENSOR_NOT_SUPPORTED_MSG);
         } 
         else 
         {
            openAlert(SENSOR_UNKNOWN_ERROR_MSG);
         }
      }

      /* Handle the sensor.change event */
      /* @param {Event} ev */
      function onSensorChange(ev) 
      {
         updatePressureValue(ev.detail.average);
         updateAltitudeValue(ev.detail.average);
      }

      /* Handle the device.ready event */
      function onDeviceReady() 
      {
         sensor.initSensor();
         if (sensor.isAvailable()) 
         {
            sensor.setChangeListener();
            sensor.start();
         }
      }

      /* Handle the click event for the OK button */
      function onOkClick() 
      {
         tau.closePopup();
      }

      /* Handle the popupHide event on the popup element */
      function onPopupHide() 
      {
         app.exit();
      }

      /* Register the event listeners */
      function bindEvents() 
      {
         calibrationBtn.addEventListener('click', onCalibrationBtnClick);
         yesBtn.addEventListener('click', onYesBtnClick);
         noBtn.addEventListener('click', onNoBtnClick);
         alertElement.addEventListener('popuphide', onPopupHide);
         alertOk.addEventListener('click', onOkClick);
      }

      /* Initialize the module */
      function init() 
      {
         calibrationMessage = document.getElementById('popup-calibration-message');
         calibrationBtn = document.getElementById('calibration-btn');
         yesBtn = document.getElementById('popup-calibration-yes');
         noBtn = document.getElementById('popup-calibration-no');
         referenceValue = document.getElementById('reference-value');
         pressureValue = document.getElementById('current-value');
         altitudeValue = document.getElementById('altitude-value');
         alertElement = document.getElementById('alert');
         alertMessage = document.getElementById('alert-message');
         alertOk = document.getElementById('alert-ok');
         popupCalibration = document.getElementById('popup-calibration');
         bindEvents();
      }

      e.listeners(
      {
         'models.pressure.start': onSensorStart,
         'models.pressure.error': onSensorError,
         'models.pressure.change': onSensorChange,
         'views.init.device.ready': onDeviceReady
      });

      return 
      {
         init: init
      };
   }
});

The js/models/settings.js module allows the application to store the reference pressure value. This value is remembered even after the application is restarted. This value is used to calculate the proper value of the current altitude.

/* js/models/settings.js */
define(
{
   name: 'models/settings',
   requires: ['core/storage/idb', 'core/event'],
   def: function modelsSettings(req) 
   {
      'use strict';

      var e = req.core.event,
          s = req.core.storage.idb,

          STORAGE_KEY = 'settings',

          DEFAULT = Object.freeze(
          {
             pressure: 1013.25
          }),

          settings = {};

      /* Save the settings to storage */
      function saveSettings() 
      {
         s.add(STORAGE_KEY, settings);
      }

      /* Set the given settings property */
      /* @param {string} property */
      /* @param {number} value */
      /* @return {boolean} */
      function set(property, value) 
      {
         if (property !== undefined && value !== undefined) 
         {
            settings[property] = value;
            saveSettings();

            return true;
         }

         return false;
      }

      /* Return the given settings property */
      /* @param {string} property */
      /* @return {number} */
      function get(property) 
      {
         if (settings[property] === undefined) 
         {
            console.error('Settings not initialized yet.');

            return null;
         }

         return settings[property];
      }

      /* Initialize the module */
      function init() 
      {
         s.get(STORAGE_KEY);
      }

      /* Handle the core.storage.idb.read event */
      /* @param {event} ev */
      function onRead(ev) 
      {
         if (ev.detail.key !== STORAGE_KEY) 
         {
            return;
         }
         if (typeof ev.detail.value !== 'object') 
         {
            settings = 
            {
               pressure: DEFAULT.pressure
            };
            saveSettings();
         } 
         else 
         {
            settings = ev.detail.value;
         }
         e.fire('ready');
      }

      /* Make sure that init is run when the storage is ready */
      function runInit() 
         {
         if (s.isReady()) 
         {
            init();
         } 
         else 
         {
            e.listen('core.storage.idb.open', init);
         }
      }

      e.listeners(
      {
         'core.storage.idb.read': onRead
      });

      return 
      {
         init: runInit,
         get: get,
         set: set
      };
   }
});

The js/models/pressure.js module uses the Sensor API to obtain the pressure sensor data used by the application:

  • When the initSensor() method from the module is called, the application tries to obtain the pressure sensor object from the Sensor API.
  • When the start() method is called, the application calls the start() method of the Sensor API object and, in case of success, notifies the views/main module about it.
  • When the events related to the setChangeListener() method occur, the application uses the received sensor data, calculates its average value, and notifies the views/main module that the pressure value has changed.
/* js/models/pressure.js */
define(
{
   name: 'models/pressure',
   requires: ['core/event','core/window'],
   def: function modelsPressure(e, window) 
   {
      'use strict';

      var sensorService = null,
          pressureSensor = null,
          SENSOR_TYPE = 'PRESSURE',
          ERROR_TYPE_NOT_SUPPORTED = 'NotSupportedError',

          previousPressures = [],
          MAX_LENGTH = 7,
          averagePressure = 0,
          currentPressure = 0;

      /* Perform the action on start sensor success */
      function onSensorStartSuccess() 
      {
         e.fire('start');
      }

      /* Perform the action on start sensor error */
      /* @param {Error} e */
      function onSensorStartError(e) 
      {
         console.error('Pressure sensor start error: ', e);
         e.fire('error', e);
      }

      function updateAveragePressure(currentPressure) 
      {
         previousPressures.push(currentPressure);

         var len = previousPressures.length;

         if (len <= MAX_LENGTH) 
         {
            /* Nothing to shift yet, recalculate whole average */
            averagePressure  = previousPressures.reduce(function sum(a, b) 
            {
               return a + b;
            }) / len;
         } 
         else 
         {
            /* Add the new item and subtract the one shifted out */
            averagePressure += (currentPressure - previousPressures.shift()) / len;
         }

         return averagePressure;
      }

      /* Perform the action on sensor change */
      /* @param {object} data */
      function onSensorChange(data) 
      {
         currentPressure = data.pressure;
         updateAveragePressure(currentPressure);
         e.fire('change', 
         {
            current: data.pressure,
            average: averagePressure
         });
      }

      /* Start the sensor */
      function start() 
      {
         pressureSensor.start(onSensorStartSuccess, onSensorStartError);
      }

      /* Set the sensor change listener */
      function setChangeListener() 
      {
         pressureSensor.setChangeListener(onSensorChange);
      }

      /* Return the sensor value */
      function getSensorValue() 
      {
         return currentPressure;
      }

      /* Return the  average of several past readings */
      /* @return {number} */
      function getAverageSensorValue() 
      {
         return averagePressure;
      }

      /* Handle the sensor data */
      /* @param {object} data */
      function setCurrentPressureValue(data) 
      {
         currentPressure = data.pressure;
      }

      /* Return true if the sensor is available, false otherwise */
      /* @return {boolean} */
      function isAvailable() 
      {
         return !!pressureSensor;
      }

      /* Initializes the module */
      function init() 
      {
         sensorService = tizen.sensorservice ||
         (window.webapis && window.webapis.sensorservice) ||
         null;

         if (!sensorService) 
         {
            e.fire('error', {type: 'notavailable'});
         } 
         else 
         {
            try 
            {
               pressureSensor = sensorService
               .getDefaultSensor(SENSOR_TYPE);
               pressureSensor
               .getPressureSensorData(setCurrentPressureValue);
            } 
            catch (error) 
            {
               if (error.type === ERROR_TYPE_NOT_SUPPORTED) 
               {
                  e.fire('error', {type: 'notsupported'});
               } 
               else 
               {
                  e.fire('error', {type: 'unknown'});
               }
            }
         }
      }

      return 
      {
         initSensor: init,
         start: start,
         isAvailable: isAvailable,
         setChangeListener: setChangeListener,
         getAverageSensorValue: getAverageSensorValue,
         getSensorValue: getSensorValue
      };
   }
});