diff --git a/source/EV3.m b/source/EV3.m index abe4c262544b5232cd40f3c330a92f3f112180ef..d45c42e8872862a8618d756abbd004935c16502c 100755 --- a/source/EV3.m +++ b/source/EV3.m @@ -198,6 +198,9 @@ classdef EV3 < MaskedHandle % b.disconnect(); % + % Reset motors and sensors before disconnecting + ev3.resetPhysicalBrick(); + % Disconnect motors and sensors % -> set references to comm handle to 0 ev3.motorA.disconnect(); @@ -480,5 +483,20 @@ classdef EV3 < MaskedHandle end end end + + function resetPhysicalBrick(ev3) + sensors = {'sensor1', 'sensor2', 'sensor3', 'sensor4'}; + motors = {'motorA', 'motorB', 'motorC', 'motorD'}; + + for i = 1:4 + motor = motors{i}; + ev3.(motor).resetPhysicalMotor(); + end + + for i = 1:4 + sensor = sensors{i}; + ev3.(sensor).resetPhysicalSensor(); + end + end end end diff --git a/source/Motor.m b/source/Motor.m index 5e2431415a3bf74f3e3d21f7c009ca315949da53..be281aea8eca1d04a9721ba351967cd30e36312d 100755 --- a/source/Motor.m +++ b/source/Motor.m @@ -95,16 +95,9 @@ classdef Motor < MaskedHandle & dynamicprops brakeMode_; %% Flags - - connectedToBrick = false; % Connection to physical Brick? init = true; % Indicates 'init-phase' (True as long as constructor is running) - sendPowerOnSet = false; % If true, OUTPUT_POWER is sent when setting power - - % Bitfield representing which opCodes should be sent on Motor.start() - % The corresponding opCodes for each bit are (in Big Endian): - % * 1st Bit: OUTPUT_POWER (sets power on physical Brick) - % * 2nd Bit: OUTPUT_STOP (stops Brick; workaround for a bug, see motor.start, Note 2) - sendOnStart = 0; + connectedToBrick = false; % Connection to physical Brick? + state = MotorState(); % State consisting of several special Motor-flags end properties (Hidden, Dependent, Access = 'private') % Hidden, dependent properties for internal use only @@ -162,35 +155,59 @@ classdef Motor < MaskedHandle & dynamicprops end % If motor has been started synced with another, and it stopped 'itself' (when - % using a tacholimit), the sync cache has to be deleted (in general, syncedStop + % using a tacholimit), the sync cache has to be deleted (otherwise, syncedStop % would do so) if motor.isSynced - delete(motor.findprop('syncCache')); - motor.internalReset(); % Better safe than sorry + % Retrieve and delete former slave + if length(findprop(motor, 'slave'))==1 + syncMotor = motor.slave; + delete(motor.findprop('slave')); + delete(syncMotor.findprop('master')); + else + syncMotor = motor.master; + delete(motor.findprop('master')); + delete(syncMotor.findprop('slave')); + end + + % Reset state + motor.applyState(); + syncMotor.applyState(); + + % Send power on next set + if motor.state.sendPowerOnSet + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Power, 1); + end + if syncMotor.state.sendPowerOnSet + syncMotor.state.sendOnStart = bitset(syncMotor.state.sendOnStart, SendOnStart.Power, 1); + end + + % Better safe than sorry + motor.internalReset(); + syncMotor.internalReset(); end % If the motor coasts into its stops, the internal tachocount has to be reset % before each start for it to behave predictable - if motor.brakeMode_ == BrakeMode.Coast + if motor.brakeMode_ == BrakeMode.Coast || motor.internalTachoCount ~= 0 motor.internalReset(); end % Call appropriate function in commInterface depending on limitValue and limitMode if motor.limitValue==0 - if motor.sendOnStart > 0 + if motor.state.sendOnStart > 0 % If stop-flag is set: call stop() and reset flag - if bitget(motor.sendOnStart, SendOnStart.Stop) + if bitget(motor.state.sendOnStart, SendOnStart.Stop) motor.stop(); - motor.sendOnStart = bitset(motor.sendOnStart, SendOnStart.Stop, 0); + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Stop, 0); end % If power-flag is set: call setPower() and reset flag if successful - if bitget(motor.sendOnStart, SendOnStart.Power) + if bitget(motor.state.sendOnStart, SendOnStart.Power) success = motor.setPower(motor.power); if ~success - motor.sendOnStart = bitset(motor.sendOnStart, SendOnStart.Power, 1); + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Power, 1); else - motor.sendOnStart = bitset(motor.sendOnStart, SendOnStart.Power, 0); + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Power, 0); end end @@ -201,6 +218,7 @@ classdef Motor < MaskedHandle & dynamicprops if motor.debug fprintf('(DEBUG) Motor::start: Called outputStart on Port %s\n', port2str('Motor', motor.port)); end + motor.state.startedNotBusy = true; else limit = motor.limitValue - (motor.smoothStart + motor.smoothStop); if limit < 0 @@ -270,6 +288,8 @@ classdef Motor < MaskedHandle & dynamicprops if motor.debug fprintf('(DEBUG) Motor::stop: Called outputStop on Port %s\n', port2str('Motor', motor.port)); end + + motor.state.startedNotBusy = false; end function syncedStart(motor, syncMotor, varargin) @@ -342,42 +362,37 @@ classdef Motor < MaskedHandle & dynamicprops 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 +% if motor.power == 0 +% warning('Motor::syncedStart: Synchronized motors starting with power=0.'); +% end % If the motor coasts into its stops, the internal tachocount has to be reset % before each start for it to behave predictable - if motor.brakeMode_ == BrakeMode.Coast + if motor.brakeMode_ == BrakeMode.Coast || motor.internalTachoCount ~= 0 motor.internalReset(); + syncMotor.internalReset(); end - if motor.sendOnStart > 0 + if motor.state.sendOnStart > 0 % If stop-flag is set: call stop() and reset flag - if bitget(motor.sendOnStart, SendOnStart.Stop) + if bitget(motor.state.sendOnStart, SendOnStart.Stop) motor.stop(); - motor.sendOnStart = bitset(motor.sendOnStart, SendOnStart.Stop, 0); + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Stop, 0); end 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 + % Cache old values to make it possible to reset them on syncedStop + % Note: the existence of 'slave' 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; + motor.addProperty(syncMotor, 'slave', true); + syncMotor.addProperty(motor, 'master', true); + + motor.saveState(); + syncMotor.saveState(); % Disable immediate sending of new power values - motor.sendPowerOnSet = false; - syncMotor.sendPowerOnSet = false; + motor.state.sendPowerOnSet = false; + syncMotor.state.sendPowerOnSet = false; % Keep 'slave'-motor synchronized syncMotor.speedRegulation = false; @@ -413,11 +428,19 @@ classdef Motor < MaskedHandle & dynamicprops if ~motor.isSynced error('Motor::syncedStop: Motor has not been started synchronized with another.'); + else + % Retrieve synced motor from cache + if length(findprop(motor, 'slave'))==1 + syncMotor = motor.slave; + delete(motor.findprop('slave')); + delete(syncMotor.findprop('master')); + else + syncMotor = motor.master; + delete(motor.findprop('master')); + delete(syncMotor.findprop('slave')); + end 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.'); elseif ~motor.physicalMotorConnected || ~syncMotor.physicalMotorConnected @@ -425,21 +448,19 @@ classdef Motor < MaskedHandle & dynamicprops 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; - syncMotor.sendPowerOnSet = motor.syncCache.master_oldSendPowerOnSet; - delete(motor.findprop('syncCache')); + % Reset state + motor.applyState(); + syncMotor.applyState(); % Synced stopping motor.commInterface.outputStop(0, motor.port+syncMotor.port, motor.brakeMode_); % On next start, both motors have to send power-opcode again - if motor.sendPowerOnSet - motor.sendOnStart = bitset(motor.sendOnStart, SendOnStart.Power, 1); + if motor.state.sendPowerOnSet + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Power, 1); end - if syncMotor.sendPowerOnSet - syncMotor.sendOnStart = bitset(syncMotor.sendOnStart, SendOnStart.Power, 1); + if syncMotor.state.sendPowerOnSet + syncMotor.state.sendOnStart = bitset(syncMotor.state.sendOnStart, SendOnStart.Power, 1); end @@ -572,7 +593,7 @@ classdef Motor < MaskedHandle & dynamicprops motor.releaseBrake(); end - motor.sendOnStart = SendOnStart.Power + SendOnStart.Stop; + motor.state.sendOnStart = SendOnStart.Power + SendOnStart.Stop; end %% Setter @@ -586,12 +607,12 @@ classdef Motor < MaskedHandle & dynamicprops motor.power = power; % Set power parameter. - if motor.sendPowerOnSet + if motor.state.sendPowerOnSet success = motor.setPower(power); if ~success - motor.sendOnStart = bitset(motor.sendOnStart, SendOnStart.Power, 1); + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Power, 1); else - motor.sendOnStart = bitset(motor.sendOnStart, SendOnStart.Power, 0); + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Power, 0); end end end @@ -610,8 +631,8 @@ classdef Motor < MaskedHandle & dynamicprops speedRegulation = str2bool(speedRegulation); if ~isempty(motor.speedRegulation) && (speedRegulation ~= motor.speedRegulation) - if motor.sendPowerOnSet - motor.sendOnStart = bitset(motor.sendOnStart, SendOnStart.Power, 1); + if motor.state.sendPowerOnSet + motor.state.sendOnStart = bitset(motor.state.sendOnStart, SendOnStart.Power, 1); end end @@ -660,12 +681,12 @@ classdef Motor < MaskedHandle & dynamicprops end function set.limitMode(motor, limitMode) - validModes = {'Time', 'Tacho'}; - if ~ischar(limitMode) || ~ismember(limitMode, validModes) + if ~ischar(limitMode) || ... + (~strcmpi(limitMode, 'tacho') && ~strcmpi(limitMode, 'time')) error('Motor::set.limitMode: Given parameter is not a valid limit mode.'); - else - motor.limitMode = limitMode; - end + end + + motor.limitMode = limitMode; end function set.limitValue(motor, limitValue) @@ -677,14 +698,14 @@ classdef Motor < MaskedHandle & dynamicprops end if limitValue == 0 - motor.sendOnStart = SendOnStart.Power; + motor.state.sendOnStart = SendOnStart.Power; if ~isempty(motor.limitValue) && motor.limitValue > 0 - motor.sendOnStart = motor.sendOnStart + SendOnStart.Stop; + motor.state.sendOnStart = motor.state.sendOnStart + SendOnStart.Stop; end - motor.sendPowerOnSet = true; + motor.state.sendPowerOnSet = true; else - motor.sendOnStart = 0; - motor.sendPowerOnSet = false; + motor.state.sendOnStart = 0; + motor.state.sendPowerOnSet = false; end motor.limitValue= limitValue; @@ -789,10 +810,6 @@ classdef Motor < MaskedHandle & dynamicprops motor.speedRegulation = p.Results.speedRegulation; motor.smoothStart = p.Results.smoothStart; motor.smoothStop = p.Results.smoothStop; - -% if motor.limitValue == 0 -% motor.sendPowerOnSet = true; -% end end %% Getter @@ -842,13 +859,20 @@ classdef Motor < MaskedHandle & dynamicprops function running = get.isRunning(motor) running = 0; + if motor.connectedToBrick - running = motor.getBusyFlag(); + busyFlag = motor.getBusyFlag(); + else + busyFlag = 0; end + + assert(~(motor.state.startedNotBusy && busyFlag)); + running = motor.state.startedNotBusy || busyFlag; end function synced = get.isSynced(motor) - synced = (length(findprop(motor, 'syncCache'))==1); + synced = (length(findprop(motor, 'slave'))==1 || ... + length(findprop(motor, 'master'))==1); end function motorType = get.type(motor) @@ -1053,6 +1077,47 @@ classdef Motor < MaskedHandle & dynamicprops end end + methods (Access = 'private', Hidden = true) + function saveState(motor) + %saveState Saves current motor state in dynamic property + + meta = motor.findprop('savedState'); + if isempty(meta) + meta = motor.addprop('savedState'); + meta.Hidden = true; + meta.Access = 'private'; + end + + motor.savedState = motor.state; + end + + function applyState(motor) + %applyState Sets motor state to saved state and deletes the dynamic property in + %which the latter is stored + + assert(length(motor.findprop('savedState')) ~= 0); + + motor.state = motor.savedState; + delete(motor.findprop('savedState')) + end + + function addProperty(motor, propValue, propName, override) + override = str2bool(override); + + meta = motor.findprop(propName); + + if isempty(meta) + meta = motor.addprop(propName); + meta.Hidden = true; + meta.Access = 'private'; + elseif ~override + error('Motor::addProperty: Motor already has this property.'); + end + + motor.(propName) = propValue; + end + end + methods (Access = {?EV3}) function connect(motor,commInterface) %connect Connects Motor-object to physical brick. @@ -1112,5 +1177,16 @@ classdef Motor < MaskedHandle & dynamicprops motor.commInterface = 0; % Note: actual deleting is done in EV3::disconnect. motor.connectedToBrick = false; end + + function resetPhysicalMotor(motor) + if ~motor.connectedToBrick || ~motor.physicalMotorConnected + return; + end + + motor.resetTachoCount(); + motor.internalReset(); + motor.setBrake(0); + %motor.stop(); + end end end diff --git a/source/MotorState.m b/source/MotorState.m new file mode 100644 index 0000000000000000000000000000000000000000..ed551f7f453b960198d5f113f67b75e826b52e2c --- /dev/null +++ b/source/MotorState.m @@ -0,0 +1,39 @@ +classdef MotorState + properties + startedNotBusy = false; % Set to true if motor started w/o tacholimit and unsynced + sendPowerOnSet = false; % If true, OUTPUT_POWER is sent when setting power + + % Bitfield representing which opCodes should be sent on Motor.start() + % The corresponding opCodes for each bit are (in Big Endian): + % * 1st Bit: OUTPUT_POWER (sets power on physical Brick) + % * 2nd Bit: OUTPUT_STOP (stops Brick; workaround for a bug, see motor.start, Note 2) + sendOnStart = 0; + end + + methods + function display(state) + fprintf('#### Motor State ####\n'); + props = properties(state); + for i = 1:length(props) + p = props{i}; + fprintf('%16s: %d\n', p, state.(p)); + end + fprintf('#####################\n'); + end + + function isequal = eq(state1, state2) + props = properties(state1); + + isequal = 1; + for i = 1:length(props) + p = props{i}; + if state1.(p) ~= state2.(p) + isequal = 0; + break; + end + end + end + end + +end + diff --git a/source/Sensor.m b/source/Sensor.m index dc12c91ee81442ee867bb17af76365a0649ed79e..d275d507371c37bb35570b7031a8641fef6a9e44 100755 --- a/source/Sensor.m +++ b/source/Sensor.m @@ -357,6 +357,15 @@ classdef Sensor < MaskedHandle sensor.commInterface = 0; % Note: actual deleting is done in EV3::disconnect. sensor.connectedToBrick = false; end + + function resetPhysicalSensor(sensor) + if ~sensor.connectedToBrick || ~sensor.physicalSensorConnected + return + end + + sensor.mode = DeviceMode(sensor.type, uint8(0)); + sensor.reset; + end end end diff --git a/tools/private/connectWires.m b/tools/private/connectWires.m new file mode 100644 index 0000000000000000000000000000000000000000..1a96bf72a8bf5a2bb724e3014e702db660ec5e3a --- /dev/null +++ b/tools/private/connectWires.m @@ -0,0 +1,11 @@ +function [] = connectWires() +%Connect motors to ports +disp(sprintf('Please connect middle motor to port A, large motors to port B and C. Then press any key to continue.\n\n')); +pause(); + +%Connect sensors to ports +disp(sprintf('Please connect touch sensor to port 1, gyro sensor to port 2, color sensor to port 3,\nultrasonic sensor to port 4. Then press any key to continue.\n\n')); +pause(); + +end + diff --git a/tools/private/motorTest.m b/tools/private/motorTest.m new file mode 100644 index 0000000000000000000000000000000000000000..ab200d110337c033a9eb540225a054242f3ac502 --- /dev/null +++ b/tools/private/motorTest.m @@ -0,0 +1,74 @@ +function ret = motorTest(EV3, mode) +ret = 0; +%Start test +disp(sprintf(['--------------------------------------------------------------\nStart motor test ', mode])); +%Test motor A +EV3.motorA.power=20; +EV3.motorA.limitValue=1000; +try EV3.motorA.start +catch all +end; + +answerA = input('Did motor A move? [y/n]', 's'); +if ~strcmp(answerA,'y') && ~strcmp(answerA,'n') + while true + answerA = input('Incorrect input. Did motor A move? [y/n]', 's'); + if strcmp(answerA,'y') || strcmp(answerA,'n') + break; + end + end +end +%Test motor B +EV3.motorB.power=20; +EV3.motorB.limitValue=1000; +try EV3.motorB.start +catch all +end; + +answerB = input('Did motor B move? [y/n]', 's'); +if ~strcmp(answerB,'y') && ~strcmp(answerB,'n') + while true + answerB = input('Incorrect input. Did motor B move? [y/n]', 's'); + if strcmp(answerB,'y') || strcmp(answerB,'n') + break; + end + end +end + +%Test motor C +EV3.motorC.power=20; +EV3.motorC.limitValue=1000; +try EV3.motorC.start +catch all +end; + +answerC = input('Did motor C move? [y/n]', 's'); +if ~strcmp(answerC,'y') && ~strcmp(answerC,'n') + while true + answerC = input('Incorrect input. Did motor C move? [y/n]', 's'); + if strcmp(answerC,'y') || strcmp(answerC,'n') + break; + end + end +end + +%Warnings for defect motors +if (~isempty(strfind(answerA, 'n'))) + warning('Test for motor A failed'); +end; + +if (~isempty(strfind(answerB, 'n'))) + warning('Test for motor B failed'); +end; + +if (~isempty(strfind(answerC, 'n'))) + warning('Test for motor C failed'); +end; + +if (~isempty(strfind(answerA, 'y'))) && (~isempty(strfind(answerB, 'y'))) && (~isempty(strfind(answerC, 'y'))) + disp(sprintf('All motors work correctly.\n')); + ret = 1; +end; + +end + diff --git a/tools/private/sensorTest.m b/tools/private/sensorTest.m new file mode 100644 index 0000000000000000000000000000000000000000..b94075a4dd0cabae373005e98a4eef5b132307cd --- /dev/null +++ b/tools/private/sensorTest.m @@ -0,0 +1,57 @@ +function ret = sensorTest(EV3, mode) + +ret = 0; + +%Start test +disp(sprintf(['--------------------------------------------------------------\nStart sensor test ', mode])); + +%Test sensor 1 +if EV3.sensor1.type == DeviceType.Touch + answer1 = 1; +else + answer1 = 0; +end; +%Test sensor 2 +if EV3.sensor2.type == DeviceType.Gyro + answer2 = 1; +else + answer2 = 0; +end; +%Test sensor 3 +if EV3.sensor3.type == DeviceType.Color + answer3 = 1; +else + answer3 = 0; +end; + +%Test sensor 4 +if EV3.sensor4.type == DeviceType.UltraSonic + answer4 = 1; +else + answer4 = 0; +end; + +%Warnings for defect motors +if answer1 == 0 + warning('Test for sensor 1 failed. Expected touch sensor.'); +end; + +if answer2 == 0 + warning('Test for sensor 2 failed. Expected gyro sensor.'); +end; + +if answer3 == 0 + warning('Test for sensor 3 failed. Expected color sensor.'); +end; + +if answer4 == 0 + warning('Test for sensor 4 failed. Expected ultrasonic sensor'); +end; + +if answer1==1 && answer2==1 && answer3==1 && answer4==1 + disp(sprintf('All sensors work correctly.\n')); + ret = 1; +end; + +end + diff --git a/tools/testAllMotorsAndSensors.m b/tools/testAllMotorsAndSensors.m new file mode 100644 index 0000000000000000000000000000000000000000..c9dc8e3aa337954b226825b9ed76483e45f86165 --- /dev/null +++ b/tools/testAllMotorsAndSensors.m @@ -0,0 +1,93 @@ +function [] = testAllMotorsAndSensors() + +% Connect the Brick via USB +clear all; +b=EV3; +connected = 0; +i=0; +while connected == 0 + disp(sprintf('Please connect the brick via USB and press any key to continue.\n\n')); + pause(); + try b.connect('usb') + catch disp('Connection failed, try again...') + %discard errors + end; + connected = b.isConnected(); + i=i+1; + if i==3 + error('USB connection failed. You can not continue the test. Restart Matlab and the brick, then try again. If the error occurs again, please contact us.'); + end; +end; + +% Connect motors and sensors to ports +connectWires(); + +% Test motors (USB) +USBresultMotor = motorTest(b, 'USB'); + +if USBresultMotor == 0 + warning('The USB motor test failed. Please contact us.'); +end; + +% Test sensors (USB) +USBresultSensor = sensorTest(b, 'USB'); + +if USBresultSensor == 0 + warning('The USB sensor test failed. Please contact us.'); +end; + +if (USBresultMotor == 1) && (USBresultSensor == 1) + disp(sprintf('\n<strong>--------------All USB tests passed successfully---------------</strong>\n')); +else + disp(sprintf('\n<strong>-----End of USB tests. Some tests failed, notice the warnings.-----</strong>\n')); +end; + +b.disconnect; +pause(1); + +%Connect the Brick via BT +b=EV3; +connected = 0; +i=0; +while connected==0 + disp(sprintf('Please remove the USB wire and use btconnect in the terminal, so that the brick can be\nautomatically connected via BT and press any key to continue.\n\n')); + pause(); + try b.connect('bt', 'serPort', '/dev/rfcomm0') + catch all + end; + try b.connect('bt', 'serPort', '/dev/rfcomm1') + catch all + end; + pause(1); + connected = b.isConnected(); + i=i+1; + if i==3 + error('BT connection failed. You can not continue the test. Did you connect the brick with btconnect? If no, start btconnect in the terminal. If yes, restart Matlab and try again. If the error occurs again, please contact us.'); + end; +end; + +%Test motors (BT) +BTresultMotor = motorTest(b, 'BT'); + +if BTresultMotor == 0 + warning('The BT motor test failed. Please contact us.'); +end; + +%Test sensors (BT) +BTresultSensor = sensorTest(b, 'BT'); + +if BTresultSensor == 0 + warning('The BT sensor test failed. Please contact us.'); +end; + +if (BTresultMotor == 1) && (BTresultSensor == 1) + disp(sprintf('\n<strong>---------------All BT tests passed successfully---------------</strong>\n')); +else + disp(sprintf('\n<strong>-----End of BT tests. Some tests failed, notice the warnings.-----</strong>\n')); +end; + +disp(sprintf('\n------------------------End of the test------------------------\n')); +clear all; + +end +