Skip to content
Snippets Groups Projects
Select Git revision
  • 879220a1c153d24c0be5300d2b24df982dcf8d1a
  • master default protected
  • dev_2022
  • patch-1
  • develop
  • 50-use-ubuntus-libhidapi
  • issue-highLevelDispatch
  • issue-highLevelDesign
  • issue-motorStartBug
  • issue-commandLayerDesign
  • v1.0
  • v0.4-rc.13
  • v0.4-rc.12
  • v0.4-rc.11
  • v0.4-rc.10
  • v0.4-rc.9
  • v0.3-rc.8
  • v0.3-rc.7
  • v0.3-rc.6
  • v0.3-rc.5
  • v0.3-rc.4
  • v0.3-rc.3
  • v0.3-rc.2
  • v0.3-rc.1
  • v0.3-rc
  • v0.2
  • v0.1.1
  • v0.1
28 results

EV3.m

Blame
  • Tim Stadtmann's avatar
    Tim Stadtmann authored
    879220a1
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    EV3.m 20.01 KiB
    classdef EV3 < MaskedHandle
        % High-level class to work with physical bricks.
        %
        % This is the 'central' class (from user's view) when working with this toolbox. It
        % delivers a convenient interface for creating a connection to the brick and sending
        % commands to it. An EV3-object creates 4 Motor- and 4 Sensor-objects, one for each port.
        %
        %
        % Notes:
        %     * Creating multiple EV3 objects and connecting them to different physical bricks has not
        %       been thoroughly tested yet, but seems to work on a first glance.
        %     * 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:
        %     motorA (Motor): Motor-object interfacing port A. See also :class:`Motor`.
        %     motorB (Motor): Motor-object interfacing port B. See also :class:`Motor`.
        %     motorC (Motor): Motor-object interfacing port C. See also :class:`Motor`.
        %     motorD (Motor): Motor-object interfacing port D. See also :class:`Motor`.
        %     sensor1 (Sensor): Motor-object interfacing port 1. See also :class:`Sensor`.
        %     sensor2 (Sensor): Motor-object interfacing port 2. See also :class:`Sensor`.
        %     sensor3 (Sensor): Motor-object interfacing port 3. See also :class:`Sensor`.
        %     sensor4 (Sensor): Motor-object interfacing port 4. See also :class:`Sensor`.
        %     debug (numeric in {0,1,2}): Debug mode. *[WRITABLE]*
        %
        %         - 0: Debug turned off
        %         - 1: Debug turned on for EV3-object -> enables feedback in the console about what firmware-commands have been called when using a method
        %         - 2: Low-level-Debug turned on -> each packet sent and received is printed to the console
        %
        %     batteryMode (string in {'Percentage', 'Voltage'}): Mode for reading battery charge. See also :attr:`batteryValue`. *[WRITABLE]*
        %     batteryValue (numeric): Current battery charge. Depending on batteryMode, the reading is either in percentage or voltage. See also :attr:`batteryMode`. *[READ-ONLY]*
        %     isConnected (bool): True if virtual brick-object is connected to physical one. *[READ-ONLY]*
        %
        %
        % Example:
        %     % This example expects a motor at port A and a (random) sensor at port 1 |br|
        %     b = EV3(); % |br|
        %     b.connect('usb'); % |br|
        %     ma = b.motorA; % |br|
        %     ma.setProperties('power', 50, 'limitValue', 720); % |br|
        %     ma.start(); % |br|
        %     % fun |br|
        %     ma.waitFor(); % |br|
        %     disp(b.sensor1.value); % |br|
        %     b.beep(); % |br|
        %     delete b; % |br|
    
        properties
            % batteryMode (string in {'Percentage', 'Voltage'}): Mode for reading battery charge. [WRITABLE]
            % See also BATTERYVALUE
            batteryMode;
    
            % debug (numeric in {0,1,2}): Debug mode. [WRITABLE]
            %     - 0: Debug turned off
            %     - 1: (High-level-) Debug turned on for EV3-object - enables feedback in the
            %          console about what firmware-commands have been called when using a method
            %     - 2: Low-level-Debug turned on - each packet sent and received is printed to the
            %          console
            debug;
        end
    
        properties (Dependent)  % Parameters to be read directly from physical brick
            % batteryValue (numeric): Current battery charge. Depending on batteryMode, the reading
            %     is either in percentage or voltage. [READ-ONLY]
            % See also BATTERYMODE
            batteryValue;
        end
    
        properties (SetAccess = private)  % Read-only properties that are set internally
            % isConnected (bool): True if virtual brick-object is connected to physical one. [READ-ONLY]
            isConnected = false;
    
            % motorA (Motor): Motor-object interfacing port A.
            % See also MOTOR
            motorA;
            % motorB (Motor): Motor-object interfacing port B.
            % See also MOTOR
            motorB;
            % motorC (Motor): Motor-object interfacing port C.
            % See also MOTOR
            motorC;
            % motorD (Motor): Motor-object interfacing port D.
            % See also MOTOR
            motorD;
    
            % sensor1 (Sensor): Sensor-object interfacing port 1.
            % See also SENSOR
            sensor1;
            % sensor2 (Sensor): Sensor-object interfacing port 2.
            % See also SENSOR
            sensor2;
            % sensor3 (Sensor): Sensor-object interfacing port 3.
            % See also SENSOR
            sensor3;
            % sensor4 (Sensor): Sensor-object interfacing port 4.
            % See also SENSOR
            sensor4;
        end
    
        properties (Access = private)
            % commInterface (CommunicationInterface): Interface to communication layer
            %     All commands sent to the Brick are created and written through this object. Each
            %     Motor- and Sensor-object has a reference to it.
            commInterface = 0;
        end
    
        properties (Hidden, Access = private)  % Hidden properties for internal use only
            % init (bool): Indicates init-phase (i.e. constructor is running).
            init = true;
        end
    
        methods  % Standard methods
            %% Constructor
            function ev3 = EV3(varargin)
                % Sets properties of EV3-object and creates Motor- and Sensor-objects with default
                % parameters.
                %
                % Arguments:
                %     varargin: see setProperties(ev3, varargin).
                %
                % See also SETPROPERTIES / :meth:`setProperties(ev3, varargin)`
    
                ev3.setProperties(varargin{:});
    
                ev3.motorA = Motor('A', 'Debug', ev3.debug>0);
                ev3.motorB = Motor('B', 'Debug', ev3.debug>0);
                ev3.motorC = Motor('C', 'Debug', ev3.debug>0);
                ev3.motorD = Motor('D', 'Debug', ev3.debug>0);
    
    
                ev3.sensor1 = Sensor('1', 'Debug', ev3.debug>0);
                ev3.sensor2 = Sensor('2', 'Debug', ev3.debug>0);
                ev3.sensor3 = Sensor('3', 'Debug', ev3.debug>0);
                ev3.sensor4 = Sensor('4', 'Debug', ev3.debug>0);
    
                ev3.init = false;
            end
    
            function delete(ev3)
                % Disconnects from physical brick and deletes this instance.
    
                if ev3.isConnected
                    ev3.disconnect();
                end
            end
    
            %% Connection
            function connect(ev3, varargin)
                % Connects EV3-object and its Motors and Sensors to physical brick.
                %
                % Arguments:
                %     connectionType (string in {'bt', 'usb'}): Connection type
                %     serPort (string in {'/dev/rfcomm1', '/dev/rfcomm2', ...}): Path to serial port
                %         (necessary if connectionType is 'bt'). *[OPTIONAL]*
                %     beep (bool): If true, EV3 beeps if connection has been established. *[OPTIONAL]*
                %
                % Example:
                %     % Setup bluetooth connection via com-port 0 |br|
                %     b = EV3(); % |br|
                %     b.connect('bt', 'serPort', '/dev/rfcomm0'); % |br|
                %     % Setup usb connection, beep when connection has been established
                %     b = EV3(); % |br|
                %     b.connect('usb', 'beep', 'on', ); % |br|
                %
                % See also ISCONNECTED / :attr:`isConnected`
    
                if ev3.isConnected
                    if isCommInterfaceValid(ev3.commInterface)
                        warning('EV3::connect: Already connected. Resetting connection now...');
                        ev3.disconnect();
                    else
                        warning(['EV3::connect: EV3.isConnected is set to ''True'', but ',...
                                 'comm handle is invalid. Deleting invalid handle and ' ,...
                                 'resetting EV3.isConnected now...']);
    
                        ev3.commInterface = 0;
                        ev3.isConnected = false;
                    end
                end
    
                if nargin < 2
                     error('EV3::connect: Wrong number of input arguments.');
                end
    
                idxes = strcmpi('beep', varargin);
                idx = find([0, idxes(1:end-1)]);
                if ~isempty(idx)
                    beep = varargin{idx}; %#ok<FNDSB>
                    if ~isBool(beep)
                        error('EV3::connect: Argument after ''beep'' has to be a bool.');
                    end
                else
                    beep = false;
                end
    
                % Try to connect
                try
                    % Connect to physical brick
                    % -> Creating communication-handle implicitly establishes connection
                    ev3.commInterface = CommunicationInterface(varargin{:}, 'debug', ev3.debug>=2);
                    ev3.isConnected = true;
    
                    if beep
                        ev3.beep();
                    end
    
                    % Connect motors
                    ev3.motorA.connect(ev3.commInterface);
                    ev3.motorB.connect(ev3.commInterface);
                    ev3.motorC.connect(ev3.commInterface);
                    ev3.motorD.connect(ev3.commInterface);
    
                    % Connect sensors
                    ev3.sensor1.connect(ev3.commInterface);
                    ev3.sensor2.connect(ev3.commInterface);
                    ev3.sensor3.connect(ev3.commInterface);
                    ev3.sensor4.connect(ev3.commInterface);
                catch ME
                    % Something went wrong...
                    ev3.isConnected = false;
                    if isCommInterfaceValid(ev3.commInterface) && ev3.commInterface ~= 0
                        ev3.commInterface.delete();
                        ev3.commInterface = 0;
                    end
    
                    rethrow(ME);
                end
            end
    
            function disconnect(ev3)
                % Disconnects EV3-object and its Motors and Sensors from physical brick.
                %
                % Notes:
                %     * Gets called automatically when EV3-object is destroyed.
                %
                % Example:
                %     b = EV3(); % |br|
                %     b.connect('bt', 'serPort', '/dev/rfcomm0'); % |br|
                %     % do stuff |br|
                %     b.disconnect(); % |br|
                
                % Resetting needs a working connection in order to send reset-commands
                % to the Brick. If the connection has been aborted (e.g. by pulling the
                % USB-cord), the reset-methods would fail -> catch this error and for
                % now do nothing.
                try
                    ev3.resetPhysicalBrick();
                catch ME
                    % For now: ignore
                end
    
                % Disconnect motors and sensors
                % -> set references to comm handle to 0
                ev3.motorA.disconnect();
                ev3.motorB.disconnect();
                ev3.motorC.disconnect();
                ev3.motorD.disconnect();
    
                ev3.sensor1.disconnect();
                ev3.sensor2.disconnect();
                ev3.sensor3.disconnect();
                ev3.sensor4.disconnect();
    
                % Delete handle to comm-interface
                if isCommInterfaceValid(ev3.commInterface) && ev3.commInterface ~= 0
                    ev3.commInterface.delete();
                end
                ev3.commInterface = 0;
    
                ev3.isConnected = false;
            end
    
            %% Device functions
            function stopAllMotors(ev3)
                % Sends a stop-command to all motor-ports.
    
                if ~ev3.isConnected
                    error(['EV3::stopAllMotors: Brick-Object not connected physical brick. ',...
                           'You have to call ev3.connect(...) first!']);
                end
    
                ev3.commInterface.outputStopAll();
            end
    
            %% Sound functions
            function beep(ev3)
                % Plays a 'beep'-tone on brick.
                %
                % Notes:
                %     * This equals playTone(10, 1000, 100).
                %
                % Example:
                %     b = EV3(); % |br|
                %     b.connect('bt', 'serPort', '/dev/rfcomm0'); % |br|
                %     b.beep(); % |br|
                %
    
                if ~ev3.isConnected
                    error(['EV3::beep: Brick-Object not connected physical brick. ',...
                           'You have to call ev3.connect(...) first!']);
                end
    
                ev3.handleCommand(@soundPlayTone, 10, 1000, 100);
            end
    
            function playTone(ev3, volume, frequency, duration)
                % Plays tone on brick.
                %
                % Arguments:
                %     volume (numeric in [0, 100]): in percent
                %     frequency (numeric in [250, 10000]): in Hertz
                %     duration (numeric > 0): in milliseconds
                %
                % Example:
                %     b = EV3(); % |br|
                %     b.connect('bt', 'serPort', '/dev/rfcomm0'); % |br|
                %     b.playTone(40, 5000, 1000);  % Plays tone with 40% volume and 5000Hz for 1
                %     second. |br|
                %
    
                if ~ev3.isConnected
                    error(['EV3::isConnected: Brick-Object not connected physical brick. ',...
                           'You have to call ev3.connect(...) first!']);
                end
    
                ev3.handleCommand(@soundPlayTone, volume, frequency, duration);
            end
    
            function stopTone(ev3)
                % Stops tone currently played.
                %
                % Example:
                %     b = EV3(); % |br|
                %     b.connect('bt', 'serPort', '/dev/rfcomm0'); % |br|
                %     b.playTone(10,100,100000000);  % Accidentally given wrong tone duration :) |br|
                %     b.stopTone();  % Stops tone immediately. |br|
                %
    
                if ~ev3.isConnected
                    error(['EV3::stopTone: Brick-Object not connected physical brick. ',...
                           'You have to call ev3.connect(...) first!']);
                end
    
                ev3.handleCommand(@soundStopTone);
            end
    
            function status = tonePlayed(ev3)
                % Tests if tone is currently played.
                %
                % Returns:
                %     status (bool): True if a tone is being played
                %
                % Example
                %     b = EV3(); % |br|
                %     b.connect('bt', 'serPort', '/dev/rfcomm0'); % |br|
                %     b.playTone(10, 100, 1000); % |br|
                %     pause(0.5); % Small pause is necessary as tone does not start instantaneously |br|
                %     b.tonePlayed(); % -> Outputs 1 to console.  |br|
                %
    
                if ~ev3.isConnected
                    error(['EV3::tonePlayed: Brick-Object not connected physical brick. ',...
                           'You have to call ev3.connect(...) first!']);
                end
    
                status = ev3.handleCommand(@soundTest);
            end
    
            %% Setter
            function set.commInterface(ev3, comm)
                if ~isCommInterfaceValid(comm)
                    error('EV3::set.commInterface: Handle to Brick-object not valid.');
                end
    
                ev3.commInterface = comm;
            end
    
            function set.batteryMode(ev3, batteryMode)
                validModes = {'Voltage', 'Percentage'};
                if ~ischar(batteryMode) || ~ismember(batteryMode, validModes)
                    error('EV3::set.batteryMode: Given parameter is not a valid battery mode.');
                end
    
                ev3.batteryMode = batteryMode;
            end
    
            function set.debug(ev3, debug)
                if ~isBool(debug) && debug ~= 2
                    error('EV3::set.debug: Given parameter is not a bool.');
                end
    
                ev3.debug = str2bool(debug);
    
                if ev3.isConnected
                    ev3.commInterface.debug = (ev3.debug >= 2);
                end
    
                ev3.motorA.debug = (ev3.debug > 0);
                ev3.motorB.debug = (ev3.debug > 0);
                ev3.motorC.debug = (ev3.debug > 0);
                ev3.motorD.debug = (ev3.debug > 0);
    
                ev3.sensor1.debug = (ev3.debug > 0);
                ev3.sensor2.debug = (ev3.debug > 0);
                ev3.sensor3.debug = (ev3.debug > 0);
                ev3.sensor4.debug = (ev3.debug > 0);
            end
    
            function setProperties(ev3, varargin)
                % Set multiple EV3 properties at once using MATLAB's inputParser.
                %
                % Arguments:
                %     debug (numeric in {0,1,2}): see EV3.debug *[OPTIONAL]*
                %     batteryMode (string in {'Voltage'/'Percentage'}): see EV3.batteryMode *[OPTIONAL]*
                %
                % Example:
                %     b = EV3(); % |br|
                %     b.connect('bt', 'serPort', '/dev/rfcomm0'); % |br|
                %     b.setProperties('debug', 'on', 'batteryMode', 'Voltage'); % |br|
                %     % Instead of: b.debug = 'on'; b.batteryMode = 'Voltage'; % |br|
                %
                % See also EV3.DEBUG, EV3.BATTERYMODE / :attr:`debug`, :attr:`batteryMode`
    
                p = inputParser();
    
                % Set default values
                if ev3.init
                    defaultDebug = false;
                    defaultBatteryMode = 'Percentage';
                else
                    defaultDebug = ev3.debug;
                    defaultBatteryMode = ev3.batteryMode;
                end
    
                % Add parameter
                p.addOptional('debug', defaultDebug);
                p.addOptional('batteryMode', defaultBatteryMode);
    
                % Parse...
                p.parse(varargin{:});
    
                % Set properties
                ev3.batteryMode = p.Results.batteryMode;
                ev3.debug = p.Results.debug;
            end
    
            %% Getter
            function bat = get.batteryValue(ev3)
                if ~ev3.isConnected
                    warning(ID('noConnection'), 'EV3::getBattery: EV3-Object not connected to physical EV3.');
    
                    bat = 0;
                    return;
                end
    
                bat = ev3.getBattery();
            end
    
            function display(ev3)
                % Displays EV3-properties and its devices.
    
                displayProperties(ev3);
    
                fprintf('\n\tDevices\n');
                props = properties(ev3);
    
                warning('off', 'all');  % Turn off warnings while reading values
                for i = 1:length(props)
                    p = props{i};
    
                    if strcmp(class(ev3.(p)),'Sensor') || strcmp(class(ev3.(p)), 'Motor')
                        fprintf('\t%15s [Type: %s]\n', p, char(ev3.(p).type));
                    end
                end
                warning('on', 'all');
            end
        end
    
        methods (Access = private)  % Private brick functions that are wrapped by dependent params
            function varargout = handleCommand(ev3, 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 ev3.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(ev3.commInterface, varargin{:});
            end
    
            function bat = getBattery(ev3)
                % Retrieve batteryValue from brick in current mode. (Wrapped by EV3.batteryValue)
    
                if strcmpi(ev3.batteryMode, 'Percentage')
                    bat = ev3.handleCommand(@uiReadLbatt);
                else
                    bat = ev3.handleCommand(@uiReadVbatt);
                end
            end
    
            function resetPhysicalBrick(ev3)
                % Resets Motors and Sensors.
                %
                % Notes:
                %     * Gets called automatically by EV3.delete.
                %
                % See also MOTOR.RESETPHYSICALMOTOR, SENSOR.RESETPHYSICALSENSOR
    
                sensors = {'sensor1', 'sensor2', 'sensor3', 'sensor4'};
                motors = {'motorA', 'motorB', 'motorC', 'motorD'};
    
                for i = 1:4
                    motor = motors{i};
                    sensor = sensors{i};
                    ev3.(motor).resetPhysicalMotor();
                    ev3.(sensor).resetPhysicalSensor();
                end
            end
        end
    end