Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Sensor.m 21.84 KiB
classdef Sensor < MaskedHandle
    % High-level class to work with sensors.
    % 
    % The Sensor-class facilitates the communication with sensors. This mainly consists of 
    % reading the sensor's type and current value in a specified mode.
    %
    % Notes:
    %     * You don't need to create instances of this class. The EV3-class automatically creates
    %       instances for each sensor port, and you can work with them via the EV3-object. 
    %     * The Sensor-class represents sensor ports, not individual sensors!
    %     * When an input argument of a method is marked as optional, the argument needs to be 
    %       'announced' by a preceding 2nd argument, which is a string containing the name of the argument. 
    %       For example, Motor.setProperties may be given a power-parameter. The syntax would be as
    %       follows: *brickObject.motorA.setProperties('power', 50);*
    %
    % Attributes:
    %    mode (DeviceMode.{Type}): Sensor mode in which the value will be read. By default, mode is set to *DeviceMode.Default.Undefined*. See also :attr:`type`. *[WRITABLE]* |br| Once a physical sensor is connected to the port *and* the physical Brick is connected to the EV3-object, the allowed mode and the default mode for a Sensor-object are the following (depending on the sensor type): 
    %
    %             * Touch-Sensor: 
    %                 * DeviceMode.Touch.Pushed *[Default]*
    %                 * DeviceMode.Touch.Bumps
    %             * Ultrasonic-Sensor: 
    %                 * DeviceMode.UltraSonic.DistCM *[Default]*
    %                 * DeviceMode.UltraSonic.DistIn
    %                 * DeviceMode.UltraSonic.Listen
    %             * Color-Sensor: 
    %                 * DeviceMode.Color.Reflect *[Default]*
    %                 * DeviceMode.Color.Ambient
    %                 * DeviceMode.Color.Col
    %             * Gyro-Sensor: 
    %                 * DeviceMode.Gyro.Angular *[Default]*
    %                 * DeviceMode.Gyro.Rate
    %             * Infrared-Sensor:
    %                 * DeviceMode.InfraRed.Prox *[Default]*
    %                 * DeviceMode.InfraRed.Seek
    %                 * DeviceMode.InfraRed.Remote
    %             * NXTColor-Sensor:
    %                 * DeviceMode.NXTColor.Reflect *[Default]*
    %                 * DeviceMode.NXTColor.Ambient
    %                 * DeviceMode.NXTColor.Color
    %                 * DeviceMode.NXTColor.Green
    %                 * DeviceMode.NXTColor.Blue
    %                 * DeviceMode.NXTColor.Raw
    %             * NXTLight-Sensor:
    %                 * DeviceMode.NXTLight.Reflect *[Default]*
    %                 * DeviceMode.NXTLight.Ambient
    %             * NXTSound-Sensor:
    %                 * DeviceMode.NXTSound.DB *[Default]*
    %                 * DeviceMode.NXTSound.DBA
    %             * NXTTemperature-Sensor
    %                 * DeviceMode.NXTTemperature.C *[Default]*
    %                 * DeviceMode.NXTTemperature.F
    %             * NXTTouch-Sensor:
    %                 * DeviceMode.NXTTouch.Pushed *[Default]*
    %                 * DeviceMode.NXTTouch.Bumps
    %             * NXTUltraSonic-Sensor:
    %                 * DeviceMode.NXTUltraSonic.CM *[Default]*
    %                 * DeviceMode.NXTUltraSonic.IN
    %             * HTAccelerometer-Sensor:
    %                 * DeviceMode.HTAccelerometer.Acceleration *[Default]*
    %                 * DeviceMode.HTAccelerometer.AccelerationAllAxes
    %             * HTCompass-Sensor:
    %                 * DeviceMode.HTCompass.Degrees *[Default]*
    %             * HTColor-Sensor:
    %                 * DeviceMode.HTColor.Col *[Default]*
    %                 * DeviceMode.HTColor.Red
    %                 * DeviceMode.HTColor.Green
    %                 * DeviceMode.HTColor.Blue
    %                 * DeviceMode.HTColor.White
    %                 * DeviceMode.HTColor.Raw
    %                 * DeviceMode.HTColor.Nr,
    %                 * DeviceMode.HTColor.All
    %    debug (bool): Debug turned on or off. In debug mode, everytime a command is passed to 
    %        the sublayer ('communication layer'), there is feedback in the console about what 
    %        command has been called. *[WRITABLE]*
    %    value (numeric): Value read from hysical sensor. What the value represents depends on
    %        :attr:`mode`. *[READ-ONLY]*
    %    type (DeviceType): Type of physical sensor connected to the port. Possible types are: [READ-ONLY]
    %
    %             * DeviceType.NXTTouch
    %             * DeviceType.NXTLight
    %             * DeviceType.NXTSound
    %             * DeviceType.NXTColor
    %             * DeviceType.NXTUltraSonic
    %             * DeviceType.NXTTemperature
    %             * DeviceType.LargeMotor
    %             * DeviceType.MediumMotor
    %             * DeviceType.Touch 
    %             * DeviceType.Color 
    %             * DeviceType.UltraSonic 
    %             * DeviceType.Gyro 
    %             * DeviceType.InfraRed 
    %             * DeviceType.HTColor
    %             * DeviceType.HTCompass
    %             * DeviceType.HTAccelerometer
    %             * DeviceType.Unknown
    %             * DeviceType.None 
    %             * DeviceType.Error
    
    properties  % Standard properties to be set by user
        % mode (DeviceMode.{Type}): Sensor mode in which the value will be read. By default, 
        %     mode is set to DeviceMode.Default.Undefined. Once a physical sensor is connected
        %     to the port *and* the physical Brick is connected to the EV3-object, the allowed 
        %     mode and the default mode for a Sensor-object are the following (depending on the
        %     sensor type): [WRITABLE]
        %
        %     * Touch-Sensor: 
        %         * DeviceMode.Touch.Pushed [Default]
        %         * DeviceMode.Touch.Bumps
        %     * Ultrasonic-Sensor: 
        %         * DeviceMode.UltraSonic.DistCM [Default]
        %         * DeviceMode.UltraSonic.DistIn
        %         * DeviceMode.UltraSonic.Listen
        %     * Color-Sensor: 
        %         * DeviceMode.Color.Reflect [Default]
        %         * DeviceMode.Color.Ambient
        %         * DeviceMode.Color.Col
        %     * Gyro-Sensor: 
        %         * DeviceMode.Gyro.Angular [Default]
        %         * DeviceMode.Gyro.Rate
        %     * Infrared-Sensor:
        %         * DeviceMode.InfraRed.Prox [Default]
        %         * DeviceMode.InfraRed.Seek
        %         * DeviceMode.InfraRed.Remote
        %     * NXTColor-Sensor:
        %         * DeviceMode.NXTColor.Reflect [Default]
        %         * DeviceMode.NXTColor.Ambient
        %         * DeviceMode.NXTColor.Color
        %         * DeviceMode.NXTColor.Green
        %         * DeviceMode.NXTColor.Blue
        %         * DeviceMode.NXTColor.Raw
        %     * NXTLight-Sensor:
        %         * DeviceMode.NXTLight.Reflect [Default]
        %         * DeviceMode.NXTLigth.Ambient
        %     * NXTSound-Sensor:
        %         * DeviceMode.NXTSound.DB [Default]
        %         * DeviceMode.NXTSound.DBA
        %     * NXTTemperature-Sensor
        %         * DeviceMode.NXTTemperature.C [Default]
        %         * DeviceMode.NXTTemperature.F
        %     * NXTTouch-Sensor:
        %         * DeviceMode.NXTTouch.Pushed [Default]
        %         * DeviceMode.NXTTouch.Bumps
        %     * NXTUltraSonic-Sensor:
        %         * DeviceMode.NXTUltraSonic.CM [Default]
        %         * DeviceMode.NXTUltraSonic.IN
        %     * HTAccelerometer-Sensor:
        %         * DeviceMode.HTAccelerometer.Acceleration [Default]
        %         * DeviceMode.HTAccelerometer.AccelerationAllAxes
        %     * HTCompass-Sensor:
        %         * DeviceMode.HTCompass.Degrees [Default]
        %     * HTColor-Sensor:
        %         * DeviceMode.HTColor.Col [Default]
        %         * DeviceMode.HTColor.Red
        %         * DeviceMode.HTColor.Green
        %         * DeviceMode.HTColor.Blue
        %         * DeviceMode.HTColor.White
        %         * DeviceMode.HTColor.Raw
        %         * DeviceMode.HTColor.Nr,
        %         * DeviceMode.HTColor.All
        % See also SENSOR.VALUE, SENSOR.TYPE 
        mode;
        
        % debug (bool): Debug turned on or off. In debug mode, everytime a command is passed to 
        %     the sublayer ('communication layer'), there is feedback in the console about what 
        %     command has been called. [WRITABLE]
        debug;
    end
    
    properties (Dependent)  % Parameters to be read directly from physical brick
        % value (numeric): Value read from physical sensor. [READ-ONLY]
        %     What the value represents depends on sensor.mode. 
        % See also SENSOR.MODE
        value;
        
        % type (DeviceType): Type of physical sensor connected to the port. [READ-ONLY]
        %     Possible types are:
        %     * DeviceType.NXTTouch
        %     * DeviceType.NXTLight
        %     * DeviceType.NXTSound
        %     * DeviceType.NXTColor
        %     * DeviceType.NXTUltraSonic
        %     * DeviceType.NXTTemperature
        %     * DeviceType.LargeMotor
        %     * DeviceType.MediumMotor
        %     * DeviceType.Touch 
        %     * DeviceType.Color 
        %     * DeviceType.UltraSonic 
        %     * DeviceType.Gyro 
        %     * DeviceType.InfraRed 
        %     * DeviceType.HTColor
        %     * DeviceType.HTCompass
        %     * DeviceType.HTAccelerometer
        %     * DeviceType.Unknown
        %     * DeviceType.None 
        %     * DeviceType.Error
        type; 
    end

    properties (Hidden, Access = private)  % Hidden properties for internal use only 
        % commInterface (CommunicationInterface): Commands are created and sent via the 
        %     communication interface class.
        commInterface; 
        
        % port (string): Sensor port. 
        %     This is only the string representation of the sensor port to work with.
        %     Internally, SensorPort-enums are used.
        port; 
        
        % init (bool): Indicates init-phase (i.e. constructor is running).
        init = true;
        
        % connectedToBrick (bool): True if virtual brick is connected to physical brick.
        connectedToBrick = false;
    end   
    
    properties (Hidden, Dependent, Access = private)
        %physicalSensorConnected (bool): True if physical sensor is connected to this port
        physicalSensorConnected;
    end
    
    methods  % Standard methods
        %% Constructor
        function sensor = Sensor(varargin)
            % Sets properties of Sensor-object and indicates end of init-phase when it's done
            %
            % Notes:
            %     * input-arguments will directly be handed to Motor.setProperties
            %
            % Arguments:
            %     varargin: see setProperties(sensor, varargin)
            %
            
            sensor.setProperties(varargin{1:end});
			sensor.init = false;
        end
        
        %% Brick functions
        function reset(sensor)
            % Resets sensor value.
            %
			% Notes:
            %     * This clears ALL the sensors right now, no other Op-Code available... :(
            %
            
            if ~sensor.connectedToBrick
                error('Sensor::reset: Sensor-Object not connected to comm handle.');
            elseif ~sensor.physicalSensorConnected
                error('Sensor::reset: No physical sensor connected to Port %d.',...
                       sensor.port+1);
            end
            
%             warning(['Sensor::reset: Current version of reset resets ALL devices, that is, ',...
%                      'all motor tacho counts and all other sensor counters!']);
            sensor.execute(@inputDeviceClrAll, 0);
        end
        
        %% Setter
        function set.mode(sensor, mode)
            if strcmp(class(mode),'DeviceMode.Default') && ~sensor.physicalSensorConnected
                sensor.mode = mode;
                return;
            end
            
            type = sensor.type;
            if ~isModeValid(mode, type)
                error('Sensor::set.mode: Invalid sensor mode.');
            else
                sensor.mode = mode;
                
                if ~strcmp(class(mode),'DeviceMode.Default') && sensor.connectedToBrick 
                    try
                        sensor.setMode(mode);  % Update physical brick's mode parameter
                    catch
                        % Ignore
                    end
                end
            end
        end
        
        function set.debug(sensor, debug)
            % Check if debug is valid and set sensor.debug if it is.
            if ~isBool(debug)
                error('Sensor::set.debug: Given parameter is not a bool.');
            end
            
            sensor.debug = str2bool(debug);
        end
        
        function set.port(sensor, port)
            if ~isPortStrValid(class(sensor),port)
                error('Sensor::set.port: Given port is not a valid port.');
            else
                sensor.port = str2PortParam(class(sensor), port);
            end
        end
        
        function set.commInterface(sensor, comm)
            if ~isCommInterfaceValid(comm)
                error('Sensor::set.commInterface: Handle to commInterface not valid.');
            else
                sensor.commInterface = comm;
            end
        end
        
        function setProperties(sensor, varargin)
            % Sets multiple Sensor properties at once using MATLAB's inputParser.
            %
            % Arguments:
            %     debug (bool): *[OPTIONAL]*
            %     mode (DeviceMode.{Type}): *[OPTIONAL]*
            %
            % Example:
            %     b = EV3(); % |br|
            %     b.connect('bt', 'serPort', '/dev/rfcomm0'); % |br|
            %     b.sensor1.setProperties('debug', 'on', 'mode', DeviceMode.Color.Ambient); % |br|
            %     % Instead of: b.sensor1.debug = 'on'; |br|
            %     %             b.sensor1.mode = DeviceMode.Color.Ambient; |br|
            %
            p = inputParser();
            
            % Set default values
            if sensor.init
                defaultDebug = 0;
                defaultMode = DeviceMode.Default.Undefined;
            else
                defaultDebug = sensor.debug;
                defaultMode = sensor.mode;
            end
            
            % Add parameter
            if sensor.init
                p.addRequired('port');
            end
            p.addOptional('debug', defaultDebug);
            p.addOptional('mode', defaultMode);
            
            % Parse...
            p.parse(varargin{:});
            
            % Set properties
            if sensor.init
                sensor.port = p.Results.port;
            end
            sensor.mode = p.Results.mode;
            sensor.debug = p.Results.debug;
        end
        
        %% Getter
        function value = get.value(sensor)
            value = 0;
            defaultMode = -1;
            
            if sensor.connectedToBrick
                value = sensor.getValue(defaultMode);
                if isnan(value)
                    warning('Sensor::get.value: Could not detect sensor at port %d.', ...
                        sensor.port+1);
                    value = 0;
                end
            end
        end
        
        function conn = get.physicalSensorConnected(sensor)
            currentType = sensor.type;
            conn = (currentType<DeviceType.Unknown && ... 
                (currentType~=DeviceType.MediumMotor && currentType~=DeviceType.LargeMotor));
        end
        
        function sensorType = get.type(sensor)
            if sensor.connectedToBrick
                [sensorType, ~] = sensor.getTypeMode(); 
            else
                sensorType = DeviceType.Unknown;
            end
        end
        
        %% Display
        function display(sensor)
            displayProperties(sensor); 
        end
    end
    
    methods (Access = private)  % Private brick functions that are wrapped by dependent params
        function setMode(sensor, mode)
            if ~sensor.connectedToBrick
                error('Sensor::getTachoCount: Sensor-Object not connected to comm handle.');
            elseif ~sensor.physicalSensorConnected
                error('Sensor::getTachoCount: No physical sensor connected to Port %d',...
                       sensor.port+1);
            end
            
            sensor.execute(@inputReadSI, 0, sensor.port, mode);  % Reading a value implicitly
                                                             % sets the mode.
        end
        
        function val = getValue(sensor, varargin)
            %getValue Reads value from sensor
            % 
            % Notes:
            %  * After changing the mode, sensors initially always send an invalid value. In
            %    this case, the inputReadSI-opCode is sent again to get the correct value.
            %
            
            if ~isempty(varargin)
                defaultMode = varargin{1};
                
                % 5 is numerically highest available number of modes for a sensor(NXT Color)
                if ~isnumeric(defaultMode) || defaultMode > 5
                     error('Sensor::getValue: Invalid mode');
                end
            else
                defaultMode = -1;
            end
            
            if ~sensor.connectedToBrick
                error('Sensor::getValue: Sensor-Object not connected to comm handle.');
            end
            
            if defaultMode ~= -1
                mode = defaultMode;
            else
                mode = sensor.mode;
            end
            		
            val = sensor.execute(@inputReadSI, 0, sensor.port, sensor.mode);
            
            
            if strcmp(class(sensor.mode), 'DeviceMode.Color')
                if sensor.mode == DeviceMode.Color.Col
                    val = Color(val);
                end
            end
            
            % See note
			if isnan(val)
				val = sensor.execute(@inputReadSI, 0, sensor.port, sensor.mode);
            end
        end
        
        function status = getStatus(sensor)
           if ~sensor.connectedToBrick
                error('Sensor::getStatus: Sensor-Object not connected to comm handle.');
           end
           
           statusNo = sensor.execute(@inputDeviceGetConnection, 0, sensor.port);
           status = ConnectionType(statusNo);
        end
        
        function [type,mode] = getTypeMode(sensor)
           if ~sensor.connectedToBrick
                error('Sensor::getTypeMode: Sensor-Object not connected to comm handle.');
           end
           
            type = DeviceType.Error;
            
            for i = 1:10
                try
                    [typeNo,modeNo] = sensor.execute(@inputDeviceGetTypeMode, 0, sensor.port);
                    type = DeviceType(typeNo);
                catch ME
                    continue;
                end
                break;
            end
                
            try
                mode = DeviceMode(type,modeNo);
            catch ME
                mode = DeviceMode.Default.Undefined;
            end
        end
        
    end
    
    methods (Access = private)
       function varargout = execute(sensor, commandHandle, varargin)
            % Execute a CommunicationInterface-method given as a handle
            %
            % As those methods have different, fixed numbers of output arguments, this quantity
            % has to be retrieved first.
            
            if sensor.debug
                fprintf('(DEBUG) Sending %s\n', func2str(commandHandle));
            end
            
            % Note: Arrg. MATLAB does not support nargout for class methods directly, so I have to
            % do this ugly workaround using strings. See 
            % https://de.mathworks.com/matlabcentral/answers/96617-how-can-i-use-nargin-nargout-to-determine-the-number-of-input-output-arguments-of-an-object-method
            nOut = nargout(strcat('CommunicationInterface>CommunicationInterface.', func2str(commandHandle)));
            [varargout{1:nOut}] = commandHandle(sensor.commInterface, varargin{:});
        end 
    end
    
    methods (Access = ?EV3)
        function connect(sensor,commInterface)
            %connect Connects Sensor-object to physical brick
            
            if sensor.connectedToBrick
                if isCommInterfaceValid(sensor.commInterface)
                    error('Sensor::connect: Sensor-Object already has a comm handle.');
                else
                    warning(['Sensor::connect: Sensor.connectedToBrick is set to ''True'', but ',...
                        'comm handle is invalid. Deleting invalid handle and ' ,...
                        'resetting Sensor.connectedToBrick now...']);
                    
                    sensor.commInterface = 0;
                    sensor.connectedToBrick = false;
                    
                    error('Sensor::connect: Disconnected due to internal error.');
                end
            end
            
            sensor.commInterface = commInterface;
            sensor.connectedToBrick = true;
            
            if sensor.debug
                fprintf('(DEBUG) Sensor-Object connected to comm handle.\n');
            end
        end
        
        function disconnect(sensor)
            %disconnect Disconnects Sensor-object from physical brick
            
            sensor.commInterface = 0; % Note: actual deleting is done in EV3::disconnect.
            sensor.connectedToBrick = false;
        end
        
        function resetPhysicalSensor(sensor)
            if ~sensor.connectedToBrick || ~sensor.physicalSensorConnected
                return
            end
            
            try
                sensor.mode = DeviceMode(sensor.type, uint8(0));
                sensor.reset;
            catch ME
                % For now: ignore...
            end
        end
    end
end