Code covered by the MathWorks Limited License

Highlights from
Android Sensor support from MATLAB

from Android Sensor support from MATLAB by MathWorks Mobile Sensor Connectivity Team
MATLAB support package for Android Sensors

sensorgroup.m
classdef (Sealed, CaseInsensitiveProperties=true, TruncatedProperties=true) sensorgroup < handle & dynamicprops
    %     SENSORGROUP Reads sensor data from app running on mobile device
    %     
    %     SUPPORTED APPS
    %     Apple iOS: Sensor Monitor Pro by Ko, Young-woo
    %     Android: SensorUdp by Takashi, Sasaki
    %     Support for specific apps may change in future versions.
    %     
    %     obj = sensorgroup(deviceType) creates an object, obj, that reads sensor data from a mobile device 
    %     connected to the same network as the PC running MATLAB.
    %
    %     deviceType - the type of mobile device. The value is either 'AppleMobile' or 'AndroidMobile'.
    %
    %     obj = sensorgroup(deviceType, Name, Value) uses additional options specified by one or more Name,Value 
    %     pair arguments.
    %
    %     deviceType - the type of mobile device. The value is either 'AppleMobile' or 'AndroidMobile'.
    %
    %     Specify optional comma-separated pairs of Name, Value arguments. Name is the argument name and 
    %     Value is the corresponding value. Name must appear inside single quotes ('  '). You can specify several 
    %     name and value pair arguments in any order as Name1, Value1, ..., NameN, ValueN.
    %
    %     'IPAddress' - Specify the Ethernet device that receives the data. The value is 'any' or the IPv4 
    %                   address of a specific Ethernet device on the host computer. 
    %                   Use single quotes with the IP address. The default value is 'any'.
    %     'Port'      - Specify the Ethernet port that is receives the data. The value is either 'auto' or a 
    %                   port number, from 0 to 65535. Do not use single quotes with the port number. 
    %                   The default value is 'auto'.
    %     'HideTip'   - Suppresses help message that describes steps that need to be done on device.
    %                   The value is either true or false. The default value is false.
    %     
    %     sensorgroup methods:
    %         showLatestValues - display a list of measurement names and the most recent value for each one
    %         accellog - returns logged acceleration data
    %         angvellog - returns logged angular velocity data
    %         magfieldlog - returns logged magnetic field data
    %         orientlog - returns logged orientation data
    %         poslog - returns logged position data
    %         delete - deletes sensorgroup
    %         discardlogs - discard all logged data
    %
    %     sensorgroup properties:
    %         IPAddress - IPv4 IP Address of the Ethernet interface on the
    %                     host computer
    %         Port - The Ethernet port number on the host computer
    %         InitialTimestamp - Timestamp when the first packet arrived
    %         Acceleration - Latest Acceleration reading: X, Y, Z in m/s^2
    %         AngularVelocity - Latest AngularVelocity reading: X, Y, Z in radians per second
    %         MagneticField - Latest MagneticField reading:  X, Y, Z in tesla
    %         MagneticDeclination - Latest MagneticDeclination reading: Magnetic Heading - True Heading in degrees (iOS only)
    %         Orientation - Latest orientation reading: [1x3] matrix representing Azimuth, Roll and Pitch
    %         Latitude - Latest Latitude in degree
    %         Longitude - Latest Longitude in degree
    %         HorizontalAccuracy - Latest Horizontal Accuracy in meters
    %         Altitude - Altitude in meters
    %         AltitudeAccuracy - Altitude Accuracy in meters up or down (iOS only)
    %         Speed - Latest Speed reading in meters per second m/s
    %         Course - Latest Course reading in degrees relative to true north (iOS only)    
    %
    %     EXAMPLES
    %     Before you start using it, please connect your device to the same network as 
    %     the host computer where you are running MATLAB. You may use Wi-Fi or 
    %     cellular network and depending on your network setup in some cases you 
    %     may need to use a VPN. Please note, that due to the nature of communication 
    %     protocol (UDP) that is used by the mobile app, the app wont complain if data 
    %     packets cannot reach the host machine.  
    %
    %     Set up and read data from an Apple device
    %     1. Open App Store and search iPhone Apps for Sensor Monitor by Ko, Young-woo.
    %     2. Install and open Sensor Monitor.
    %     3. Make an in-app purchase to upgrade to the Pro version.
    %     4. Select the Network tab and change Current Send Mode to Binary.
    %     5. In MATLAB, enter:  obj = sensorgroup('AppleMobile') 
    %        MATLAB displays instructions for configuring Sensor Monitor Pro.
    %     6. In Sensor Monitor Pro, choose which sensors to send data, and how often to send it.
    %     7. Update the host and port values. Then, tap the Start Send button. 
    %        MATLAB displays a message that it is logging data from the mobile device, including a list of 
    %        measurements.  
    %        Leave Sensor Monitor Pro app open and running on foreground. If it goes to the background iOS will 
    %        stop the app after some time and it stops sending data.
    %     8. In MATLAB, display the current data by entering: showLatestValues(s) 
    %        MATLAB displays the measurements, latest values, units, and log size for each measurement. It 
    %        identifies measurements for which it has not received data.
    %     9. When you are done, in Sensor Monitor Pro, tap Stop Send.
    %     
    %     Set up and log data from an Android device
    %     1. Open Google Play and search for SensorUdp by Takashi, Sasaki.
    %     2. Install and open SensorUdp.
    %     3. In MATLAB, enter:  obj = sensorgroup('AndroidMobile') 
    %        MATLAB displays instructions for configuring SensorUdp.    
    %     4. In SensorUdp, update the dest. host and port values. 
    %     5. In SensorUdp, use the check boxes for accelerometer cvs line, magnetic field cvs line, and
    %        orientation cvs line to choose which sensors send data. 
    %        Then, tap the send button. MATLAB displays a message that it is logging 
    %        data from the mobile device, including a list of sensors.
    %     6. In MATLAB, display the current data by entering: showLatestValues(s)
    %        MATLAB displays the measurements, latest values, units, and log size for each measurement. It
    %        also identifies measurements for which it has not received data.
    %     7. When you are done, exit from the app by pressing the Android back button. In order to increase
    %        battery life, its recommended to open the Android Task Manager and make sure that SensorUdp is
    %        not running.
    %     
    %     ACCESS RECIEVED DATA
    %     Use showLatestValues to display a list of measurement names and the most recent value for 
    %     each one. For example:
    %
    %         showLatestValues(obj)
    %
    %     You can also get the latest value of a specific measurement listed by showLatestValues. For 
    %     example:
    %
    %         obj.Acceleration
    %
    %     You can use sensorgroup methods to access the logged measurement values. 
    %     For example, to get logged acceleration values call:
    %
    %         [a, t] = accellog(obj)
    %     
    %     RECEIVE DATA FROM MULTIPLE DEVICES
    %     Create separate objects for each mobile device. 
    %     Specify the IPv4 address of the Ethernet devices on the host computer. 
    %     If multiple mobile devices send data to the same Ethernet address on the host computer, use 
    %     different port numbers for each mobile device. 
    %
    %     For example, enter:
    %
    %         SamsungGalaxyTab =  sensorgroup('AndroidMobile', 'IPAddress', '192.168.1.1', 'Port', 49152) 
    %         iPhone =  sensorgroup('AppleMobile', 'IPAddress', '192.168.1.1', 'Port', 50000)
    %         iPad =  sensorgroup('AppleMobile', 'IPAddress', '172.28.194.136', 'Port', 50000)
    %     
    %     TROUBLESHOOTING
    %     MATLAB does not receive data from the mobile device
    %     Symptom: MATLAB does not display logging data from the mobile device message after you 
    %     tap the send or Start Send button in the app on the mobile device.
    %     Verify or try the following:
    %     - In the app on the mobile device:
    %         Enable the sensors.
    %         Set the IP address and port number provided by MATLAB. 
    %         If MATLAB provides multiple IP addresses, try each one.
    %         Tap the send or Start Send button.
    %     -	The mobile device is connected to the correct Wi-Fi network. Airplane mode is off.
    %     -	The host computer running MATLAB is connected to the network.
    %     -	Routers on the network are configured to pass UDP traffic for the specified port number.
    %
    %    See also: sensorgroup,
    %           <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAzimuthRollPitchExample.html'))">Example of Capturing of Azimuth, Roll and Pitch</a>,
    %           <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAndMappingGPSExample.html'))">Example of Capturing and Mapping GPS</a>,
    %           <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'StepCounter.html'))">Example of Step Counting with Acceleration Data</a>

    %   Copyright 2013 The MathWorks, Inc.
    
    properties(GetAccess = public, SetAccess = private, Dependent)
        % IPAddress - IPv4 IP Address of the Ethernet interface on the host computer.
        IPAddress
        
        % Port - IP Port that we are currently listening on.
        Port
        
        % InitialTimestamp - Timestamp when the first packet arrived.
        InitialTimestamp
        
        % Acceleration - Latest Acceleration reading: X, Y, Z in m/s^2.
        %
        % Acceleration is defined in relation to the X, Y and Z axes.
        %
        % If you set the phone down face up on a table, the positive X-axis
        % extends out of the right side of the phone, positive Y-axis
        % extends out of the top side, and the positive Z-axis extends out
        % of the front face of the phone. This is independent of the
        % orientation of the phone.
        %
        % For an image of the axes see
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAzimuthRollPitchExample.html'))">Example of Capturing of Azimuth, Roll and Pitch</a>
        %
        % See also: accellog
        Acceleration 
        
        % AngularVelocity - Latest AngularVelocity reading: X, Y, Z in radians per second.
        %
        % AngularVelocity is defined in relation to the X, Y and Z axes and
        % in standard right-hand rotational vector notation.
        %
        % If you set the phone down face up on a table, the positive X-axis
        % extends out of the right side of the phone, positive Y-axis
        % extends out of the top side, and the positive Z-axis extends out
        % of the front face of the phone. This is independent of the
        % orientation of the phone.
        %
        % For an image of the axes see
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAzimuthRollPitchExample.html'))">Example of Capturing of Azimuth, Roll and Pitch</a>
        %
        % See also: angvellog
        AngularVelocity

        % MagneticField - Latest MagneticField reading:  X, Y, Z in Tesla.
        %
        % MagneticField is defined in relation to the X, Y and Z axes.
        %
        % If you set the phone down face up on a table, the positive X-axis
        % extends out of the right side of the phone, positive Y-axis
        % extends out of the top side, and the positive Z-axis extends out
        % of the front face of the phone. This is independent of the
        % orientation of the phone.
        %
        % For an image of the axes see
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAzimuthRollPitchExample.html'))">Example of Capturing of Azimuth, Roll and Pitch</a>
        %
        % See also: magfieldlog
        MagneticField

        % MagneticDeclination - Latest MagneticDeclination reading: Magnetic Heading - True Heading in degrees. (iOS only)
        MagneticDeclination 
        
        % Orientation - Latest orientation reading: [1x3] matrix representing Azimuth, Roll and Pitch.
        %
        % Orientation is defined in relation to the X, Y and Z axes.
        %
        % If you set the phone down face up on a table, the positive X-axis
        % extends out of the right side of the phone, positive Y-axis
        % extends out of the top side, and the positive Z-axis extends out
        % of the front face of the phone. This is independent of the
        % orientation of the phone.
        %
        % Azimuth is angle between the positive Y-axis and magnetic north
        % and its range is between 0 and 360 degrees.
        %
        % Positive Roll is defined when the phone starts by laying flat on
        % a table and the positive Z-axis begins to tilt towards the
        % positive X-axis. (Android only)
        %
        % Positive Pitch is defined when the phone starts by laying flat on
        % a table and the positive Z-axis begins to tilt towards the
        % positive Y-axis. (Android only)
        %
        % For an image of the axes and a full example, see
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAzimuthRollPitchExample.html'))">Example of Capturing of Azimuth, Roll and Pitch</a>
        %
        % See also: orientlog
        Orientation
        
        % Latitude - Latest Latitude in degrees.
        %
        % Position data is obtained from GPS, Wi-Fi or cellular network,
        % which ever is most appropriate.
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAndMappingGPSExample.html'))">Example of Capturing and Mapping GPS</a>
        %
        % See also: poslog
        Latitude 
 
        % Longitude - Latest Longitude in degrees.
        %
        % Position data is obtained from GPS, Wi-Fi or cellular network,
        % which ever is most appropriate.
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAndMappingGPSExample.html'))">Example of Capturing and Mapping GPS</a>
        %
        % See also: poslog
        Longitude                 
        
        % HorizontalAccuracy - Latest Horizontal Accuracy in meters.
        %
        % Position data is obtained from GPS, Wi-Fi or cellular network,
        % which ever is most appropriate.
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAndMappingGPSExample.html'))">Example of Capturing and Mapping GPS</a>
        %
        % See also: poslog
        HorizontalAccuracy
        
        % Altitude - Altitude in meters.
        %
        % Position data is obtained from GPS, Wi-Fi or cellular network,
        % which ever is most appropriate.
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAndMappingGPSExample.html'))">Example of Capturing and Mapping GPS</a>
        %
        % See also: poslog
        Altitude 
        
        % AltitudeAccuracy - Altitude Accuracy in meters up or down. (iOS only)
        %
        % Position data is obtained from GPS, Wi-Fi or cellular network,
        % which ever is most appropriate.
        % <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAndMappingGPSExample.html'))">Example of Capturing and Mapping GPS</a>
        %
        % See also: poslog
        AltitudeAccuracy 
         
        % Speed - Latest Speed reading in meters m/s.
        Speed
        
        % Course - Latest Course reading in degrees relative to true north. (iOS only)
        Course 
    end
    
    properties(Access = private)
        Controller
        ParsedConstructorArgs
        AccelerationLog % Logged Acceleration readings: X, Y, Z m/s^2.
        AngularVelocityLog % Logged AngularVelocity readings: X, Y, Z in in radians per second.
        MagneticFieldLog % Logged MagneticField readings:  X, Y, Z in Tesla.
        OrientationLog % Logged Orientation reading: [1x3] matrix representing Azimuth, Roll and Pitch 
        LatitudeLog % Logged Latitude readings
        LongitudeLog % Logged Longitude readings
        HorizontalAccuracyLog % Logged Horizontal Accuracy readings
        AltitudeLog % Logged Altitude readings.
        AltitudeAccuracyLog % Logged AltitudeAccuracy readings.
        SpeedLog % Logged Speed readings.
        CourseLog % Logged Course readings. (iOS only)
    end
    
    properties(Access = private)
        ShowMessages
        HasSaveWarningBeenIssued = false;
    end
    
    properties(Dependent = true, Access = private)
        ListeningAddress
        OpenedPort
    end
    
    properties(Constant, Access = private, Hidden)
        LocationTimeout = 1;
        IPAndPortPrompt = 1;
        SecondsWithoutInitialData = 60;
        LoopSleepTime = 0.1;
    end
    
    methods(Access = public)
        function obj = sensorgroup(deviceType, varargin)
            % sensorgroup - Create a SENSORGROUP object.
            %   obj = SENSORGROUP(DEVICETYPE, 'Property1', 'Value1', 'Property2', 'Value2', ...),
            %   creates obj, a SENSORGROUP object.
            %
            %   See also: sensorgroup
            
            try
                p = inputParser;
                p.addParamValue('HideTip', false);
                varargin = obj.fixParams(varargin, 'HideTip');
                p.addParamValue('Port', 'auto');
                varargin = obj.fixParams(varargin, 'Port');
                p.addParamValue('IPAddress', 'any');
                varargin = obj.fixParams(varargin, 'IPAddress');
                
                p.parse(varargin{:});
                res = p.Results;
                % verify ShowMessages
                if ~islogical(res.HideTip) && ~isa(res.HideTip, 'function_handle')
                    throw(MException('MATLAB:sensorgroup:InvalidShowMessagesValue', 'The HideTip value should be a logical value.'));
                else
                    if islogical(res.HideTip)
                        res.HideTip = logical(res.HideTip);
                        obj.ShowMessages = ~res.HideTip;
                    else
                        obj.ShowMessages = res.HideTip;        
                    end
                end
                               
                % verify Port
                if isnumeric(res.Port)
                    if res.Port < 0 || res.Port > 65535
                        throw(MException('MATLAB:sensorgroup:InvalidPortNumber', 'The Port value must be between 0 and 65535.'));
                    end
                elseif ischar(res.Port)
                    switch lower(res.Port)
                        case {'a', 'au', 'aut', 'auto'}
                            res.Port = 'auto';
                        otherwise
                            throw(MException('MATLAB:sensorgroup:InvalidPortValue', ...
                                'The Port value must be specified as ''auto'' or be numeric between 0 and 65535.'));
                    end
                end
                % verify IPAddress
                if ischar(res.IPAddress)
                    switch lower(res.IPAddress)
                        case {'a', 'an', 'any'}
                            res.IPAddress = 'any';
                        otherwise
                            if isempty(regexp(res.IPAddress, ...
                                    '\<\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\>', 'once'))
                                % not a dotted quad
                                throw(MException('MATLAB:sensorgroup:InvalidIPAddressForm', ...
                                    'The IPAddress value must be specified as ''any'' or be a valid IP address in the form A.B.C.D.'));
                            else
                                % a dotted quad, but possibly not a valid
                                % IP address
                                try
                                    addr = java.net.InetAddress.getByName(res.IPAddress); %#ok<NASGU>
                                catch %#ok<CTCH>
                                    throw(MException('MATLAB:sensorgroup:InvalidIPAddress', ...
                                        'The IPAddress value must be specified as a valid IP address.'));
                                end
                            end
                    end
                end
                
                obj.Controller = matlab.mobilesensor.internal.MobileSensorController(deviceType, res);
                obj.ParsedConstructorArgs.DeviceType = deviceType;
                obj.ParsedConstructorArgs.Arguments = res;
                try
                    obj.Controller.open();
                catch e
                    if strcmp(e.identifier, 'MATLAB:UDPListener:startUDPServer:unableToOpen') && ...
                            isnumeric(res.Port)
                        if(strcmp(res.IPAddress, 'any'))
                            throw(MException('MATLAB:sensorgroup:CannotOpenPortAnyIP', ...
                                'The UDP port %d cannot be opened for listening. Type %s for more information', res.Port, obj.getHelpString()));
                        else
                            throw(MException('MATLAB:sensorgroup:CannotOpenPortGivenIP', ...
                                'The UDP port %d at address %s cannot be opened for listening. Type %s for more information', res.Port, res.IPAddress, obj.getHelpString()));
                        end
                    else
                        rethrow(e);
                    end
                end
                sleepTime = obj.LoopSleepTime;

                for t = 0:sleepTime:obj.SecondsWithoutInitialData
                    % check if anything has been received
                    if obj.Controller.getPointsAvailable() > 0
                        return;
                    end
                    
                    pause(sleepTime);
                    
                    if (islogical(res.HideTip) && obj.ShowMessages) || isa(res.HideTip, 'function_handle')
                        if t == obj.IPAndPortPrompt;
                            if strcmp(obj.Controller.Platform, 'iOS')
                                str = ['Waiting for data...\n' ...
                                       'To configure your mobile device:\n' ...
                                       '  1.  Open the Sensor Monitor app.\n' ...
                                       '  2.  Select the Network tab.\n' ...
                                       sprintf('  3.  For Host, enter %s\n', obj.getInterfacesString()) ...
                                       sprintf('  4.  For Port, enter %d\n', obj.port()) ...
                                       '  5.  Choose one or more sensor.\n' ...
                                       '  6.  Set Current Send Mode to Binary.\n' ...
                                       '  7.  Tap Start Send.\n' ...
                                       ];
                                if isa(res.HideTip, 'function_handle')
                                    res.HideTip(str);
                                else
                                    fprintf(str);
                                end
                            elseif strcmp(obj.Controller.Platform, 'Android')
                                str = ['Waiting for data...\n' ...
                                       'To configure your mobile device:\n' ...
                                       '  1.  Open the SensorUdp app.\n' ...
                                       sprintf('  2.  For dest. host, enter %s\n', obj.getInterfacesString()) ...
                                       sprintf('  3.  For port, enter %d\n', obj.port()) ...
                                       '  4.  Choose one or more sensor.\n' ...
                                       '  5.  Tap send.\n' ...
                                       ];
                                if isa(res.HideTip, 'function_handle')
                                    res.HideTip(str);
                                else
                                    fprintf(str);
                                end
                            else
                            end
                        end
                        
                    end
                end
                throw(MException('MATLAB:sensorgroup:NoDataReceived', 'No data received. Type %s for help diagnosing the problem.', obj.getHelpString()));
            catch e
                switch e.identifier
                    case 'mobilesensor:MobileSensorChannel:UnknownDeviceType'
                        throw(MException(e.identifier, 'The deviceType should be ''AppleMobile'' or ''AndroidMobile''. Type %s for more information.', obj.getHelpString()));
                    otherwise
                        throwAsCaller(e);
                end
            end
        end
        
        function disp(obj)
            try
                if strcmp(obj.Controller.Platform, 'iOS')
                    fprintf('%s logging data from Apple device on port %d\n\n', ...
                        obj.getSensorgroupString(), obj.port());
                    fprintf('  Measurements: (%s)\n\n', obj.getShowLatestValuesString());
                    fprintf('    Acceleration                        Orientation\n');
                    fprintf('\n');
                    fprintf('    Latitude                            Speed\n');
                    fprintf('    Longitude                           Course\n');
                    fprintf('    Altitude\n');
                    fprintf('                                        MagneticField\n');
                    fprintf('    AngularVelocity                     MagneticDeclination\n');
                    fprintf('    \n\n');
                else % AndroidMobile
                    fprintf('%s logging data from Android device on port %d\n\n', ...
                        obj.getSensorgroupString(), obj.port());
                    fprintf('  Measurements: (%s)\n\n', obj.getShowLatestValuesString());
                    fprintf('    Acceleration                        Orientation\n');
                    fprintf('\n');
                    fprintf('    Latitude                            Speed\n');
                    fprintf('    Longitude\n');
                    fprintf('    Altitude                            MagneticField\n');
                end
                
            catch e
                throwAsCaller(e);
            end
        end
        
        function showLatestValues(obj)
            % showLatestValues - Shows the latest values of the
            % Measurements that have been logged using the sensorgroup
            % object. Those that are available for the given platform of
            % the device (iOS vs. Android) that have not yet had a value
            % logged will be noted at the bottom of the the table in the
            % line that begins with, "Waiting for."
            %
            % MagneticDeclination does not have a corresponding Log
            % property as it does not naturally change over small
            % geographic distances and therefore its Log Size is left blank
            % at all times.
            %
            % When a Measurement has not had a value logged yet, there are
            % several possible reasons. The first reason is that the
            % device simply may not have sent an update to MATLAB yet and
            % this might be due to the update  frequency chosen within the 
            % app itself. The second reason is that you may need to turn on 
            % the measurement within the app. Another possible reason is that 
            % while other measurements may have been successfully transmitted, 
            % it is possible that the packet/s for one or more specific 
            % measurements may have been lost.
            %
            % See also: sensorgroup
            try
                props = obj.baseProperties();
                colOneWidth = max(cellfun(@(x)length(x), props));
                colOneStr = sprintf('\n%s%s', 'Measurement', repmat(' ', 1, colOneWidth - length('Measurement')));
                propValues = cellfun(@(x)obj.(x), props, 'UniformOutput', false);
                maxLengthPropValues = max(cellfun(@(x)length(x), propValues));
                % We want 2 spaces miniumum between columns when in
                % scientific notation such as:
                % MagneticField          1.48e-05,   1.25e-05,  -1.84e-05
                % so we are using 10.2e or 10.2f format specifiers
                colTwoWidth = max(10 * maxLengthPropValues + 2 * (maxLengthPropValues - 1), length('Latest Values'));
                spacesBefore = floor(colTwoWidth / 2 - length('Latest Values') / 2);
                spacesAfter = ceil(colTwoWidth / 2 - length('Latest Values') / 2);
                colTwoStr = sprintf('%s%s%s', repmat(' ', 1, spacesBefore), 'Latest Values', repmat(' ', 1, spacesAfter));
                fprintf('%s  %s   Units   %s\n', colOneStr, colTwoStr, obj.getLogSizeLink());
                fprintf('%s  %s  -------  %s\n', repmat('-', 1, colOneWidth), repmat('-', 1, colTwoWidth), repmat('-', 1, length('Log Size')));
                emptyProps = {};
                for pp = 1:length(props)
                    values = propValues{pp};
                    if isempty(values)
                        emptyProps = {emptyProps{:} props{pp}}; %#ok<CCAT>
                        continue;
                    end
                    measStr = sprintf('%s%s', props{pp}, repmat(' ', 1, colOneWidth - length(props{pp}) + 2));
                    for vv = 1:length(values)
                        if abs(values(vv)) < 0.01
                            if vv ~= length(values)
                                measStr = [measStr sprintf('%10.2e  ', values(vv))]; %#ok<AGROW>
                            else
                                measStr = [measStr sprintf('%10.2e', values(vv))]; %#ok<AGROW>
                            end
                        else
                            if vv ~= length(values)
                                measStr = [measStr sprintf('%10.2f  ', values(vv))]; %#ok<AGROW>
                            else
                                measStr = [measStr sprintf('%10.2f', values(vv))]; %#ok<AGROW>
                            end
                        end
                    end
                    measStr = [measStr sprintf('%s  %s', repmat(' ', 1, colOneWidth + 2 + colTwoWidth - length(measStr)), obj.getUnits(props{pp}))]; %#ok<AGROW>
                    if ~strcmp(props{pp}, 'MagneticDeclination')
                        [measLogM, measLogN] = size(obj.([props{pp} 'Log']));
                        fprintf('%s  <%ix%i>\n', measStr, measLogM, measLogN);
                    else
                        fprintf('%s\n', measStr);
                    end
                end
                if ~isempty(emptyProps)
                    fprintf('\nWaiting for: ');
                    if length(emptyProps) == 1
                        fprintf('%s.  %s.\n\n', emptyProps{end}, obj.getShowLatestValuesMoreInfoString());
                        
                    elseif length(emptyProps) > 1
                        for pp = 1:length(emptyProps) - 1
                            fprintf('%s, ', emptyProps{pp});
                        end
                        fprintf('and %s.  %s.\n\n', emptyProps{end}, obj.getShowLatestValuesMoreInfoString());
                    end
                else
                    fprintf('\n');
                end
                
            catch e
                throwAsCaller(e);
            end
        end

        
        function delete(obj)
            % delete - Stops listening and frees all associated resources
            % delete(obj)
            try
                if isvalid(obj)
                    obj.Controller.delete();
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function discardlogs(obj)
            % discardlogs - Discards all logged measurements and InitialTimestamp 
            % discardlogs(obj)
            try
                obj.Controller.discardLogs();
            catch e
                throwAsCaller(e);
            end
        end

        function [a, t] = accellog(obj)
            % accellog - Returns logged acceleration data
            % [a, t] = accellog(obj)
            % a is an [m x 3] matrix containing acceleration data points 
            % t is an [m x 1] vector of timestamps
            %
            % See also: <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'StepCounter.html'))">Example of step counting with acceleration data</a>
            [a, t] = obj.Controller.accellog();
        end

       
        function [a, t] = angvellog(obj)
            % angvellog - Returns logged angular velocity data
            % [a, t] = angvellog(obj)
            % a is an [m x 3] matrix containing angular velocity data points
            % t is an [m x 1] vector of timestamps
            [a, t] = obj.Controller.angvellog();
        end
        
        function [m, t] = magfieldlog(obj)
            % magfieldlog - Returns logged magnetic field data
            % [m, t] = magfieldlog(obj)
            % m is an [m x 3] matrix containing magnetic field data points 
            % t is an [m x 1] vector of timestamps
            [m, t] = obj.Controller.magfieldlog();
        end
        
        function [o, t] = orientlog(obj)
            % orientlog - Returns logged orientation data
            % [o, t] = orientlog(obj)
            % o is an [m x 3] matrix containing orientation data points
            % t is an [m x 1] vector of timestamps. Each row in matrix o represents azimuth, roll and pitch. 
            % If some values are unavailable, they are represented as NaN
            %
            % See also: <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAzimuthRollPitchExample.html'))">Example of Capturing of Azimuth, Roll and Pitch</a>    
            [o, t] = obj.Controller.orientlog();
        end
        
        function [lat, long, sp, alt, crse, t, llacc, altacc] = poslog(obj)
            % poslog  Returns logged position data
            % [lat, long, sp, alt, crse, t, llacc, altacc] = poslog(obj)
            % lat - [m x 1] vector of latitude values
            % long - [m x 1] vector of longitude values 
            % sp - [m x 1] vector of speed values
            % alt  [m x 1] vector of altitude values
            % crse  [m x 1] vector of course values
            % t  [m x 1] vector of timestamps
            % llacc  [m x 1] vector of horizontal accuracy values
            % altacc  [m x 1] vector of vertical accuracy values
            % Position data is obtained from GPS, Wi-Fi or cellular
            % network, which ever is most appropriate
            %
            % See alos: <a href="matlab:web(fullfile(fileparts(which('sensorgroup')), 'Examples', 'html', 'CapturingAndMappingGPSExample.html'))">Example of Capturing and Mapping GPS</a>
            [lat, long, sp, alt, crse, t, llacc, altacc] = obj.Controller.poslog(); 
        end
    end
    
    methods
        function S = saveobj(obj)
            try
                % Only issue the warning once per object lifetime
                if obj.HasSaveWarningBeenIssued
                    S = [];
                    return
                end
                obj.HasSaveWarningBeenIssued = true;
                
                sWarningBacktrace = warning('off','backtrace');
                oc = onCleanup(@()warning(sWarningBacktrace));
                warning('MATLAB:sensorgroup:SaveNotSupported', ...
                    'sensorgroup objects cannot be saved, for help on saving your data, type %s', ...
                    obj.getHelpString());
                S = [];
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.IPAddress(obj)
            value = obj.Controller.ListeningAddress;
        end
        
        function value = get.Port(obj)
            value = obj.Controller.OpenedPort;
        end

        function value = get.InitialTimestamp(obj)
            if isempty(obj.Controller.InitialTimestamp)
                obj.Controller.readData();
            end
            value = datestr(obj.Controller.InitialTimestamp);
        end
        
        function value = get.Acceleration(obj)
            try
                value = obj.Controller.getCurrentValueFor('Acceleration');
                if isempty(value)
                    value = zeros(0, 3);
                end
            catch e
                if strcmp(e.identifier, 'MATLAB:badsubscript')
                    value = zeros(0, 3);
                else
                    throwAsCaller(e);
                end
            end
        end
        
        function value = get.AccelerationLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('Acceleration');
                if isempty(value)
                    value = zeros(0, 3);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.AngularVelocity(obj)
            try
                value = obj.Controller.getCurrentValueFor('AngularVelocity');
                if isempty(value)
                    value = zeros(0, 3);
                end
            catch e
                if strcmp(e.identifier, 'MATLAB:badsubscript')
                    value = zeros(0, 3);
                else
                    throwAsCaller(e);
                end
            end
        end
        
        function value = get.AngularVelocityLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('AngularVelocity');
                if isempty(value)
                    value = zeros(0, 3);
                end
            catch e
                throwAsCaller(e);
            end
        end
               
        function value = get.Orientation(obj)
            try
                value = obj.Controller.getCurrentValueFor('Orientation');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                if strcmp(e.identifier, 'MATLAB:badsubscript')
                    value = zeros(0, 1);
                else
                    throwAsCaller(e);
                end
            end
        end
        
        
        function value = get.OrientationLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('Orientation');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        
        function value = get.MagneticField(obj)
            try
                value = obj.Controller.getCurrentValueFor('MagneticField');
                if isempty(value)
                    value = zeros(0, 3);
                end
            catch e
                if strcmp(e.identifier, 'MATLAB:badsubscript')
                    value = zeros(0, 3);
                else
                    throwAsCaller(e);
                end
            end
        end
        
        function value = get.MagneticFieldLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('MagneticField');
                if isempty(value)
                    value = zeros(0, 3);
                end
            catch e
                throwAsCaller(e);
            end
        end
                        
        function value = get.MagneticDeclination(obj)
            try
                value = obj.Controller.getCurrentValueFor('MagneticHeading') ...
                    - obj.Controller.getCurrentValueFor('TrueHeading');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                if strcmp(e.identifier, 'MATLAB:badsubscript')
                    value = zeros(0, 1);
                else
                    throwAsCaller(e);
                end
            end
        end
               
        function value = get.HorizontalAccuracy(obj)
            try
                value = obj.Controller.getCurrentValueFor('HorizontalAccuracy');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.HorizontalAccuracyLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('HorizontalAccuracy');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.Latitude(obj)
            try
                value = obj.Controller.getCurrentValueFor('Latitude');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.LatitudeLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('Latitude');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.Longitude(obj)
            try
                value = obj.Controller.getCurrentValueFor('Longitude');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.LongitudeLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('Longitude');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        
        function value = get.Altitude(obj)
            try
                value = obj.Controller.getCurrentValueFor('Altitude');
                if isempty(value)
                    value = zeros(0, 2);
                end
            catch e
                if strcmp(e.identifier, 'MATLAB:badsubscript')
                    value = zeros(0, 2);
                else
                    throwAsCaller(e);
                end
            end
        end
        
        function value = get.AltitudeLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('Altitude');
                if isempty(value)
                    value = zeros(0, 2);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        
        function value = get.AltitudeAccuracy(obj)
            try
                value = obj.Controller.getCurrentValueFor('AltitudeAccuracy');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.AltitudeAccuracyLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('AltitudeAccuracy');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
        
        function value = get.Speed(obj)
            try
                value = obj.Controller.getCurrentValueFor('Speed');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                if strcmp(e.identifier, 'MATLAB:badsubscript')
                    value = zeros(0, 1);
                else
                    throwAsCaller(e);
                end
            end
        end
        
        function value = get.SpeedLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('Speed');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end
               
        function value = get.Course(obj)
            try
                value = obj.Controller.getCurrentValueFor('Course');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                if strcmp(e.identifier, 'MATLAB:badsubscript')
                    value = zeros(0, 1);
                else
                    throwAsCaller(e);
                end
            end
        end
        
        function value = get.CourseLog(obj)
            try
                value = obj.Controller.getLoggedValuesFor('Course');
                if isempty(value)
                    value = zeros(0, 1);
                end
            catch e
                throwAsCaller(e);
            end
        end        
    end
    
    methods(Access = private)
        function str = getInterfacesString(obj)
            str = '';
            cellArray = obj.Controller.AvailableInterfaces;
            if length(cellArray) == 1
                str = cellArray{1};
            elseif length(cellArray) > 1
                for ii = 1:length(cellArray) - 1
                    if ii == 1
                        str = cellArray{1};
                    else
                        str = [str ', ' cellArray{ii}]; %#ok<AGROW>
                    end
                end
                str = [str ', or ' cellArray{length(cellArray)}];
            end
        end
        
        function props = baseProperties(obj)
            
            if strcmp(obj.Controller.Platform, 'iOS')
                props = { ...
                    'Acceleration', ...
                    'AngularVelocity', ...
                    'MagneticField', ...                    
                    'MagneticDeclination', ...
                    'Orientation', ...
                    'Latitude', ...
                    'Longitude', ...
                    'Altitude', ...
                    'Speed', ...
                    'Course' ...
                    };
            else % AndroidMobile
                props = { ...
                    'Acceleration', ...
                    'MagneticField', ...
                    'Orientation', ...
                    'Latitude', ...
                    'Longitude', ...
                    'Altitude', ...
                    'Speed' ...
                    };
            end
            %props = sort(props);
        end
        
        function units = getUnits(~, prop)
            switch prop
                case 'Acceleration'
                    units = 'm/s^2  ';
                case 'AngularVelocity'
                    units = 'rad/s  ';
                case 'MagneticField'
                    units = 'Tesla  ';
                case 'Altitude'
                    units = 'm      ';
                case 'Speed'
                    units = 'm/s    ';
                case {'Azimuth', 'MagneticDeclination', 'Orientation', 'Latitude', 'Longitude', 'Course'}
                    units = 'degrees';
            end
        end
        
        function helpString = getHelpString(obj) %#ok<MANU>
            if feature('hotlinks')
                helpString = '<a href="matlab:helpPopup sensorgroup">"help sensorgroup"</a>';
            else
                helpString = '"help sensorgroup"';
            end
        end
        
        function str = getShowLatestValuesString(obj) %#ok<MANU>
            if feature('hotlinks')
                str = '<a href="matlab:helpPopup sensorgroup/showLatestValues">showLatestValues</a>';
            else
                str = 'showLatestValues';
            end
        end
        
        function str = getShowLatestValuesMoreInfoString(obj) %#ok<MANU>
            if feature('hotlinks')
                str = '<a href="matlab:helpPopup sensorgroup/showLatestValues">More information</a>';
            else
                str = 'More information';
            end
        end
        
        function str = getLogSizeLink(obj) %#ok<MANU>
            if feature('hotlinks')
                str = '<a href="matlab:helpPopup sensorgroup">Log Size</a>';
            else
                str = 'Log Size';
            end
        end
        
        function str = getSensorgroupString(obj) %#ok<MANU>
            if feature('hotlinks')
                str = '<a href="matlab:helpPopup sensorgroup">sensorgroup</a>';
            else
                str = 'sensorgroup';
            end
        end
        
        function out = fixParams(obj, in, param)   %#ok<INUSL>
            out = in;
            param = lower(param);
            for pp = 1:2:length(in)
                lowerIn = lower(in{pp});
                if length(strfind(param, lowerIn)) == 1 && ...
                        strfind(param, lowerIn) == 1
                    out{pp} = param;
                    continue;
                elseif length(strfind(param, lowerIn)) >= 2
                    indices = strfind(param, lowerIn);
                    if indices(1) == 1
                        out{pp} = param;
                        continue;
                    end
                end
                
            end
        end
    end
    
    methods (Hidden)
        % Hide methods inherited from base classes
        
        function c = horzcat(varargin)
            if (nargin == 1)
                c = varargin{1};
            else
                throw(MException('MATLAB:sensorgroup:nohconcatenation', 'Horizontal concatenation of sensorgroup objects is not allowed'));
            end
        end
        function c = vertcat(varargin)
            if (nargin == 1)
                c = varargin{1};
            else
                throw(MException('MATLAB:sensorgroup:novconcatenation', 'Vertical concatenation of sensorgroup objects is not allowed'));
            end
        end
        function c = cat(varargin)
            if (nargin > 2)
                throw(MException('MATLAB:sensorgroup:noconcatenation', 'Concatenation of sensorgroup objects is not allowed'));
            else
                c = varargin{2};
            end
        end
        
        function res = addlistener(obj, varargin)
            res = addlistener@hgsetget(obj, varargin{:});
        end
        function res = addprop(obj, varargin)
            res = addprop@dynamicprops(obj, varargin{:});
        end
        function res = eq(obj, varargin)
            res = eq@handle(obj, varargin{:});
        end
        function res = findobj(obj, varargin)
            res = findobj@handle(obj, varargin{:});
        end
        function res = findprop(obj, varargin)
            res = findprop@handle(obj, varargin{:});
        end
        function res = ge(obj, varargin)
            res = ge@handle(obj, varargin{:});
        end
        function res = gt(obj, varargin)
            res = gt@handle(obj, varargin{:});
        end
        function res = le(obj, varargin)
            res = le@handle(obj, varargin{:});
        end
        function res = lt(obj, varargin)
            res = lt@handle(obj, varargin{:});
        end
        function res = ne(obj, varargin)
            res = ne@handle(obj, varargin{:});
        end
        function res = notify(obj, varargin)
            res = notify@handle(obj, varargin{:});
        end
        
        
    end
    
end

Contact us