From 976cd5de36ea3fa52630b991e076a2208dff8c95 Mon Sep 17 00:00:00 2001 From: Tim Stadtmann <tim.stadtmann@rwth-aachen.de> Date: Mon, 15 Aug 2016 20:18:07 +0200 Subject: [PATCH] Implement sync-functionality in Motor-class Also, lots of code cleanup, comments, etc. --- source/CommunicationInterface.m | 1 + source/ConnectionType.m | 2 - source/EV3.m | 142 ++-- source/Motor.m | 1221 +++++++++++++++---------------- source/MotorPort.m | 3 +- source/Sensor.m | 80 +- 6 files changed, 723 insertions(+), 726 deletions(-) diff --git a/source/CommunicationInterface.m b/source/CommunicationInterface.m index 34fcecb..18bca6c 100644 --- a/source/CommunicationInterface.m +++ b/source/CommunicationInterface.m @@ -220,6 +220,7 @@ classdef CommunicationInterface < handle function setProperties(brick, varargin) p = inputParser(); + p.KeepUnmatched = true; % Set default values defaultIOType = 'usb'; diff --git a/source/ConnectionType.m b/source/ConnectionType.m index df8a552..c474df6 100644 --- a/source/ConnectionType.m +++ b/source/ConnectionType.m @@ -1,7 +1,5 @@ classdef ConnectionType < uint8 %ConnectionType Type resp. status of connection at a certain port. - - enumeration Unknown (111) DaisyChain (117) diff --git a/source/EV3.m b/source/EV3.m index 0cbb62d..0d970ec 100644 --- a/source/EV3.m +++ b/source/EV3.m @@ -7,82 +7,110 @@ classdef EV3 < handle % % Properties: % Standard - % motorA[,B,C,D] - - % sensor1[,2,3,4] - - % debug - + % motorA[,B,C,D] + % sensor1[,2,3,4] + % debug - Debug turned on or off % batteryMode - Mode for reading battery charge % Dependent % batteryValue - Current battery charge level % get-only % isConnected - Is virtual brick connected to physical one? - % commInterface - Brick object from sublayer class Brick -> used as interface to the physical brick. + % commInterface - Interface to communication layer % % Methods: - % Standard - % EV3 - - % connect - Connects EV3-object and its Motors and Sensors to physical brick via bt or usb. - % disconnect - Disconnects EV3-object and its Motors and Sensors from physical brick. - % update - Updates all Motors and Sensors - % coupleMotors - Creates and connects a SyncMotor-object using two chosen Motor-objects - % beep - Plays a 'beep' tone on brick. - % playTone - Plays tone on brick. - % stopTone - Stops tone currently played. - % tonePlayed - Tests if a tone is currently played. - % setProperties - Set multiple EV3 properties at once using MATLAB's inputParser. + % General + % EV3 + % connect - Connects EV3-object and its Motors and Sensors to physical brick via bt or usb + % disconnect - Disconnects EV3-object and its Motors and Sensors from physical brick + % setProperties - Set multiple EV3 properties at once using MATLAB's inputParser + % Brick functions + % beep - Plays a 'beep' tone on brick + % playTone - Plays tone on brick + % stopTone - Stops tone currently played + % tonePlayed - Tests if a tone is currently played % % % 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 referring to an instance of this class the term 'virtual brick' is used from now - % on. The LEGO brick itself is referred to as 'physical brick'. + % on. The LEGO EV3 brick itself is referred to as 'physical brick'. % % Example - % % This small example connects to the brick, starts motorA with power 50, reads a value - % % from sensor1, brakes the motor and disconnects again. - % % This should roughly demonstrate how this toolbox works. + % % This small example connects to the brick, starts motorA with power 50 and time-limit + % % of 5 seconds. As long as it's running, sensor 1 is read and its readings are plotted + % % the end. % - % brickObject = EV3(); - % brickObject = EV3('debug', 'on', 'batteryMode', 'Voltage'); - % brickObject.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); - % m = brickObject.motorA; - % m.power = 50; - % m.start(); - % s = brickObject.sensor1; - % s.value - % m.tachoCount - % m.brakeMode = 'Brake'; - % m.stop(); - % brickObject.disconnect(); - % delete brickObject; - % delete m; delete s; - % + %%%%%%%%%% + % brickObject = EV3('debug', 'on', 'batteryMode', 'Voltage'); + % brickObject.connect('bt', 'serPort', '/dev/rfcomm0'); + % + % m = brickObject.motorA; + % s = brickObject.sensor1; + % + % m.setProperties('Power', 50, 'BrakeMode', 'Brake', 'TachoLimitMode', 'Time', ... + % 'TachoLimit', 5000); + % + % if s.type ~= DeviceType.Color + % fprintf('Please connect EV3-color-sensor to port 1!\n'); + % else + % s.mode = DeviceMode.Color.Ambient; + % + % readings = []; + % time = []; + % + % tic; + % m.start(); + % pause(0.5); + % while m.isRunning + % readings = [readings, s.value]; + % time = [time toc]; + % end + % + % plot(time, readings); + % end + % + % brickObject.disconnect(); + % brickObject.delete(); + % clear all + %%%%%%%%%% % % Signature % Author: Tim Stadtmann % Date: 2016/05/19 + % Updated: 2016/08/15 properties % Standard properties + %debug - Debug turned on or off + % In debug mode 1 (debug=1/'on'/true), everytime a command is passed to the sublayer + % ('communication layer'), there is feedback in the console about what command has been + % called. Debug mode 2 (debug=2) enables debug mode in the lower layers. Each packet + % sent and received is printed to the console. + % -> Any valid boolean (0/1/'on'/'off'/true/false) or 2 (for enabling debugging in + % lower layers) + debug; + %% Modi %batteryMode - Mode for reading battery charge % -> 'Percentage' / 'Voltage' % See also EV3.BATTERYVALUE - batteryMode = 'Percentage'; - - %% Debug - - debug; + batteryMode; end properties (Dependent) % Parameters to be read directly from physical brick - batteryValue; % Current battery status (either in Percentage or Voltage) + %batteryValue - Current battery charge + % Depending on batteryMode, the reading is either in percentage or voltage. + % See also EV3.BATTERYMODE + batteryValue; end properties (SetAccess = 'private') % Read-only properties that are set internally - isConnected = false; % Is virtual brick currently connected to physical brick? - commInterface = 0; % Brick object from sub-layer class Brick -> used as interface to the - % physical brick. + %isConnected - Virtual brick connected to physical one? + isConnected = false; + + %commInterface - Interface to communication layer + commInterface = 0; %% Motors and Sensors @@ -98,13 +126,13 @@ classdef EV3 < handle end properties (Hidden, Access = 'private') % Hidden properties for internal use only - init = 1; % Indicates 'init-phase' (Set to 1 as long as constructor is running) + init = true; % Indicates 'init-phase' (Set to 1 as long as constructor is running) end methods % Standard methods %% Constructor function ev3 = EV3(varargin) - % Sets properties of EV3-object and creates Motor- and Sensor-objects with default + %EV3 Sets properties of EV3-object and creates Motor- and Sensor-objects with default % parameters. % % Arguments @@ -477,14 +505,8 @@ classdef EV3 < handle end function set.batteryMode(ev3, batteryMode) - % Check if batteryMode is valid and set ev3.batteryMode if it is. - % - % Arguments - % * batteryMode ('Voltage'/'Percentage'): Mode in which batteryValue will be read. - % - validModes = {'Voltage', 'Percentage'}; - if ~ischar(batteryMode) || ~any(validatestring(batteryMode, validModes)) + if ~ischar(batteryMode) || ~ismember(batteryMode, validModes) error('EV3::set.batteryMode: Given parameter is not a valid battery mode.'); else ev3.batteryMode = batteryMode; @@ -492,12 +514,6 @@ classdef EV3 < handle end function set.debug(ev3, debug) - % Check if debug is valid and set ev3.debug if it is. - % - % Arguments - % * debug (0/1/'on'/'off'/'true'/'false'/2) - % - if ~isBool(debug) && debug ~= 2 error('EV3::set.debug: Given parameter is not a bool.'); end @@ -518,16 +534,18 @@ classdef EV3 < handle % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % b.setProperties('debug', 'on', 'batteryMode', 'Voltage'); % % Instead of: b.debug = 'on'; b.batteryMode = 'Voltage'; % + % See also EV3.DEBUG, EV3.BATTERYMODE + % p = inputParser(); % Set default values if ev3.init - defaultDebug = 0; + defaultDebug = false; defaultBatteryMode = 'Percentage'; else defaultDebug = ev3.debug; @@ -570,8 +588,8 @@ classdef EV3 < handle methods (Access = 'private') % Private brick functions that are wrapped by dependent params function bat = getBattery(ev3) if ~ev3.isConnected - error(['EV3::getBattery: EV3-Object not connected to physical EV3. You have ',... - 'to call ev3.connect(properties) first!']); + error(['EV3::getBattery: EV3-Object not connected to physical EV3. You have ',... + 'to call ev3.connect(properties) first!']); end if strcmpi(ev3.batteryMode, 'Percentage') diff --git a/source/Motor.m b/source/Motor.m index 8625762..35172e1 100644 --- a/source/Motor.m +++ b/source/Motor.m @@ -1,10 +1,9 @@ -classdef Motor < handle +classdef Motor < handle & dynamicprops % Motor High-level class to work with motors. % % This class is supposed to ease the use of the brick's motors. It is possible to set all - % kinds of parameters (even without connecting to the brick), request the current status of - % the motor port and of course send commands to the brick to execute on the respective - % port. + % kinds of parameters, request the current status of the motor ports and of course send + % commands to the brick to be executed on the respective port. % % Properties: % Standard @@ -13,175 +12,131 @@ classdef Motor < handle % smoothStart - Degrees/Time for how far/long the motor should smoothly start (depends on limitMode) % smoothStop - Degrees/Time for how far/long the motor should smoothly stop (depends on limitMode) % limitValue - Degrees/Time for how far/long the motor should run (depends on limitMode) - % brakeMode - Mode for braking % limitMode - Mode for motor limit - % mode - Device mode at port, used as tacho mode (format in which tachoCount should be returned) + % brakeMode - Mode for braking % debug - Debug mode turned on or off % Dependent - % tachoCount - Current tacho count (either in degrees or rotations) - % speed - Current speed of motor (only valid with speedRegulation turned on) - % Get-only + % isRunning - True if motor is running (currentSpeed > 0) + % tachoCount - Current tacho count + % currentSpeed - Current speed of motor (should equal power if speedRegulation = 1) + % type - Current motor type (large or medium motor) + % Other % port - Motor port - % motorAtPort - Is physical motor actually connected to port? - % brick - Brick object from sublayer class Brick -> used as interface to the physical brick. - % status - Connection status at port - % type - Device type at port % % Methods: % Standard - % Motor - - % connect - Connects Motor-object to physical brick. - % disconnect - Disconnects Motor-object from physical brick. - % update - Updates Motor-object to current status at its port. - % start - Starts the motor taking all parameters set by user into account. - % stop - Stops the motor. :) - % waitFor - Stops execution of program as long as motor is running with tacholimit. - % isRunning - Returns whether motor is running (WITH TACHOLIMIT) or not. + % Motor + % connect - Connects Motor-object to physical brick + % disconnect - Disconnects Motor-object from physical brick + % setProperties - Sets multiple Motor properties at once using MATLAB's inputParser + % Brick functions + % start - Starts the motor taking all parameters set by user into account + % stop - Stops the motor :) + % syncedStart - Starts the motor synchronized with another + % syncedStop - Stops the motors previously started together with syncedStart + % waitFor - Stops execution of program as long as motor is running with tacholimit % reset - Resets internal tacho count - % resetTachoCount - Resets tacho count to 0 (if running without tacholimit). - % togglePolarity - Switches the direction in which the motor turns. - % setProperties - Sets multiple Motor properties at once using MATLAB's inputParser. + % resetTachoCount - Resets tacho count to 0 (if running without tacholimit) % % - % % Notes: % * You don't need to create instances of this class. The EV3-class automatically creates % instances for each motor port, and you can work with them via the EV3-object. - % In fact, the connect/disconnect-methods of Motor are even written to be used via - % EV3-class mainly, so you should only work without the EV3-class if you know what you - % are doing! - % - % Example - % % This small example should only roughly demonstrate how to work with motors. - % % Establish connection, set properties on motorA and wait until it's connected. After - % % starting, output current speed every 100ms until motor is turned off. Then output - % % current tachoCount and disconnect. - % - % b = EV3(); - % b = EV3('debug', 'on', 'batteryMode', 'Voltage'); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); - % b.motorA.setProperties('Power', 50, 'speedRegulation', 'on', 'limitMode', 'Time'); - % b.motorA.limitValue = 1000; - % b.motorA.brakeMode = 'Coast'; - % b.motorA.mode = DeviceMode.Motor.Rotations; - % while ~b.motorA.motorAtPort % Wait until physical motor connected to A - % b.motorA.update(); - % pause(0.5); - % end - % b.motorA.start(); % Motor on portA runs with power 50 for 1000ms and then coasts to a - % % stop - % while ~b.motorA.isRunning() - % b.motorA.speed - % pause(0.1); - % end - % b.motorA.tachoCount - % b.disconnect(); - % b.delete() % % % Signature % Author: Tim Stadtmann % Date: 2016/05/19 + % Updated: 2016/08/15 properties % Standard properties to be set by user %power - Power level of motor in percent - % -> Any integer or double in [-100, 100] + % -> Any numeric in [-100, 100] power; %speedRegulation - Speed regulation turned on or off - % Turning speed regulation on enables Motor.isRunning(), Motor.waitFor() and correctly - % reading current speed with Motor.speed. - % -> Any valid boolean (0/1/'on'/'off'/'true'/'false') + % -> Any valid boolean (0/1/'on'/'off'/true/false) speedRegulation; - %smoothStart - Degrees/Time for how far/long the motor should smoothly start (depends on limitMode) - % -> Any integer or double in [0, tachoLimit/2] + %smoothStart - Degrees/Time for how far/long the motor should smoothly start (depends on tachoLimitMode) + % -> Any numeric in [0, tachoLimit] smoothStart; - %smoothStop - Degrees/Time for how far/long the motor should smoothly stop (depends on limitMode) - % -> Any integer or double in [0, tachoLimit/2] + %smoothStop - Degrees/Time for how far/long the motor should smoothly stop (depends on tachoLimitMode) + % -> Any numeric in [0, tachoLimit] smoothStop; - %limitValue - Degrees/Time for how far/long the motor should run (depends on limitMode) - % -> Any integer or double >= 0 (in ms, if limitMode = 'Time') - limitValue; + %tachoLimit - Degrees/Time for how far/long the motor should run (depends on tachoLimitMode) + % -> Any numeric >= 0 (in ms, if tachoLimitMode = 'Time') + tachoLimit; - %brakeMode - Mode for braking - % -> 'Brake' / 'Coast' - brakeMode; % Mode for braking: 'Brake' or 'Coast' - - %limitMode - Mode for motor limit + %tachoLimitMode - Mode for motor limit % -> 'Tacho' / 'Time' - limitMode; + tachoLimitMode; - %mode - Device mode at port, used as tacho mode (i.e. what tachoCount should return) - % -> DeviceMode.Motor.Degrees / DeviceMode.Motor.Rotations - % (DeviceMode.Motor.Speed is also defined, but this is only used internally.) - mode; + %brakeMode - Mode for braking + % -> 'Brake' / 'Coast' + brakeMode; %debug - 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, etc. - % -> Any valid boolean (0/1/'on'/'off'/'true'/'false') + % -> Any valid boolean (0/1/'on'/'off'/true/false) debug; end - properties (Dependent) % Parameters to be read directly from physical brick - tachoCount; % Current tacho count (either in Degrees or Rotations) - speed; % Current speed of motor (only valid with speedRegulation turned on) - end - - properties (SetAccess = 'protected') % Read-only properties that are set internally or in init-phase - %motorAtPort - Is physical motor actually connected to port? - % This property will be changed by Motor.update() if changes have happened on the - % physical brick, depending on status, type and mode values. - % See also STATUS, TYPE, MODE, UPDATE - motorAtPort = 0; - - %brick - Brick object from sublayer class Brick -> used as interface to the physical brick. - % See also BRICK - brick = 0; - + properties (SetAccess = 'private') %port - Motor port % This is only the string representation of the motor port to work with. % Internally, either MotorPort-, MotorBitfield- or MotorInput-member will be used. % -> 'A' / 'B' / 'C' / 'D' - port; - - %status - Connection status at port - % This property will be changed by Motor.update() if changes have happened on the - % physical brick. - % See also CONNECTIONTYPE, UPDATE - status = DeviceMode.Error.Undefined; - - %type - Device type at port - % This property will be changed by Motor.update() if changes have happened on the - % physical brick. - % See also DEVICEMODE.ERROR, UPDATE - type = DeviceMode.Error.Undefined; + port; end - properties (Hidden, Access = 'protected') % Hidden properties for internal use only - init = 1; % Indicates 'init-phase' (Set to 1 as long as constructor is running) - isConnected = 0; % Does virtual motor-object have a valid brick-handle? + properties (Dependent) % Read-only parameters to be read directly from physical brick + %isRunning - True if motor is running (speed > 0) + isRunning; - %% Equivalents for Motor.port and Motor.brakeMode in enumerations - % Port (obviously) & brakeMode are actual parameters on the brick. To avoid using string - % comparisons each time they are used, the corresponding values to the given strings - % are saved (hidden from the user). - port_; - brakeMode_; + %tachoCount - Current tacho count + tachoCount; - %% Indicator for changes which will be needed later on - changed = 0; % Saves whether power and/or speed reg have been changed virtually without - % updating the physical brick. - limitSetToZero = 0; % See motor.start (2nd note) for more info. + %currentSpeed - Current speed of motor (equals power if speedRegulation = true) + currentSpeed; + %type - Type of connected device if any + % See also DEVICETYPE + type; end - properties (Hidden, Dependent, Access = 'protected') % Hidden, dependent properties for internal use only + properties (Hidden, Access = 'private') % Hidden properties for internal use only + commInterface; % Communication interface + + %% Hidden brick parameters + + % brakeMode is an actual parameter on the brick. To avoid inconsistencies with other + % modi and to prettify the output, a string representing it is saved. In order to avoid + % using string comparisons each time it is used, the corresponding value, that is going + % to be sent, is saved (hidden from the user). + brakeMode_; + + %% Miscallenous flags + + connectedToBrick = false; % Connection to physical Brick? + sendPowerOnNextStart = false; % Indicates whether current power parameter should be sent + % to the Brick right before starting it next time + sendPowerOnSet = true; % Indicates whether power parameter should be sent to the Brick + % immediately after setting it + limitSetToZero = false; % Indicates whether tachoLimit has been set to zero + % (workaround for a bug, see motor.start, Note 2) + init = true; % Indicates 'init-phase' (True as long as constructor is running) + end + + properties (Hidden, Dependent, Access = 'private') % Hidden, dependent properties for internal use only portNo; % Port number portInput; % Port number for input functions + + isSynced; % Is motor running in synced mode? + physicalMotorConnected; % Physical motor connected to this port? end methods % Standard methods @@ -194,133 +149,7 @@ classdef Motor < handle % motor.setProperties(varargin{:}); - motor.init = 0; - end - - %% Connection - function connect(motor,brick) - %connect Connects Motor-object to physical brick. - % - % Notes: - % * This method actually only saves a handle to a Brick-object which has been - % created beforehand (probably by an EV3-object). - % - % Arguments - % * brick: instance of Brick-class - % - % Examples (for use without EV3-class) - % m = Motor(); - % brickInterface = Brick('ioType', 'usb'); - % m.connect(brickInterface); - % % do stuff - % - - if motor.isConnected - if isBrickValid(motor.brick) - error('Motor::connect: Motor-Object already has a brick handle.'); - else - warning(['Motor::connect: Motor.isConnected is set to ''True'', but ',... - 'brick handle is invalid. Deleting invalid handle and ' ,... - 'resetting Motor.isConnected now...']); - - motor.brick = 0; - motor.isConnected = 0; - - error('Motor::connect: Disconnected due to internal error.'); - end - end - - motor.brick = brick; - motor.isConnected = 1; - - if motor.debug - fprintf('(DEBUG) Motor-Object connected to brick handle.\n'); - end - - motor.update(); - end - - function disconnect(motor) - %disconnect Disconnects Motor-object from physical brick. - % - % Notes: - % * As with Motor::connect, this method actually only sets the property, in which - % the handle to the Brick-class was stored, to 0. - % - % Examples (for use without EV3-class) - % m = Motor(); - % brickInterface = Brick('ioType', 'usb'); - % m.connect(brickInterface); - % % do stuff - % m.disconnect(); - % brickInterface.delete(); % Actual disconnecting!!! - % - -% if ~motor.isConnected -% error('Motor::disconnect: No brick connected.'); -% end - - motor.brick = 0; % Note: actual deleting is done in EV3::disconnect. - motor.isConnected = 0; - motor.motorAtPort = 0; - - motor.status = DeviceMode.Error.Undefined; - motor.type = DeviceMode.Error.Undefined; - end - - function update(motor) - %update Updates motor-object to current status at its port. - % The parameters type, status and mode, which fully represent any device that can - % be connected to the brick, will be updated by this method. Furthermore, update() - % concludes from these parameters if there is a physical motor currently connected - % to the port (Motor.motorAtPort). - % - % Examples - % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); - % % Connect motor to port A (in 'real life') - % b.motorA.power = 50; - % b.motorA.start(); -> error, because virtual brick does not know about new motor - % b.motorA.type -> outputs DeviceType.Error - % b.motorA.update(); - % b.motorA.start(); -> no error - % b.motorA.type -> output DeviceType.LargeMotor/DeviceType.MediumMotor - % - if ~motor.isConnected - error(['Motor::update: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - end - - % Get current status at both ports - status = motor.getStatus(); - [type, mode] = motor.getTypeMode(); - - % Conclude if both physical motors are connected - motor.motorAtPort = 0; - if uint8(status) >= uint8(ConnectionType.OutputDumb) && ... - uint8(status) <= uint8(ConnectionType.OutputTacho) % Is status of motor valid? - if (type == DeviceType.MediumMotor || type == DeviceType.LargeMotor) % Is type of motor valid? - if strcmp(class(mode), 'DeviceMode.Motor') % Is mode of motor valid? - motor.motorAtPort = 1; - end - end - end - - % Set parameter - motor.status = status; - motor.type = type; - - if motor.mode ~= mode && strcmp(class(mode), 'DeviceMode.Motor') - if mode == DeviceMode.Motor.Speed - mode = motor.mode; % This trick prevents set.mode from throwing an error if - % the physical motor's mode has internally been set to - % DeviceMode.Motor.Speed - else - warning(['Motor::update: Physical motor''s mode was not ',... - 'the specified one. Changing...']); - end - end - motor.mode = mode; + motor.init = false; end %% Brick functions @@ -343,101 +172,260 @@ classdef Motor < handle % However, this does not even work all the time. If motor does not % start, call stop() and setPower() manually. :/ % - - if ~motor.isConnected - error(['Motor::start: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort + if ~motor.connectedToBrick + error(['Motor::start: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); + elseif ~motor.physicalMotorConnected error('Motor::start: No physical motor connected to Port %s',... - motor.port); + port2str('Motor', motor.port)); + elseif motor.isRunning + error('Motor::start: Motor is already runnning!'); + end + + if motor.isSynced + delete(motor.findprop('syncCache')); end if motor.power == 0 warning('Motor::start: Motor starting with power=0.'); end - if motor.limitValue==0 - if motor.changed || motor.limitSetToZero - % See 2nd note - motor.stop(); + if motor.tachoLimit==0 + if motor.sendPowerOnNextStart + if motor.limitSetToZero + motor.stop(); + motor.limitSetToZero = false; + end motor.setPower(motor.power); - motor.limitSetToZero = 0; end - motor.brick.outputStart(0, motor.port_); + motor.commInterface.outputStart(0, motor.port); if motor.debug - fprintf('(DEBUG) Motor::start: Called outputStart on Port %s\n', motor.port); + fprintf('(DEBUG) Motor::start: Called outputStart on Port %s\n', port2str('Motor', motor.port)); end else - if motor.isRunning() - error('Motor::start: Motor is already running.'); - end - - if strcmpi(motor.limitMode, 'Tacho') + if strcmpi(motor.tachoLimitMode, 'Tacho') if motor.speedRegulation - motor.brick.outputStepSpeed(0, motor.port_, motor.power,... - motor.smoothStart, motor.limitValue, motor.smoothStop,... + motor.commInterface.outputStepSpeed(0, motor.port, motor.power,... + motor.smoothStart, motor.tachoLimit, motor.smoothStop,... motor.brakeMode_); if motor.debug fprintf('(DEBUG) Motor::start: Called outputStepSpeed on Port %s\n',... - motor.port); + port2str('Motor', motor.port)); end else - motor.brick.outputStepPower(0, motor.port_, motor.power,... - motor.smoothStart, motor.limitValue, motor.smoothStop,... + motor.commInterface.outputStepPower(0, motor.port, motor.power,... + motor.smoothStart, motor.tachoLimit, motor.smoothStop,... motor.brakeMode_); if motor.debug fprintf('(DEBUG) Motor::start: Called outputStepPower on Port %s\n',... - motor.port); + port2str('Motor', motor.port)); end end - elseif strcmpi(motor.limitMode, 'Time') + elseif strcmpi(motor.tachoLimitMode, 'Time') if motor.speedRegulation - motor.brick.outputTimeSpeed(0, motor.port_, motor.power,... - motor.smoothStart, motor.limitValue, motor.smoothStop,... + motor.commInterface.outputTimeSpeed(0, motor.port, motor.power,... + motor.smoothStart, motor.tachoLimit, motor.smoothStop,... motor.brakeMode_); if motor.debug fprintf('(DEBUG) Motor::start: Called outputTimeSpeed on Port %s\n',... - motor.port); + port2str('Motor', motor.port)); end else - motor.brick.outputTimePower(0, motor.port_, motor.power,... - motor.smoothStart, motor.limitValue, motor.smoothStop,... + motor.commInterface.outputTimePower(0, motor.port, motor.power,... + motor.smoothStart, motor.tachoLimit, motor.smoothStop,... motor.brakeMode_); if motor.debug fprintf('(DEBUG) Motor::start: Called outputTimePower on Port %s\n',... - motor.port); + port2str('Motor', motor.port)); end end - else - % Note: After all the input parsing done in setProperties and set.limitMode - % this REALLY should not happen.. - error('Motor::start: Limit Mode not valid.'); end - end end function stop(motor) %stop Stops the motor. :) - if ~motor.isConnected - error(['Motor::stop: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort + if ~motor.connectedToBrick + error(['Motor::stop: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); + elseif ~motor.physicalMotorConnected error('Motor::stop: No physical motor connected to Port %s',... - motor.port); + port2str('Motor', motor.port)); + elseif motor.isSynced && motor.isRunning + error(['Motor::stop: Motor is running synchronized with another motor. ' ,... + 'Use ''syncedStop'' on the ''master''-motor.']); end - motor.brick.outputStop(0, motor.port_, motor.brakeMode_); + motor.commInterface.outputStop(0, motor.port, motor.brakeMode_); + + if motor.debug + fprintf('(DEBUG) Motor::stop: Called outputStop on Port %s\n', port2str('Motor', motor.port)); + end + end + + function syncedStart(motor, syncMotor, varargin) + %syncedStart Starts this motor synchronized with another + % This motor acts as a 'master', meaning that the synchronized control is done via + % it. When syncedStart is called, the master sets the slave's (syncMotor) + % properties to keep it consistent with the last parameters sent to the physical + % brick. So, for example, changing the power on the master motor will take effect + % on the slave as soon as this method is called. + % The following parameters will be affected on the slave: power, brakeMode, + % tachoLimit, speedRegulation + % + % Arguments + % * syncMotor (the other motor object to sync with) + % * 'turnRatio', numeric in [-200,200] + % + % Notes + % * Description of turn_ratio (Excerpt of Firmware-comments, in c_output.c): + % "Turn ratio is how tight you turn and to what direction you turn. + % -> 0 value is moving straight forward + % -> Negative values turn to the left + % -> Positive values turn to the right + % -> Value -100 stops the left motor + % -> Value +100 stops the right motor + % -> Values less than -100 makes the left motor run the opposite + % direction of the right motor (Spin) + % -> Values greater than +100 makes the right motor run the opposite + % direction of the left motor (Spin)" + % * This is right now a pretty 'heavy' function, as it tests if both motors are + % connected AND aren't running, wasting four commands, so keep that in mind + % + % Example + % b = EV3(); b.connect('usb'); + % m = b.motorA; + % m.power = 50; + % m.syncedStart(b.motorB); + % % Do stuff + % m.syncedStop(); + % + + turnRatio = 0; + + % Check parameters + if ~isDeviceValid('Motor', syncMotor) + error('Motor::syncedStart: Given motor to sync with is not a valid motor object.'); + elseif ~isempty(varargin) + if length(varargin)~=2 + error(['Motor::syncedStart: Wrong number of input arguments. ' ,... + 'Possible input: ''turnRatio'', value (with value in [-200,200])']); + end + parameter = varargin{1}; + turnRatio = varargin{2}; + if ~strcmpi(parameter, 'turnRatio') || ~isnumeric(turnRatio) || ... + turnRatio<-200 || turnRatio > 200 + error(['Motor::syncedStart: Wrong format of input arguments. Possible ',... + 'input: ''turnRatio'', value (with value in [-200,200])']); + end + end + + % Check connection and motor parameter + if ~motor.connectedToBrick || ~syncMotor.connectedToBrick + error(['Motor::syncedStart: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); + elseif ~motor.physicalMotorConnected || ~syncMotor.physicalMotorConnected + error('Motor::syncedStart: No physical motor connected to Port %s or %s.',... + port2str('Motor', motor.port), port2str('Motor', syncMotor.port)); + elseif motor.speedRegulation + error(['Motor::syncedStart: Cannot run motors synchronized if ',... + 'speedRegulation is turned on.']); + elseif motor.isRunning || syncMotor.isRunning + error('Motor::syncedStart: One of the motors is already running!'); + end + + if motor.power == 0 + warning('Motor::syncedStart: Synchronized motors starting with power=0.'); + end + + % Cache old values to make it possible to reset them on stopSynced + % Note: the existence of 'syncCache' is also used to determine whether motor is + % running synchronized or not, see get.isSynced() + if motor.isSynced + meta = motor.findprop('syncCache'); + else + meta = motor.addprop('syncCache'); + meta.Hidden = true; + meta.Access = 'private'; + end + + motor.syncCache.master_oldSendPowerOnSet = motor.sendPowerOnSet; + motor.syncCache.slave_oldSendPowerOnSet = syncMotor.sendPowerOnSet; + motor.syncCache.slave = syncMotor; + + % Disable immediate sending of new power values + motor.sendPowerOnSet = false; + syncMotor.sendPowerOnSet = false; + + % Keep 'slave'-motor synchronized + syncMotor.speedRegulation = false; + syncMotor.tachoLimit = motor.tachoLimit; + syncMotor.brakeMode = motor.brakeMode; + syncMotor.power = motor.power; + + if strcmpi(motor.tachoLimitMode, 'Tacho') + motor.commInterface.outputStepSync(0, motor.port+syncMotor.port, ... + motor.power, turnRatio, ... + motor.tachoLimit, motor.brakeMode_); + if motor.debug + fprintf(['(DEBUG) SyncMotor::syncedStart: Called outputStepSync on ' ,... + 'Ports %s and %s.\n'], port2str('Motor', motor.port), port2str('Motor', syncMotor.port)); + end + elseif strcmpi(motor.tachoLimitMode, 'Time') + motor.commInterface.outputTimeSync(0, motor.port+syncMotor.port, ... + motor.power, turnRatio, ... + motor.tachoLimit, motor.brakeMode_); + if motor.debug + fprintf('(DEBUG) SyncMotor::start: Called outputStepSync on Ports %s and %s.\n',... + port2str('Motor', motor.port), port2str('Motor', syncMotor.port)); + end + end + end + + function syncedStop(motor) + %syncedStop Stops both motors previously started with syncedStart + % Obviously, if motors have not been started synchronized, this throws an error. + % + % See also MOTOR.SYNCEDSTART + % + + if ~motor.isSynced + error('Motor::syncedStop: Motor has not been started synchronized with another.'); + end + + % Retrieve synced motor from cache + syncMotor = motor.syncCache.slave; + + if ~motor.connectedToBrick || ~syncMotor.connectedToBrick + error(['Motor::syncedStop: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); + elseif ~motor.physicalMotorConnected || ~syncMotor.physicalMotorConnected + error('Motor::syncedStop: No physical motor connected to either Port %s or %s.',... + port2str('Motor', motor.port), port2str('Motor', syncMotor.port)); + end + + % Retrieve other values from cache and delete it + motor.sendPowerOnSet = motor.syncCache.master_oldSendPowerOnSet; + syncMotor.sendPowerOnSet = motor.syncCache.slave_oldSendPowerOnSet; + delete(motor.findprop('syncCache')); + + % Synced stopping + motor.commInterface.outputStop(0, motor.port+syncMotor.port, motor.brakeMode_); + + % On next start, both motors have to send power-opcode again + motor.sendPowerOnNextStart = true; + syncMotor.sendPowerOnNextStart = true; if motor.debug - fprintf('(DEBUG) Motor::stop: Called outputStop on Port %s\n', motor.port); + fprintf('(DEBUG) Motor::stop: Called outputStop on Ports %s and %s\n.', ... + port2str('Motor', motor.port), port2str('Motor', syncMotor.port)); end end @@ -457,67 +445,46 @@ classdef Motor < handle % long as not both OutputTest and OutputReady are buggy). % - if ~motor.isConnected - error(['Motor::waitFor: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort + if ~motor.connectedToBrick + error(['Motor::waitFor: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); + elseif ~motor.physicalMotorConnected error('Motor::waitFor: No physical motor connected to Port %s',... - motor.port); - elseif ~motor.limitValue - error(['Motor::waitFor: Motor has no tacho limit. ' ,... - 'Can''t reliably determine whether it is running or not.']); + port2str('Motor', motor.port)); end - tic; - while 1 - try - warning('off','all'); - - motor.brick.outputReady(0, motor.port_); - t = toc; - - if t < 1 - while motor.isRunning() % If outputReady correctly returned in less - % than a second, isRunning should instantly send 0. - end - end - - warning('on','all'); - break; - catch % TO DO: Catch only timeout exception, otherwise death and destruction possible (aka infinite loop) - continue; - end - end - - if motor.debug - fprintf('(DEBUG) Motor::waitFor: Called outputReady on Port %s\n', motor.port); - end - end - - function running = isRunning(motor) - %isRunning Returns whether motor is running (WITH TACHOLIMIT) or not. - % - % Notes: - % * This *mostly* works. Sometimes this falsely returns 0 if isRunning() gets - % called immediately after starting the motor. - % - - if ~motor.isConnected - error(['Motor::isRunning: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort - error('Motor::isRunning: No physical motor connected to Port %s',... - motor.port); - elseif ~motor.limitValue - warning(['Motor::isRunning: Motor has no tacho limit. ' ,... - 'Can''t reliably determine whether it is running or not.']); - end - - running = motor.brick.outputTest(0, motor.port_); - - if motor.debug - fprintf('(DEBUG) Motor::isRunning: Called outputReady on Port %s\n', motor.port); + while motor.isRunning + pause(0.5); end +% elseif ~motor.tachoLimit +% error(['Motor::waitFor: Motor has no tacho limit. ' ,... +% 'Can''t reliably determine whether it is running or not.']); +% end +% +% tic; +% while 1 +% try +% warning('off','all'); +% +% motor.commInterface.outputReady(0, motor.port); +% t = toc; +% +% if t < 1 +% while motor.isRunning() % If outputReady correctly returned in less +% % than a second, isRunning should instantly send 0. +% end +% end +% +% warning('on','all'); +% break; +% catch % TO DO: Catch only timeout exception, otherwise death and destruction possible (aka infinite loop) +% continue; +% end +% end +% +% if motor.debug +% fprintf('(DEBUG) Motor::waitFor: Called outputReady on Port %s\n', motor.port); +% end end function reset(motor) @@ -528,100 +495,61 @@ classdef Motor < handle % physically change the motor's position. % See also MOTOR.RESETTACHOCOUNT - if ~motor.isConnected + if ~motor.connectedToBrick error(['Motor::reset: Motor-Object not connected to brick handle.',... 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort + elseif ~motor.physicalMotorConnected error('Motor::reset: No physical motor connected to Port %s',... - motor.port); + port2str('Motor', motor.port)); end - motor.brick.outputReset(0, motor.port_); + motor.commInterface.outputReset(0, motor.port); if motor.debug fprintf('(DEBUG) Motor::reset: Called outputReset on Port %s\n',... - motor.port); + port2str('Motor', motor.port)); end end - function togglePolarity(motor) - %togglePolarity Switches the direction in which the motor turns. - if ~motor.isConnected - error(['Motor::togglePolarity: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort - error('Motor::togglePolarity: No physical motor connected to Port %s',... - motor.port); + function resetTachoCount(motor) + %resetTachoCount Resets tacho count to 0 if running without tacholimit + % Compared to motor.reset, this resets the 'sensor mode' tacho count, a second + % tacho counter. This counter is used for reading the tacho count with inputRead + % and outputGetCount (via motor.tachoCount). + % See also MOTOR.RESET + + if ~motor.connectedToBrick + error(['Motor::resetTachoCount: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); + elseif ~motor.physicalMotorConnected + error('Motor::resetTachoCount: No physical motor connected to Port %s',... + port2str('Motor', motor.port)); end - motor.brick.outputPolarity(0, motor.port_, 0); + motor.commInterface.outputClrCount(0, motor.port); + if motor.debug - fprintf('(DEBUG) Motor::togglePolarity: Called outputPolarity on Port %s\n', motor.port); + fprintf('(DEBUG) Motor::resetTachoCount: Called outputClrCount on Port %s\n',... + port2str('Motor', motor.port)); end end %% Setter - function set.port(motor, port) - if ~isPortValid(class(motor),port) - error('Motor::set.port: Given port is not a valid port.'); - end - - if ischar(port) - motor.port = port; - [motor.port_, ~, ~] = str2PortParam(class(motor), port); - else - error('Motor::set.port: Port has to be a string.'); - end - end - function set.power(motor, power) if ~isnumeric(power) error('Motor::set.power: Given parameter is not a numeric.'); elseif power<-100 || power>100 warning('Motor::set.power: Motor power has to be an element of [-100,100]!'); error('Motor::set.power: Given motor power is out of bounds.'); - elseif power == 0 - if ~motor.init - warning('Motor::set.power: Setting power to zero...'); - end - elseif power == motor.power - warning('Motor::set.power: Motor power already is %d',power); end motor.power = power; % Set power parameter. - motor.changed = 1; % Indicate that virtual and physical brick have different power + motor.sendPowerOnNextStart = true; % Indicate that virtual and physical brick have different power % parameters right now. See 'setPower' for more info. - motor.setPower(power); % Update physical brick's power parameter. - end - - function set.mode(motor, mode) - % Convert given mode to DeviceMode if necessary - if strcmp(class(mode), 'uint8') || strcmp(class(mode), 'double') - mode = DeviceMode(motor.type, uint8(mode)); - end - if isModeValid(DeviceType.LargeMotor, mode) - if mode == DeviceMode.Motor.Speed - error(['Motor::update: Physical motor''s mode has to be either ' ,... - 'DeviceMode.Motor.Degrees or DeviceMode.Motor.Rotations. ' ,... - '(DeviceMode.Motor.Speed is for internal use only.']); - end - - % At this point, mode is a valid DeviceMode for Motor-objects. - - % If motor is currently not connected, allow changes - if strcmp(class(mode),'DeviceMode.Error') && ~motor.motorAtPort - motor.mode = mode; - return; - end - motor.mode = mode; - - if motor.isConnected && motor.motorAtPort - motor.setMode(mode); - end - else - error(['Motor::update: Physical motor''s mode has to be either ' ,... - 'DeviceMode.Motor.Degrees or DeviceMode.Motor.Rotations.']); + if motor.sendPowerOnSet && motor.connectedToBrick && ... + motor.physicalMotorConnected + motor.setPower(power); % Update physical brick's power parameter. end end @@ -630,12 +558,8 @@ classdef Motor < handle error('Motor::set.speedRegulation: Given parameter is not a bool.'); end - if ischar(speedRegulation) - motor.speedRegulation = str2bool(speedRegulation); - else - motor.speedRegulation = speedRegulation; - end - motor.changed = 1; + motor.speedRegulation = str2bool(speedRegulation); + motor.sendPowerOnNextStart = true; end function set.smoothStart(motor, steps) @@ -644,13 +568,11 @@ classdef Motor < handle elseif steps<0 warning('Motor::set.smoothStart: Smooth start steps have to be positive.'); error('Motor::set.smoothStart: Smooth start steps are out of bounds.'); - elseif isempty(motor.limitValue) - warning(['Motor::set.smoothStart: Smooth start steps should be set after ',... - 'setting motor.limitValue. Setting smooth start steps to zero...']); - steps = 0; - elseif steps>motor.limitValue - error(['Motor::set.smoothStart: Smooth start steps are greater than ',... - 'actual limitValue.']); + end + + if ~isempty(motor.tachoLimit) && steps>motor.tachoLimit + warning(['Motor::set.smoothStart: Smooth start steps are greater than ',... + 'tachoLimit.']); end motor.smoothStart = steps; @@ -662,33 +584,19 @@ classdef Motor < handle elseif steps<0 warning('Motor::set.smoothStop: Smooth stop steps have to be positive.'); error('Motor::set.smoothStop: Smooth stop steps are out of bounds.'); - elseif isempty(motor.limitValue) - warning(['Motor::set.smoothStop: Smooth stop steps should be set after ',... - 'setting motor.limitValue. Setting smooth stop steps to zero...']); - steps = 0; - elseif steps>motor.limitValue - error(['Motor::set.smoothStop: Smooth stop steps (%d) are greater than ',... - 'actual limitValue (%d).'], steps, motor.limitValue); end - motor.smoothStop = steps; - end - - function set.debug(motor, debug) - if ~isBool(debug) - error('Motor::set.debug: Given parameter is not a bool.'); + if ~isempty(motor.tachoLimit) && steps>motor.tachoLimit + error(['Motor::set.smoothStop: Smooth stop steps are greater than ',... + 'tachoLimit.']); end - if ischar(debug) - motor.debug = str2bool(debug); - else - motor.debug = debug; - end + motor.smoothStop = steps; end function set.brakeMode(motor, brakeMode) validModes = {'Coast', 'Brake'}; - if ~ischar(brakeMode) || ~any(validatestring(brakeMode, validModes)) + if ~ischar(brakeMode) || ~ismember(brakeMode, validModes) error('Motor::set.brakeMode: Given parameter is not a valid brake mode.'); else motor.brakeMode = brakeMode; @@ -696,127 +604,117 @@ classdef Motor < handle end end - function set.limitMode(motor, limitMode) + function set.tachoLimitMode(motor, tachoLimitMode) validModes = {'Time', 'Tacho'}; - if ~ischar(limitMode) || ~any(validatestring(limitMode, validModes)) - error('Motor::set.limitMode: Given parameter is not a valid limit mode.'); + if ~ischar(tachoLimitMode) || ~ismember(tachoLimitMode, validModes) + error('Motor::set.tachoLimitMode: Given parameter is not a valid limit mode.'); else - motor.limitMode = limitMode; + motor.tachoLimitMode = tachoLimitMode; + end + end + + function set.tachoLimit(motor, tachoLimit) + if ~isnumeric(tachoLimit) + error('Motor::set.tachoLimit: Given parameter is not a numeric.'); + elseif tachoLimit<0 + warning('Motor::set.tachoLimit: tachoLimit has to be positive!'); + error('Motor::set.tachoLimit: Given tachoLimit is out of bounds.'); + elseif any(motor.tachoLimit) + if tachoLimit==0 && motor.tachoLimit~=0 + motor.sendPowerOnNextStart = true; + motor.limitSetToZero = true; + motor.sendPowerOnSet = true; + elseif tachoLimit~=0 && motor.tachoLimit==0 + motor.sendPowerOnSet = false; + end end + + motor.tachoLimit = tachoLimit; end - function set.limitValue(motor, limitValue) - if ~isnumeric(limitValue) - error('Motor::set.limitValue: Given parameter is not a numeric.'); - elseif limitValue<0 - warning('Motor::set.limitValue: limitValue has to be positive!'); - error('Motor::set.limitValue: Given limitValue is out of bounds.'); - elseif any(motor.limitValue) - if motor.limitValue~=0 && limitValue==0 - motor.limitSetToZero = 1; - end + function set.port(motor, port) + if ~isPortStrValid(class(motor), port) + error('Motor::set.port: Given parameter is not a valid port string.'); + else + motor.port = str2PortParam(class(motor), port); end - motor.limitValue = limitValue; end - function set.brick(motor, brick) - if ~isBrickValid(brick) - error('Motor::set.brick: Handle to brick not valid.'); + function set.commInterface(motor, comm) + if ~isCommInterfaceValid(comm) + error('Motor::set.commInterface: Handle to commInterface not valid.'); else - motor.brick = brick; + motor.commInterface = comm; end end - function set.tachoCount(motor, tachoCount) - if tachoCount~=0 - error('Motor::set.tachoCount: Cannot set the tachoCount to a value ~= 0.'); - end - - motor.resetTachoCount(); + function set.debug(motor, debug) + if ~isBool(debug) + error('Motor::set.debug: Given parameter is not a bool.'); + end + + motor.debug = str2bool(debug); end function setProperties(motor, varargin) %setProperties Sets multiple Motor properties at once using MATLAB's inputParser. % % Arguments - % * 'debug', 0/1/'on'/'off'/'true'/'false' - % * 'smoothStart', integer or double in [0, tachoLimit/2] - % * 'smoothStop', integer or double in [0, tachoLimit/2] - % * 'speedRegulation', 0/1/'on'/'off'/'true'/'false' + % * 'debug', bool + % * 'smoothStart', numeric in [0, tachoLimit] + % * 'smoothStop', numeric in [0, tachoLimit] + % * 'speedRegulation', bool % * 'brakeMode', 'Coast'/'Brake' - % * 'limitMode', 'Time'/'Tacho' - % * 'limit', integer or double > 0 - % * 'power', integer or double in [-100,100] - % * 'mode', DeviceMode.Motor.Degrees/DeviceMode.Motor.Rotations + % * 'tachoLimitMode', 'Time'/'Tacho' + % * 'limit', numeric > 0 + % * 'power', numeric in [-100,100] % * 'batteryMode', 'Voltage'/'Percentage' % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % b.motorA.setProperties('debug', 'on', 'power', 50, 'limit', 720, 'speedRegulation', 'on'); % % Instead of: b.motorA.debug = 'on'; % % b.motorA.power = 50; % % b.motorA.limit = 720; % % b.motorA.speedRegulation = 'on'; % - p = inputParser(); - p.KeepUnmatched = 1; % For possible use in SyncMotor::setProperties + p.KeepUnmatched = 1; % Set default values if motor.init - defaultSmoothStart = 0; - defaultSmoothStop = 0; defaultDebug = 0; defaultSpeedReg = 0; defaultBrakeMode = 'Coast'; defaultLimitMode = 'Tacho'; defaultLimit = 0; defaultPower = 0; - defaultMode = DeviceMode.Motor.Degrees; + defaultSmoothStart = 0; + defaultSmoothStop = 0; else - defaultSmoothStart = motor.smoothStart; - defaultSmoothStop = motor.smoothStop; defaultDebug = motor.debug; defaultSpeedReg = motor.speedRegulation; defaultBrakeMode = motor.brakeMode; - defaultLimitMode = motor.limitMode; - defaultLimit = motor.limitValue; + defaultLimitMode = motor.tachoLimitMode; + defaultLimit = motor.tachoLimit; defaultPower = motor.power; - defaultMode = motor.mode; + defaultSmoothStart = motor.smoothStart; + defaultSmoothStop = motor.smoothStop; end - % set valid values and save them in a cell array -% validBools = {num2str(0), num2str(1), 'off', 'on'}; -% validBrakeModes = {num2str(BrakeMode.Coast), num2str(BrakeMode.Brake), 'Coast', 'Brake'}; -% validPorts = {num2str(MotorBitfield.MotorA), num2str(MotorBitfield.MotorB),... -% num2str(MotorBitfield.MotorC), num2str(MotorBitfield.MotorD),... -% num2str(Device.MotorSync),... -% 'A', 'B', 'C', 'D'}; -% validLimitModes = {'Time', 'Tacho'}; - - % define anonymous functions that will return whether given value in varargin is - % valid -% checkBools = @(x) any(validatestring(num2str(x),validBools)); -% checkBrakeMode = @(x) any(validatestring(num2str(x),validBrakeModes)); -% checkLimitMode = @(x) any(validatestring(x, validLimitModes)); -% checkPorts = @(x) any(validatestring(num2str(x),validPorts)); -% checkLimit = @(x) x>=0; -% checkPower = @(x) isnumeric(x) && x>=-100 && x<=100; - % Add parameter if motor.init - p.addRequired('port');%, checkPorts); + p.addRequired('port'); end - p.addOptional('smoothStart', defaultSmoothStart);%, checkBools); - p.addOptional('smoothStop', defaultSmoothStop);%, checkBools); - p.addOptional('debug', defaultDebug);%, checkBools); - p.addOptional('speedRegulation', defaultSpeedReg);%, checkBools); - p.addOptional('brakeMode', defaultBrakeMode);%, checkBrakeMode); - p.addOptional('limitMode', defaultLimitMode);%, checkLimitMode); - p.addOptional('limitValue', defaultLimit);%, checkLimit); - p.addOptional('power', defaultPower);%, checkPower); - p.addOptional('mode', defaultMode); + p.addOptional('debug', defaultDebug); + p.addOptional('speedRegulation', defaultSpeedReg); + p.addOptional('brakeMode', defaultBrakeMode) + p.addOptional('tachoLimitMode', defaultLimitMode); + p.addOptional('tachoLimit', defaultLimit); + p.addOptional('power', defaultPower); + p.addOptional('smoothStart', defaultSmoothStart); + p.addOptional('smoothStop', defaultSmoothStop); % Parse... p.parse(varargin{:}); @@ -825,51 +723,70 @@ classdef Motor < handle if motor.init motor.port = p.Results.port; end - motor.limitValue = p.Results.limitValue; - motor.limitMode = p.Results.limitMode; -% if ~motor.init && motor.power ~= p.Results.power % To avoid unnecessary communication with physical brick -% motor.power = p.Results.power; -% end + motor.tachoLimit = p.Results.tachoLimit; + motor.tachoLimitMode = p.Results.tachoLimitMode; motor.power = p.Results.power; motor.brakeMode = p.Results.brakeMode; - motor.smoothStart = p.Results.smoothStart; - motor.smoothStop = p.Results.smoothStop; motor.debug = p.Results.debug; motor.speedRegulation = p.Results.speedRegulation; - motor.mode = p.Results.mode; + motor.smoothStart = p.Results.smoothStart; + motor.smoothStop = p.Results.smoothStop; end %% Getter function portNo = get.portNo(motor) - portNo = motor.getPortNo(); + portNo = bitfield2port(motor.port); end function portInput = get.portInput(motor) - portInput = motor.getPortInput(); + portInput = bitfield2input(motor.port); end function cnt = get.tachoCount(motor) - if ~motor.isConnected || ~motor.motorAtPort - warning('Motor::get.tachoCount: Could not detect motor at port %s.', ... - motor.port); - - cnt = 0; - return; + cnt = 0; + if motor.connectedToBrick + try + cnt = motor.getTachoCount(); + catch + warning('Motor::get.tachoCount: Could not detect motor at port %s.', ... + port2str('Motor', motor.port)); + return; + end end - - cnt = motor.getTachoCount(); end - function speed = get.speed(motor) - if ~motor.isConnected || ~motor.motorAtPort - warning('Motor::get.tachoCount: Could not detect motor at port %s.', ... - motor.port); - - speed = 0; - return; + function speed = get.currentSpeed(motor) + speed = 0; + if motor.connectedToBrick + try + speed = motor.getSpeed(); + catch + warning('Motor::get.tachoCount: Could not detect motor at port %s.', ... + port2str('Motor', motor.port)); + return; + end end - - speed = motor.getSpeed(); + end + + function running = get.isRunning(motor) + running = motor.currentSpeed>0; + end + + function synced = get.isSynced(motor) + synced = (length(findprop(motor, 'syncCache'))==1); + end + + function motorType = get.type(motor) + if motor.connectedToBrick + [motorType, ~] = motor.getTypeMode(); + else + motorType = DeviceType.Unknown; + end + end + + function conn = get.physicalMotorConnected(motor) + currentType = motor.type; + conn = (currentType==DeviceType.MediumMotor || currentType==DeviceType.LargeMotor); end %% Display @@ -880,9 +797,35 @@ classdef Motor < handle end end - methods (Access = 'protected') % Private brick functions that are wrapped by dependent params + methods (Access = 'private') % Private brick functions that are wrapped by dependent params + function running = getMotorStatus(motor) + %isRunning Returns whether motor is running (WITH TACHOLIMIT) or not. + % + % Notes: + % * This *mostly* works. Sometimes this falsely returns 0 if isRunning() gets + % called immediately after starting the motor. + % + + if ~motor.connectedToBrick + error(['Motor::isRunning: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); +% elseif ~motor.physicalMotorConnected +% error('Motor::isRunning: No physical motor connected to Port %s',... +% port2str('Motor', motor.port)); + elseif ~motor.tachoLimit + warning(['Motor::isRunning: Motor has no tacho limit. ' ,... + 'Can''t reliably determine whether it is running or not.']); + end + + running = motor.commInterface.outputTest(0, motor.port); + + if motor.debug + fprintf('(DEBUG) Motor::isRunning: Called outputReady on Port %s\n', port2str('Motor', motor.port)); + end + end + function setPower(motor, power) - %setPower Sets given power value on the physical brick. + %setPower Sets given power value on the physical Brick. % % Notes: % * If motor is running with a limit, calling outputSpeed/outputPower, to @@ -891,160 +834,176 @@ classdef Motor < handle % with the new value instantly. However, this sometimes leads to unexpected behaviour. % Therefore, if motor is running with a limit, setPower aborts with a warning. % - - if ~motor.isConnected || ~motor.motorAtPort - return; + if ~motor.connectedToBrick + error(['Motor::getTachoCount: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); +% elseif ~motor.physicalMotorConnected +% error('Motor::getTachoCount: No physical motor connected to Port %s',... +% port2str('Motor', motor.port)); end - if motor.limitValue~=0 && motor.isRunning() + if motor.tachoLimit~=0 && motor.isRunning warning(['Motor::setPower: Can''t set power if motor is running with a ',... 'tacholimit. This would mess up the internal tacho state.']); return; else if motor.speedRegulation - motor.brick.outputSpeed(0, motor.port_, power); + motor.commInterface.outputSpeed(0, motor.port, power); if motor.debug - fprintf('(DEBUG) Motor::setPower: Called outputSpeed on Port %s\n', motor.port); + fprintf('(DEBUG) Motor::setPower: Called outputSpeed on Port %s\n', port2str('Motor', motor.port)); end else - motor.brick.outputPower(0, motor.port_, power); + motor.commInterface.outputPower(0, motor.port, power); if motor.debug - fprintf('(DEBUG) Motor::setPower: Called outputPower on Port %s\n', motor.port); + fprintf('(DEBUG) Motor::setPower: Called outputPower on Port %s\n', port2str('Motor', motor.port)); end end end - motor.changed = 0; + motor.sendPowerOnNextStart = false; end function setMode(motor, mode) - if ~motor.isConnected || ~motor.motorAtPort - return; + if ~motor.connectedToBrick + error(['Motor::getTachoCount: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); +% elseif ~motor.physicalMotorConnected +% error('Motor::getTachoCount: No physical motor connected to Port %s',... +% port2str('Motor', motor.port)); end - motor.brick.inputReadSI(0, motor.portInput, mode); % Reading a value implicitly + motor.commInterface.inputReadSI(0, motor.portInput, mode); % Reading a value implicitly % sets the mode. if motor.debug - fprintf('(DEBUG) Motor::setMode: Called inputReadSI on input port %d\n',... - uint8(motor.portInput)); + fprintf('(DEBUG) Motor::setMode: Called inputReadSI on Port %s\n',... + port2str('Motor', motor.port)); end end - function resetTachoCount(motor) - %resetTachoCount Resets tacho count to 0 if running without tacholimit - % Compared to motor.reset, this resets the 'sensor mode' tacho count, a second - % tacho counter. This counter is used for reading the tacho count with inputRead - % and outputGetCount (via motor.tachoCount). - % See also MOTOR.RESET - - if ~motor.isConnected || ~motor.motorAtPort - return; - end - - motor.brick.outputClrCount(0, motor.port_); - - if motor.debug - fprintf('(DEBUG) Motor::resetTachoCount: Called outputClrCount on Port %s\n',... - motor.port); - end - end - function [type,mode] = getTypeMode(motor) - if ~motor.isConnected - error(['Motor::getTypeMode: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); + if ~motor.connectedToBrick + error(['Motor::getTypeMode: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); end - [typeNo,modeNo] = motor.brick.inputDeviceGetTypeMode(0, motor.portInput); + [typeNo,modeNo] = motor.commInterface.inputDeviceGetTypeMode(0, motor.portInput); type = DeviceType(typeNo); mode = DeviceMode(type,modeNo); if motor.debug fprintf('(DEBUG) Motor::getTypeMode: Called inputDeviceGetTypeMode on Port %s\n',... - motor.port); + port2str('Motor', motor.port)); end end function status = getStatus(motor) - if ~motor.isConnected - error(['Motor::getStatus: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); + if ~motor.connectedToBrick + error(['Motor::getStatus: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); end - statusNo = motor.brick.inputDeviceGetConnection(0, motor.portInput); + statusNo = motor.commInterface.inputDeviceGetConnection(0, motor.portInput); status = ConnectionType(statusNo); if motor.debug fprintf('(DEBUG) Motor::getStatus: Called inputDeviceGetConnection on Port %s\n',... - motor.port); + port2str('Motor', motor.port)); end end function cnt = getTachoCount(motor) - if ~motor.isConnected - error(['Motor::getTachoCount: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort - error('Motor::getTachoCount: No physical motor connected to Port %s',... - motor.port); + if ~motor.connectedToBrick + error(['Motor::getTachoCount: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); +% elseif ~motor.physicalMotorConnected +% error('Motor::getTachoCount: No physical motor connected to Port %s',... +% port2str('Motor', motor.port)); end - if motor.mode == DeviceMode.Motor.Degrees - cnt = motor.brick.outputGetCount(0, motor.portNo); - if motor.debug - fprintf('(DEBUG) Motor::getTachoCount: Called outputGetCount on Port %s\n', motor.port); - end - elseif motor.mode == DeviceMode.Motor.Rotations - cnt = motor.brick.inputReadSI(0, motor.portInput, motor.mode); - if motor.debug - fprintf('(DEBUG) Motor::getTachoCount: Called inputReadSI on Port %s\n', motor.port); - end - else - error('Motor::getTachoCount: Motor mode not valid!'); + cnt = motor.commInterface.outputGetCount(0, motor.portNo); + if motor.debug + fprintf('(DEBUG) Motor::getTachoCount: Called outputGetCount on Port %s\n', port2str('Motor', motor.port)); end end function speed = getSpeed(motor) - if ~motor.isConnected - error(['Motor::getSpeed: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort - error('Motor::getSpeed: No physical motor connected to Port %s',... - motor.port); + if ~motor.connectedToBrick + error(['Motor::getSpeed: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); +% elseif ~motor.physicalMotorConnected +% error('Motor::getSpeed: No physical motor connected to Port %s',... +% port2str('Motor', motor.port)); end - if ~motor.speedRegulation - warning('Motor::getSpeed: Speed only valid with speed regulation turned on.'); - end - speed = motor.brick.inputReadSI(0, motor.portInput, DeviceMode.Motor.Speed); + speed = motor.commInterface.inputReadSI(0, motor.portInput, DeviceMode.Motor.Speed); if motor.debug - fprintf('(DEBUG) Motor::getSpeed: Called inputReadSI on Port %s\n', motor.port); + fprintf('(DEBUG) Motor::getSpeed: Called inputReadSI on Port %s\n', port2str('Motor', motor.port)); end end end - methods (Hidden, Access = 'protected') % Wrapper for utility functions, hidden from user - % These are overridden in SyncMotor so the 'right' utility function - % will always be called. - function portNo = getPortNo(motor) - if isempty(motor.port_) - error(['Motor::portNo: This method should only be called AFTER ',... - 'setting motor.port.']); + methods (Access = {?EV3}) + function connect(motor,commInterface) + %connect Connects Motor-object to physical brick. + % + % Notes: + % * This method actually only saves a handle to a Brick-object which has been + % created beforehand (probably by an EV3-object). + % + % Arguments + % * commInterface: instance of Brick-class + % + % Examples (for use without EV3-class) + % m = Motor(); + % brickInterface = Brick('usb'); + % m.connect(brickInterface); + % % do stuff + % + + if motor.connectedToBrick + if isCommInterfaceValid(motor.commInterface) + error('Motor::connect: Motor-Object already has a comm handle.'); + else + warning(['Motor::connect: Motor.connectedToBrick is set to ''True'', but ',... + 'comm handle is invalid. Deleting invalid handle and ' ,... + 'resetting Motor.connectedToBrick now...']); + + motor.commInterface = 0; + motor.connectedToBrick = false; + + error('Motor::connect: Disconnected due to internal error.'); + end end - portNo = bitfield2port(motor.port_); + motor.commInterface = commInterface; + motor.connectedToBrick = true; + + if motor.debug + fprintf('(DEBUG) Motor-Object connected to comm handle.\n'); + end end - function portInput = getPortInput(motor) - if isempty(motor.port_) - error(['Motor::portInput: This method should only be called AFTER ',... - 'setting motor.port.']); - end + function disconnect(motor) + %disconnect Disconnects Motor-object from physical brick. + % + % Notes: + % * As with Motor::connect, this method actually only sets the property, in which + % the handle to the Brick-class was stored, to 0. + % + % Examples (for use without EV3-class) + % m = Motor(); + % brickInterface = Brick('usb'); + % m.connect(brickInterface); + % % do stuff + % m.disconnect(); + % brickInterface.delete(); % Actual disconnecting!!! + % - portInput = port2input(motor.portNo); + motor.commInterface = 0; % Note: actual deleting is done in EV3::disconnect. + motor.connectedToBrick = false; end end end diff --git a/source/MotorPort.m b/source/MotorPort.m index c4e5386..a25cf54 100644 --- a/source/MotorPort.m +++ b/source/MotorPort.m @@ -5,5 +5,4 @@ classdef MotorPort < uint8 MotorC (2) MotorD (3) end -end - +end \ No newline at end of file diff --git a/source/Sensor.m b/source/Sensor.m index fe0c983..33a531b 100644 --- a/source/Sensor.m +++ b/source/Sensor.m @@ -4,21 +4,18 @@ classdef Sensor < handle % Properties: % Standard % debug - Debug mode turned on or off - % mode - Sensor mode in which the value will be read + % mode - Sensor mode in which the values will be read % Dependent % value - Value read from sensor - % get-only - % isConnected - Is virtual brick connected to physical one? - % brick - Brick object from sublayer class Brick -> used as interface to the physical brick. + % type - Type of connected sensor if any % % Methods: % Standard - % EV3 - - % connect - Connects Sensor-object to physical brick. - % disconnect - Disconnects Sensor-object from physical brick. - % update - Updates Sensor-object to current status at its port. + % EV3 + % connect - Connects Sensor-object to physical brick + % disconnect - Disconnects Sensor-object from physical brick % reset - Resets value on sensor - % setProperties - Sets multiple Sensor properties at once using MATLAB's inputParser. + % setProperties - Sets multiple Sensor properties at once using MATLAB's inputParser % % Example % % This small example should only roughly demonstrate how to work with sensors. @@ -177,7 +174,7 @@ classdef Sensor < handle % Set default values if sensor.init defaultDebug = 0; - defaultMode = DeviceMode.Error.Undefined; + defaultMode = DeviceMode.Default.Undefined; else defaultDebug = sensor.debug; defaultMode = sensor.mode; @@ -203,15 +200,36 @@ classdef Sensor < handle %% Getter function value = get.value(sensor) - if ~sensor.isConnected || ~sensor.sensorAtPort - warning('Sensor::get.value: Could not detect sensor at port %s.', ... - sensor.port); - - value = 0; + value = 0; + if strcmp(class(sensor.mode), 'DeviceMode.Default') || ... + ~isModeValid(sensor.mode, sensor.type) + warning('Sensor::get.value: Cannot read sensor values in current mode.'); return; end - value = sensor.getValue(); + if sensor.connectedToBrick + try + value = sensor.getValue(); + catch + warning('Sensor::get.value: Could not detect sensor at port %d.', ... + sensor.port+1); + return; + 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 @@ -224,16 +242,20 @@ classdef Sensor < handle methods (Access = 'private') % Private brick functions that are wrapped by dependent params function setMode(sensor, mode) - if ~sensor.isConnected || ~sensor.sensorAtPort - return + if ~sensor.connectedToBrick + error(['Sensor::getTachoCount: Sensor-Object not connected to comm handle.',... + 'You have to call sensor.connect(commInterface) first!']); +% elseif ~sensor.physicalSensorConnected +% error('Sensor::getTachoCount: No physical sensor connected to Port %d',... +% sensor.port+1); end - sensor.brick.inputReadSI(0, sensor.port_, mode); % Reading a value implicitly - % sets the mode. + sensor.commInterface.inputReadSI(0, sensor.port, mode); % Reading a value implicitly + % sets the mode. if sensor.debug - fprintf('(DEBUG) Sensor::setMode: Called inputReadSI on Port %s\n',... - sensor.port); + fprintf('(DEBUG) Sensor::setMode: Called inputReadSI on Port %d.\n',... + sensor.port+1); end end @@ -245,15 +267,15 @@ classdef Sensor < handle % this case, the inputReadSI-opCode is sent again to get the correct value. % - if ~sensor.isConnected - error(['Sensor::getValue: Sensor-Object not connected to brick handle.',... - 'You have to call sensor.connect(brick) first!']); - elseif ~sensor.sensorAtPort - error('Sensor::getValue: No physical sensor connected to Port %d.',... - sensor.port); + if ~sensor.connectedToBrick + error(['Sensor::getValue: Sensor-Object not connected to comm handle.',... + 'You have to call sensor.connect(commInterface) first!']); +% elseif ~sensor.physicalSensorConnected +% error('Sensor::getValue: No physical sensor connected to Port %d.',... +% sensor.port+1); end - val = sensor.brick.inputReadSI(0, sensor.port_, sensor.mode); + val = sensor.commInterface.inputReadSI(0, sensor.port, sensor.mode); if strcmp(class(sensor.mode), 'DeviceMode.Color') if sensor.mode == DeviceMode.Color.Col -- GitLab