diff --git a/examples/MotorBraking.m b/examples/MotorBraking.m new file mode 100644 index 0000000000000000000000000000000000000000..47b6f2cb1e999dcef377c0bda4fe43274007cd93 --- /dev/null +++ b/examples/MotorBraking.m @@ -0,0 +1,21 @@ +b = EV3('debug', 2); +b.connect('bt', 'serPort', '/dev/rfcomm0'); +m = b.motorA; +sl = b.motorB; + +m.tachoLimit = 1000; +m.brakeMode = 'Coast'; + +counts = []; +power = [60, 65, 70, 75, 80, 85, 90, 95, 100]; + +for i=1:length(power) + m.resetTachoCount; + m.reset; + m.power = power(i); + m.syncedStart(sl); + m.waitFor(); + counts = [counts, m.tachoCount]; + b.beep; + b.debug = false; +end \ No newline at end of file diff --git a/examples/distancePlotting.m b/examples/distancePlotting.m new file mode 100644 index 0000000000000000000000000000000000000000..416e8a5c4e5bc441cd3e8f7a778ba84b3106e248 --- /dev/null +++ b/examples/distancePlotting.m @@ -0,0 +1,39 @@ +b = EV3('debug', 0); +b.connect('usb'); + +s = b.sensor4; +if s.type ~= DeviceType.UltraSonic + error('Connect US-sensor to port 4'); +end +s.mode = DeviceMode.UltraSonic.DistCM; + +readings = []; +time = []; + +tic +hold on; +while toc < 20 + readings = [readings, s.value]; + time = [time, toc]; +% plot(time, readings) +% drawnow +end +hold off; + +fprintf('\nSensor readings per sec: %f\n', length(readings)/time(length(time))); + +b.disconnect(); + +%% Results +% Bluetooth +% debug=2, sensor-typ-kontrolle an: 7.5 Readings/sec % worst case +% debug=1, sensor-typ-kontrolle an: 9.3 readings/sec +% debug=0, sensor-typ-kontrolle an: 9.6 readings/sec +% debug=0, sensor-typ-kontrolle aus: 16.5 readings/sec +% debug=0, sensor-typ-kontrolle aus, plotting aus: 22.5 readings/sec % best case +% debug=0, sensor-typ-kontrolle an, plotting aus: 10 +% USB +% debug=2, sensor-typ-kontrolle an: 16.5 readings/sec % worst case +% debug=0, sensor-typ-kontrolle aus, plotting aus: 100 readings/sec % best case +% +% -> NOTIZ AN MICH: TYP KONTROLLE ÜBERDENKEN diff --git a/examples/exampleCommLayer.m b/examples/exampleCommLayer.m deleted file mode 100644 index 48f27d257c3a700483d1b3cca6cfd401532448a9..0000000000000000000000000000000000000000 --- a/examples/exampleCommLayer.m +++ /dev/null @@ -1,81 +0,0 @@ -% init the connection -disp('Connecting ... ') -% brick usb init -b = Brick('ioType','usb'); -% beep to indicate connection -b.beep(); -% print information -disp('===================================') -disp('EV3 example code') -disp('===================================') -disp('u - increase motor speed') -disp('d - decrease motor speed') -disp('t - start the motor') -disp('s - stop the motor') -disp('b - beep') -disp('o - output the tachometer') -disp('c - clear the tachometer') -disp('v - output the brick voltage') -disp('f - quit the program'); -disp('===================================') -% user intput -userIn = ''; -% motor power -motorPower = 10; -% set motor power and start -b.outputPower(0,Device.MotorA,motorPower) -b.outputStart(0,Device.MotorA) -while(~strcmp(userIn,'f')) - % get input - userIn = input('> Input(u,d,t,s,b,o,c,v,f): ','s'); - % increase speed - if (userIn == 'u') - motorPower = motorPower+10; - if motorPower >= 100 - motorPower = 100; - end - b.outputPower(0,Device.MotorA,motorPower) - disp(['> Motor Power: ' num2str(motorPower)]); - end - % decrease speed - if (userIn == 'd') - motorPower = motorPower-10; - if motorPower <= -100 - motorPower = -100; - end - b.outputPower(0,Device.MotorA,motorPower) - disp(['> Motor Power: ' num2str(motorPower)]); - end - % start the motor - if (userIn == 't') - b.outputStart(0,Device.MotorA) - disp('> Motor Started'); - end - % stop the motor - if (userIn == 's') - b.outputStop(0,Device.MotorA,0) - disp('> Motor Stopped'); - end - % beep test - if (userIn == 'b') - b.beep(); - disp('> Beep'); - end - % output the tachometer - if (userIn == 'o') - tacho = b.outputGetCount(0,Device.MotorA); - disp(['> Tachometer: ' num2str(tacho)]); - end - % clear the tachometer - if (userIn == 'c') - b.outputClrCount(0,Device.MotorA) - disp('> Cleared tacho'); - end - % voltage output - if (userIn == 'v') - v = b.uiReadVbatt; - disp(['> Brick voltage: ' num2str(v) 'V']); - end -end -% delete the brick object -delete(b) \ No newline at end of file diff --git a/examples/testConnSpeed.m b/examples/testConnSpeed.m index ea2a6df794a50bcb74e2fa5bdaff8146c55440ed..c0a3630279bac23f1d202085d1eb2910e65ea9de 100644 --- a/examples/testConnSpeed.m +++ b/examples/testConnSpeed.m @@ -13,21 +13,22 @@ function testConnSpeed(ioType, serPort, it, withResponse) b = EV3(); if strcmp(ioType, 'bt') - b.connect('ioType', ioType, 'serPort', serPort); + b.connect(ioType, 'serPort', serPort); else - b.connect('ioType', 'usb'); + b.connect('usb'); end m = b.motorB; m.setProperties('power', 10, 'limitMode', 'Time', 'limitValue', 15000); - m.tachoCount = 0; + m.resetTachoCount(); if withResponse for i = 0:it-1 m.start(); t = 0; tic; - while m.isRunning() + pause(0.5); + while m.isRunning t = [t; toc]; end diff --git a/examples/testSyncMotor.m b/examples/testSyncMotor.m deleted file mode 100644 index c683d61aca989f4c0be657726c0305ae20813e14..0000000000000000000000000000000000000000 --- a/examples/testSyncMotor.m +++ /dev/null @@ -1,38 +0,0 @@ -% Connect with physical brick. If battery charge less than 10% abort. Otherwise, -% create a SyncMotor-object for ports A & B and set some propeties. -% Turn speed regulation on motors A & B on in order to be able to read speed values later. -% ('help Motor.speed' for a little more info) -% Pause program until both motors are connected and then start them. -% As long as they run, print out current speed of both motors. -% Finally, disconnect. - -clear all; - -b = EV3(); -b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm1'); - -if b.batteryValue < 10 - fprintf('Battery charge too low.'); - b.disconnect(); -else - s = b.coupleMotors('AB', 'useMotorParams', 0); - s.setProperties('Power', 50, 'speedRegulation', 'on', ... - 'limitMode', 'Time', 'turnRatio', 50); - s.limitValue = 5000; - s.brakeMode = 'Coast'; - b.motorA.speedRegulation = 'on'; - b.motorB.speedRegulation = 'on'; - s.mode = DeviceMode.Motor.Rotations; - while ~s.motorAtPort - s.update(); - pause(0.5); - end - s.start(); - while s.isRunning() - fprintf('Speed on A: %d\n',b.motorA.speed); - fprintf('Speed on B: %d\n',b.motorB.speed); - pause(0.1); - end - s.disconnect(); s.delete(); - b.disconnect(); b.delete(); -end diff --git a/examples/testToolbox.m b/examples/testToolbox.m index c955069e00c5ecff539abdbe2cca1fae6e9a6adc..a04346e0a81a98aa306ccd4318fc04d05b0225b8 100644 --- a/examples/testToolbox.m +++ b/examples/testToolbox.m @@ -1,24 +1,15 @@ -b = EV3('debug', 1); -b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); - +b = EV3(); +b.connect('bt', 'serPort', '/dev/rfcomm0'); +b.sensor3.mode = DeviceMode.Color.Col; +b.sensor4.mode = DeviceMode.UltraSonic.DistCM; tic; t = 0; -while 1 - if b.motorA.motorAtPort && b.motorB.motorAtPort - if b.sensor3.sensorAtPort && b.sensor4.sensorAtPort - if b.sensor3.type == DeviceType.Color - b.sensor3.value - break; - end - end - end - b.update(); - t = [t; toc]; -end -b.motorA.setProperties('power', 50, 'speedRegulation', 'on', 'smoothStart', 10, 'limitMode', 'time', 'limitValue', 3000); +b.motorA.setProperties('power', 50, 'speedRegulation', 'on', 'smoothStart', 10, 'limitMode', ... + 'Time', 'limitValue', 3000); b.motorA.start(); +pause(0.5); while b.motorA.isRunning() b.sensor3.value b.sensor4.value @@ -27,14 +18,25 @@ end b.motorB.power = 50; b.motorB.limitValue = 4*360; +b.motorB.start(); b.motorB.waitFor(); +b.beep(); -b.sensor3.value -b.motorA.tachoCount -b.sensor3.reset -b.motorA.tachoCount +b.motorA.speedRegulation = false; +pause(1); +b.motorA.syncedStart(b.motorB, 'turnRatio', 200); +b.motorA.waitFor(); + +for i=1:10 + b.sensor3.value + b.motorA.tachoCount + b.sensor3.reset + b.motorA.tachoCount +end b.beep +b.beep + b.disconnect -b.delete +b.delete \ No newline at end of file diff --git a/libhidapi-libusb.la~ b/libhidapi-libusb.la~ deleted file mode 100755 index fb6a07b073cade47ecca6f3bc8f2d5d1a627f5b5..0000000000000000000000000000000000000000 --- a/libhidapi-libusb.la~ +++ /dev/null @@ -1,41 +0,0 @@ -# libhidapi-libusb.la - a libtool library file -# Generated by libtool (GNU libtool) 2.4.2 Debian-2.4.2-1.7ubuntu1 -# -# Please DO NOT delete this file! -# It is necessary for linking the library. - -# The name that we can dlopen(3). -dlname='libhidapi-libusb.so.0' - -# Names of this library. -library_names='libhidapi-libusb.so.0.0.0 libhidapi-libusb.so.0 libhidapi-libusb.so' - -# The name of the static archive. -old_library='libhidapi-libusb.a' - -# Linker flags that can not go in dependency_libs. -inherited_linker_flags=' -pthread' - -# Libraries that this one depends upon. -dependency_libs=' -lrt -lusb-1.0' - -# Names of additional weak libraries provided by this library -weak_library_names='' - -# Version information for libhidapi-libusb. -current=0 -age=0 -revision=0 - -# Is this an already installed library? -installed=yes - -# Should we warn about portability when linking against -modules? -shouldnotlink=no - -# Files to dlopen/dlpreopen -dlopen='' -dlpreopen='' - -# Directory that this library needs to be installed in: -libdir='/usr/local/MATLAB/R2013b/toolbox/EV3/lib' diff --git a/source/+DeviceMode/Error.m b/source/+DeviceMode/Default.m similarity index 52% rename from source/+DeviceMode/Error.m rename to source/+DeviceMode/Default.m index 244ea15b1858b236b79684e979a2354732ad702b..1efa576db09f94be29fde9dcae251bb0d076abeb 100644 --- a/source/+DeviceMode/Error.m +++ b/source/+DeviceMode/Default.m @@ -1,6 +1,4 @@ -classdef Error < uint8 - %Error Test - +classdef Default < uint8 enumeration Undefined (0) end diff --git a/source/Brick.m b/source/CommunicationInterface.m similarity index 95% rename from source/Brick.m rename to source/CommunicationInterface.m index 6927b8a298b77dacd7bd1a517597063c3418efbf..18bca6c83d0eca00caf62e2d609c66f9743454fb 100644 --- a/source/Brick.m +++ b/source/CommunicationInterface.m @@ -99,31 +99,37 @@ % b = Brick('ioType','bt','serPort','/dev/rfcomm0') -classdef Brick < handle +classdef CommunicationInterface < handle properties - % Connection handle - conn; % Debug debug; + end + + properties (SetAccess = 'private') % IO connection type ioType; % Bluetooth brick device name - btDevice; + btDevice = ''; % Bluetooth brick communication channel - btChannel; + btChannel = 0; % Wifi brick IP address - wfAddr; + wfAddr = ''; % Wifi brick TCP port - wfPort; + wfPort = ''; % Brick serial number - wfSN; + wfSN = ''; % Bluetooth serial port serPort; end - + + properties (Hidden, Access = 'private') + % Connection handle + conn; + end + methods - function brick = Brick(varargin) + function commInterface = CommunicationInterface(varargin) % Brick.Brick Create a Brick object % % b = Brick(OPTIONS) is an object that represents a connection @@ -153,53 +159,49 @@ classdef Brick < handle % - For instrBrickIO (bluetooth) % b = Brick('ioType','instrbt','btDevice','EV3','btChannel',1) - % Init the properties - opt.debug = 0; - opt.btDevice = 'EV3'; - opt.btChannel = 1; - opt.wfAddr = '192.168.1.104'; - opt.wfPort = 5555; - opt.wfSN = '0016533dbaf5'; - opt.ioType = 'usb'; - opt.serPort = '/dev/rfcomm0'; - - % Read in the options - opt = tb_optparse(opt, varargin); - brick.debug = opt.debug; - brick.ioType = opt.ioType; + commInterface.setProperties(varargin{:}); +% % Init the properties +% opt.debug = 0; +% opt.btDevice = 'EV3'; +% opt.btChannel = 1; +% opt.wfAddr = '192.168.1.104'; +% opt.wfPort = 5555; +% opt.wfSN = '0016533dbaf5'; +% opt.ioType = 'usb'; +% opt.serPort = '/dev/rfcomm0'; +% +% % Read in the options +% opt = tb_optparse(opt, varargin); +% commInterface.debug = opt.debug; +% commInterface.ioType = opt.ioType; + try - if(strcmp(opt.ioType,'usb')) % USB - brick.conn = usbBrickIO(brick.debug); - elseif(strcmp(opt.ioType,'wifi')) % WiFi - brick.wfAddr = opt.wfAddr; - brick.wfPort = opt.wfPort; - brick.wfSN = opt.wfSN; - brick.conn = wfBrickIO(brick.debug,brick.wfAddr,brick.wfPort,brick.wfSN); - elseif(strcmp(opt.ioType,'bt')) % Bluetooth - brick.serPort = opt.serPort; - brick.conn = btBrickIO(brick.debug,brick.serPort); - elseif(strcmp(opt.ioType,'instrwifi')) % Instrumentation and Control: WiFi - brick.wfAddr = opt.wfAddr; - brick.wfPort = opt.wfPort; - brick.wfSN = opt.wfSN; - brick.conn = instrBrickIO(brick.debug,'wf',brick.wfAddr,brick.wfPort,brick.wfSN); - elseif(strcmp(opt.ioType,'instrbt')) % Instrumentation and Control: Bluetooth - brick.btDevice = opt.btDevice; - brick.btChannel = opt.btChannel; - brick.conn = instrBrickIO(brick.debug,'bt',brick.btDevice,brick.btChannel); + if(strcmp(commInterface.ioType,'usb')) % USB + commInterface.conn = usbBrickIO(commInterface.debug); + elseif(strcmp(commInterface.ioType,'wifi')) % WiFi + commInterface.wfAddr = opt.wfAddr; + commInterface.wfPort = opt.wfPort; + commInterface.wfSN = opt.wfSN; + commInterface.conn = wfBrickIO(commInterface.debug,commInterface.wfAddr,commInterface.wfPort,commInterface.wfSN); + elseif(strcmp(commInterface.ioType,'bt')) % Bluetooth +% commInterface.serPort = opt.serPort; + commInterface.conn = btBrickIO(commInterface.debug,commInterface.serPort); + elseif(strcmp(commInterface.ioType,'instrwifi')) % Instrumentation and Control: WiFi + commInterface.wfAddr = opt.wfAddr; + commInterface.wfPort = opt.wfPort; + commInterface.wfSN = opt.wfSN; + commInterface.conn = instrBrickIO(commInterface.debug,'wf',commInterface.wfAddr,commInterface.wfPort,commInterface.wfSN); + elseif(strcmp(commInterface.ioType,'instrbt')) % Instrumentation and Control: Bluetooth + commInterface.btDevice = opt.btDevice; + commInterface.btChannel = opt.btChannel; + commInterface.conn = instrBrickIO(commInterface.debug,'bt',commInterface.btDevice,commInterface.btChannel); end catch ME - brick.conn = []; + commInterface.conn = []; rethrow(ME); end end - function set.debug(brick, debug) - % If debug is set in this layer, also set BrickIO.debug in lower layer - brick.debug = debug; - brick.conn.debug = debug; - end - function delete(brick) % Brick.delete Delete the Brick object % @@ -208,7 +210,41 @@ classdef Brick < handle if isa(brick.conn, 'handle') && isvalid(brick.conn) brick.conn.delete(); end - end + end + + function set.debug(brick, debug) + % If debug is set in this layer, also set BrickIO.debug in lower layer + brick.debug = debug; + brick.conn.debug = debug; + end + + function setProperties(brick, varargin) + p = inputParser(); + p.KeepUnmatched = true; + + % Set default values + defaultIOType = 'usb'; + defaultSerPort = '/dev/rfcomm0'; + defaultDebug = false; + + % Define anonymous function that will return whether given value in varargin is valid + %validTypes = ; + checkIOType = @(x) ismember(x, {'usb', 'bt'}); + checkDebug = @(x) isBool(x); + + % Add parameters + p.addRequired('ioType', checkIOType); + p.addOptional('serPort', defaultSerPort); + p.addOptional('debug', defaultDebug, checkDebug); + + % Parse input... + p.parse(varargin{:}); + + % Set properties + brick.ioType = p.Results.ioType; + brick.serPort = p.Results.serPort; + brick.debug = p.Results.debug; + end function send(brick, cmd) % Brick.send Send data to the brick @@ -230,7 +266,7 @@ classdef Brick < handle % a delay, some commands may even be bypassed. % (Maybe too many commands screw up the brick's internal command queue?..) % Temporary workaround: Wait 5ms after each sent packet. - pause(0.005); + % pause(0.005); % Verbose output if brick.debug > 0 @@ -629,7 +665,6 @@ classdef Brick < handle % % Example:: % [type,mode] = b.inputDeviceTypeMode(0,SensorPort.Sensor1) - cmd = Command(); cmd.addHeaderDirectReply(42,2,0); cmd.opINPUT_DEVICE_GET_TYPEMODE(layer,no,0,1); @@ -1316,7 +1351,7 @@ classdef Brick < handle % - layer is the usb chain layer (usually 0) % - NOS is a bit field representing output 1 to 4 (0x01, 0x02, 0x04, 0x08) % - pol is the polarity [-1,0,1], -1 makes the motor run - % backward, 1 makes the motor run forward, 0 makes the motor + % backwards, 1 makes the motor run forwards, 0 makes the motor % run the opposite direction when starting next time % % Example:: diff --git a/source/ConnectionType.m b/source/ConnectionType.m index df8a552fb4c5040ee230d52adf4937202b1c4bcc..c474df67a2a975f89951d90c2f2e86317dc8e9c8 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/DeviceMode.m b/source/DeviceMode.m index f131a060edec8a0c7ff551a236a5022f2e6b2a58..146666fcae37a1cfad442d497edc0bbca9a104f9 100644 --- a/source/DeviceMode.m +++ b/source/DeviceMode.m @@ -35,7 +35,7 @@ function mode = DeviceMode(type, modeNo) case DeviceType.InfraRed mode = DeviceMode.InfraRed(modeNo); otherwise - mode = DeviceMode.Error(0); % Need to think about this... + mode = DeviceMode.Default.Undefined; % Need to think about this... end catch error('MATLAB:RWTHMindstormsEV3:noclass:DeviceMode:invalidInputValue',... diff --git a/source/EV3.m b/source/EV3.m index 2c9032c15b3cc1cf37b0db909e742e64203424cc..ec1a8c08dac5b71e7b49c2429afbd70ee6ca6dc4 100644 --- a/source/EV3.m +++ b/source/EV3.m @@ -7,82 +7,107 @@ 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? - % brick - 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 = 0; % Is virtual brick currently connected to physical brick? - brick = 0; % Brick object from sub-layer class Brick -> used as interface to the - % physical brick. + %isConnected - Virtual brick connected to physical one? + isConnected = false; %% Motors and Sensors @@ -97,14 +122,19 @@ classdef EV3 < handle sensor4; end + properties (Access = 'private') + %commInterface - Interface to communication layer + commInterface = 0; + 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 @@ -113,99 +143,99 @@ classdef EV3 < handle ev3.setProperties(varargin{:}); - ev3.motorA = Motor('A', 'Debug', ev3.debug); - ev3.motorB = Motor('B', 'Debug', ev3.debug); - ev3.motorC = Motor('C', 'Debug', ev3.debug); - ev3.motorD = Motor('D', 'Debug', ev3.debug); + ev3.motorA = Motor('A', 'Debug', ev3.debug>=1); + ev3.motorB = Motor('B', 'Debug', ev3.debug>=1); + ev3.motorC = Motor('C', 'Debug', ev3.debug>=1); + ev3.motorD = Motor('D', 'Debug', ev3.debug>=1); - ev3.sensor1 = Sensor('1', 'Debug', ev3.debug); - ev3.sensor2 = Sensor('2', 'Debug', ev3.debug); - ev3.sensor3 = Sensor('3', 'Debug', ev3.debug); - ev3.sensor4 = Sensor('4', 'Debug', ev3.debug); + ev3.sensor1 = Sensor('1', 'Debug', ev3.debug>=1); + ev3.sensor2 = Sensor('2', 'Debug', ev3.debug>=1); + ev3.sensor3 = Sensor('3', 'Debug', ev3.debug>=1); + ev3.sensor4 = Sensor('4', 'Debug', ev3.debug>=1); - ev3.init = 0; + ev3.init = false; end function delete(ev3) - if ev3.isConnected - ev3.disconnect(); - end + %delete Disconnects from physical brick and deletes this instance + + if ev3.isConnected + ev3.disconnect(); + end end + %% Connection function connect(ev3, varargin) %connect Connects EV3-object and its Motors and Sensors to physical brick. % % Arguments - % * 'beep', 'on'/'off': EV3 beeps if connection has been established. - % * 'ioType', 'bt'/'usb': Connection type - % * ['ioType', 'bt'] 'serPort', '/dev/rfcommx': Path to serial port + % * 'bt'/'usb': Connection type + % * 'serPort', '/dev/rfcommx': Path to serial port (if 'bt'), where x = 0...9 + % * 'beep', bool: EV3 beeps if connection has been established. % % Examples - % b = EV3(); b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); - % b = EV3(); b.connect('beep', 'on', ''ioType', 'usb'); + % b = EV3(); b.connect('bt', 'serPort', '/dev/rfcomm0'); + % b = EV3(); b.connect('usb', 'beep', 'on', ); % % Check connection if ev3.isConnected - if isBrickValid(ev3.brick) + if isCommInterfaceValid(ev3.commInterface) error('EV3::connect: Already connected.'); else warning(['EV3::connect: EV3.isConnected is set to ''True'', but ',... - 'brick handle is invalid. Deleting invalid handle and ' ,... + 'comm handle is invalid. Deleting invalid handle and ' ,... 'resetting EV3.isConnected now...']); - ev3.brick = 0; - ev3.isConnected = 0; + ev3.commInterface = 0; + ev3.isConnected = false; end end - % Interpret arguments - if nargin < 3 + if nargin < 2 error('EV3::connect: Wrong number of input arguments.'); end - beep = 0; - if strcmpi(varargin{1}, 'beep') - if strcmpi(varargin{2}, 'on') - beep = 1; - elseif ~strcmpi(varargin{2}, 'off') - % First argument was beep, but second one was neither 'true' nor - % 'false' - warning(['EV3::connect: The parameter after "beep" has to be either ',... - '"true" or "false"']); - error('EV3::connect: Wrong input arguments.'); + + 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 - varargin = varargin(3:end); % Cut beep-part off + else + beep = false; end % Try to connect try % Connect to physical brick - % -> Creating Brick-object implicitly establishes connection - ev3.brick = Brick('debug', ev3.debug>=2, varargin{1:end}); - ev3.isConnected = 1; + % -> 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.brick); - ev3.motorB.connect(ev3.brick); - ev3.motorC.connect(ev3.brick); - ev3.motorD.connect(ev3.brick); + 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.brick); - ev3.sensor2.connect(ev3.brick); - ev3.sensor3.connect(ev3.brick); - ev3.sensor4.connect(ev3.brick); + 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 = 0; - if isBrickValid(ev3.brick) && ev3.brick ~= 0 - ev3.brick.delete(); - ev3.brick = 0; + ev3.isConnected = false; + if isCommInterfaceValid(ev3.commInterface) && ev3.commInterface ~= 0 + ev3.commInterface.delete(); + ev3.commInterface = 0; end rethrow(ME); @@ -217,154 +247,42 @@ classdef EV3 < handle % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % % do stuff % b.disconnect(); % -% if ~ev3.isConnected -% error('EV3::connect: No brick connected.'); -% end - - % Disconnect motors - % -> set references to brick object to 0 + % Disconnect motors and sensors + % -> set references to comm handle to 0 ev3.motorA.disconnect(); ev3.motorB.disconnect(); ev3.motorC.disconnect(); ev3.motorD.disconnect(); - % Disconnect sensors - % -> set references to brick object to 0 ev3.sensor1.disconnect(); ev3.sensor2.disconnect(); ev3.sensor3.disconnect(); ev3.sensor4.disconnect(); - % Delete handle to brick object - if isBrickValid(ev3.brick) && ev3.brick ~= 0 - ev3.brick.delete(); + % Delete handle to comm-interface + if isCommInterfaceValid(ev3.commInterface) && ev3.commInterface ~= 0 + ev3.commInterface.delete(); end - ev3.brick = 0; + ev3.commInterface = 0; - ev3.isConnected = 0; + ev3.isConnected = false; end - function update(ev3) - %update Updates all Motors and Sensors to current status of their corresponding ports. - % - % Example - % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); - % % Connect some new motors and sensors, disconnect some others (in 'real life') - % b.update(); - % % Now, virtual brick knows about all new devices and commands can be sent to - % % them - % + %% Device functions + function stopAllMotors(ev3) + %stopAllMotors Sends a stop-command to all motor-ports if ~ev3.isConnected - error('EV3::update: No brick connected.'); - end - - ev3.motorA.update(); - ev3.motorB.update(); - ev3.motorC.update(); - ev3.motorD.update(); - - ev3.sensor1.update(); - ev3.sensor2.update(); - ev3.sensor3.update(); - ev3.sensor4.update(); - end - - %% System functions - function syncMotor = coupleMotors(ev3, varargin) - %coupleMotors Creates and connects a SyncMotor-object using two chosen Motor-objects. - % - % Notes - % * Right now only possible if connection to Brick has been established. - % - % Arguments - % * port ('AB','BA', ...): Motors to 'couple' - % * 'useMotorParams', 0/1/'on'/'off'/'true'/'false': Use first motor's or set own params - % * varargin: other Properties for Motor/SyncMotor (see - % (Sync-)Motor::setProperties(motor, varargin)) - % - % Example - % b = EV3(); b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); - % sync = b.coupleMotors('AB'); % Couple motors A and B using parameters of - % % Motor-object motorA. - % sync2 = b.coupleMotors('CD', 'useMotorParams', 0, 'power', 50, 'debug', 'on'); - % % Couple motors C and D with new object being - % % initialized with default resp. given parameters. - % - - if ~ev3.isConnected - error('EV3::update: No brick connected.'); - end - - % Check input arguments - p = inputParser(); - p.KeepUnmatched = 1; - p.addRequired('port', @(x) isPortValid('SyncMotor', x)); - p.addOptional('useMotorParams', 1, @(x) isBool(x)); - p.parse(varargin{:}); - - useMotorParams = str2bool(p.Results.useMotorParams); - if strcmpi(p.Results.port, 'AB') - motor1 = ev3.motorA; - motor2 = ev3.motorB; - elseif strcmpi(p.Results.port, 'BA') - motor1 = ev3.motorB; - motor2 = ev3.motorA; - elseif strcmpi(p.Results.port, 'AC') - motor1 = ev3.motorA; - motor2 = ev3.motorC; - elseif strcmpi(p.Results.port, 'CA') - motor1 = ev3.motorC; - motor2 = ev3.motorA; - elseif strcmpi(p.Results.port, 'AD') - motor1 = ev3.motorA; - motor2 = ev3.motorD; - elseif strcmpi(p.Results.port, 'DA') - motor1 = ev3.motorD; - motor2 = ev3.motorA; - elseif strcmpi(p.Results.port, 'BC') - motor1 = ev3.motorB; - motor2 = ev3.motorC; - elseif strcmpi(p.Results.port, 'CB') - motor1 = ev3.motorC; - motor2 = ev3.motorB; - elseif strcmpi(p.Results.port, 'BD') - motor1 = ev3.motorB; - motor2 = ev3.motorD; - elseif strcmpi(p.Results.port, 'DB') - motor1 = ev3.motorD; - motor2 = ev3.motorB; - elseif strcmpi(p.Results.port, 'CD') - motor1 = ev3.motorC; - motor2 = ev3.motorD; - elseif strcmpi(p.Results.port, 'DC') - motor1 = ev3.motorD; - motor2 = ev3.motorC; - end - - % Create SyncMotor-object with parameters of motor1 and given parameters - if useMotorParams - syncMotor = SyncMotor(motor1, motor2, p.Results.port, ... - 'Power', motor1.power, ... - 'SpeedRegulation', motor1.speedRegulation, ... - 'BrakeMode', motor1.brakeMode, ... - 'LimitMode', motor1.limitMode, ... - 'LimitValue', motor1.limitValue, ... - 'SmoothStart', motor1.smoothStart, ... - 'SmoothStop', motor1.smoothStop, ... - 'Debug', motor1.debug); - else - syncMotor = SyncMotor(motor1, motor2, p.Results.port, varargin{4:end}); + stopAllMotors(['EV3::beep: Brick-Object not connected physical brick. ',... + 'You have to call ev3.connect(...) first!']); end - % Connect SyncMotor-object to brick handle. - syncMotor.connect(ev3.brick); + ev3.commInterface.outputStopAll(); end %% Sound functions @@ -372,11 +290,11 @@ classdef EV3 < handle %beep Plays a 'beep' tone on brick. % % Notes - % * This equals playTone(10, 1000, 100). (Wraps the same opCode in comm-layer) + % * This equals playTone(10, 1000, 100) (Wraps the same opCode in comm-layer) % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % b.beep(); % @@ -385,7 +303,7 @@ classdef EV3 < handle 'You have to call ev3.connect(...) first!']); end - ev3.brick.beep(); + ev3.commInterface.beep(); if ev3.debug fprintf('(DEBUG) EV3::beep: Called beep on brick\n'); @@ -398,11 +316,11 @@ classdef EV3 < handle % Arguments % * volume (0...100) % * frequency (250...10000) - % * duration (in milliseconds) + % * duration (>0, in milliseconds) % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % b.playTone(50, 5000, 1000); % Plays tone with 50% volume and 5000Hz for 1 % % second. % @@ -412,7 +330,7 @@ classdef EV3 < handle 'You have to call ev3.connect(...) first!']); end - ev3.brick.soundPlayTone(volume, frequency, duration); + ev3.commInterface.soundPlayTone(volume, frequency, duration); if ev3.debug fprintf('(DEBUG) EV3::beep: Called soundPlayTone on brick\n'); @@ -424,7 +342,7 @@ classdef EV3 < handle % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % b.playTone(10,100,100000000); % Accidentally given wrong tone duration. % b.stopTone(); % Stops tone immediately. % @@ -434,7 +352,7 @@ classdef EV3 < handle 'You have to call ev3.connect(...) first!']); end - ev3.brick.soundStopTone(); + ev3.commInterface.soundStopTone(); if ev3.debug fprintf('(DEBUG) EV3::beep: Called soundStopTone on brick\n'); @@ -445,11 +363,11 @@ classdef EV3 < handle %tonePlayed Tests if tone is currently played. % % Output - % * status: Returns 0 if a tone is being played, 1 if not. + % * status: True for a tone being played % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % b.playTone(10, 100, 1000); % pause(0.5); % b.tonePlayed() -> Outputs 1 to console. @@ -460,7 +378,7 @@ classdef EV3 < handle 'You have to call ev3.connect(...) first!']); end - status = ev3.brick.soundTest; + status = ev3.commInterface.soundTest; if ev3.debug fprintf('(DEBUG) EV3::beep: Called soundTest on brick\n'); @@ -468,25 +386,17 @@ classdef EV3 < handle end %% Setter - function set.brick(ev3, brick) - % Check if brick is valid and set ev3.brick if it is. - - if ~isBrickValid(brick) - error('EV3::set.brick: Handle to Brick-object not valid.'); + function set.commInterface(ev3, comm) + if ~isCommInterfaceValid(comm) + error('EV3::set.commInterface: Handle to Brick-object not valid.'); else - ev3.brick = brick; + ev3.commInterface = comm; end 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; @@ -494,24 +404,14 @@ 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 - if ischar(debug) - ev3.debug = str2bool(debug); - else - ev3.debug = debug; - end + ev3.debug = str2bool(debug); - if ev3.isConnected() - ev3.brick.debug = (ev3.debug >= 2); + if ev3.isConnected + ev3.commInterface.debug = (ev3.debug >= 2); end end @@ -524,16 +424,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; @@ -576,25 +478,22 @@ 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') - bat = ev3.brick.uiReadLbatt(); + bat = ev3.commInterface.uiReadLbatt(); if ev3.debug fprintf('(DEBUG) EV3::getBattery: Called uiReadLBatt.\n'); end elseif strcmpi(ev3.batteryMode, 'Voltage') - bat = ev3.brick.uiReadVbatt(); + bat = ev3.commInterface.uiReadVbatt(); if ev3.debug fprintf('(DEBUG) EV3::getBattery: Called uiReadVBatt.\n'); end - else - warning('EV3:getBattery: Mode has to be Percentage or Voltage!'); - error('EV3::getBattery: Mode not valid.'); end end end diff --git a/source/Motor.m b/source/Motor.m index 86257623e871f8fc8b14ead2371e50c78002c391..e4d195262433d897297e3448fd09f9333c2aa1d8 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] + % -> Any numeric in [0, limitValue] smoothStart; %smoothStop - Degrees/Time for how far/long the motor should smoothly stop (depends on limitMode) - % -> Any integer or double in [0, tachoLimit/2] + % -> Any numeric in [0, limitValue] 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- Degrees/Time for how far/long the motor should run (depends on limitMode) + % -> Any numeric >= 0 (in ms, if limitMode = 'Time') limitValue; - %brakeMode - Mode for braking - % -> 'Brake' / 'Coast' - brakeMode; % Mode for braking: 'Brake' or 'Coast' - %limitMode - Mode for motor limit % -> 'Tacho' / 'Time' limitMode; - %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 limitValue 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 @@ -344,100 +173,267 @@ classdef Motor < handle % 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 + % Check connection and if motor is already running + 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 has been started synced with another, and it stopped 'itself' (when + % using a tacholimit), the sync cache has to be deleted (in general, syncedStop + % would do so) + if motor.isSynced + delete(motor.findprop('syncCache')); end + % Trigger warning if power = 0 (as it still sets the motor internally in a + % 'running' mode if motor.power == 0 warning('Motor::start: Motor starting with power=0.'); end + % Call right function in commInterface depending on limitValue and limitMode if motor.limitValue==0 - if motor.changed || motor.limitSetToZero - % See 2nd note - motor.stop(); + 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 motor.speedRegulation - motor.brick.outputStepSpeed(0, motor.port_, motor.power,... + motor.commInterface.outputStepSpeed(0, motor.port, motor.power,... motor.smoothStart, motor.limitValue, 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.commInterface.outputStepPower(0, motor.port, motor.power,... motor.smoothStart, motor.limitValue, 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') if motor.speedRegulation - motor.brick.outputTimeSpeed(0, motor.port_, motor.power,... + motor.commInterface.outputTimeSpeed(0, motor.port, motor.power,... motor.smoothStart, motor.limitValue, 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.commInterface.outputTimePower(0, motor.port, motor.power,... motor.smoothStart, motor.limitValue, 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.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, + % limitValue, 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 - motor.brick.outputStop(0, motor.port_, motor.brakeMode_); + % 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.limitValue= motor.limitValue; + syncMotor.brakeMode = motor.brakeMode; + syncMotor.power = motor.power; + + if strcmpi(motor.limitMode, 'Tacho') + motor.commInterface.outputStepSync(0, motor.port+syncMotor.port, ... + motor.power, turnRatio, ... + motor.limitValue, 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.limitMode, 'Time') + motor.commInterface.outputTimeSync(0, motor.port+syncMotor.port, ... + motor.power, turnRatio, ... + motor.limitValue, 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 @@ -455,69 +451,49 @@ classdef Motor < handle % outputReady returns in less than a second, another while-loop iterates until % the motor has stopped, this time using motor.isRunning() (this only works as % long as not both OutputTest and OutputReady are buggy). + % * Workaround: Poll isRunning (which itself return (speed>0)) until it is false + % -> No need to check if motor is connected as speed correctly + % returns 0 if it's not % - if ~motor.isConnected - error(['Motor::waitFor: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort - 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.']); - 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.']); + if ~motor.connectedToBrick + error(['Motor::waitFor: Motor-Object not connected to comm handle.',... + 'You have to call motor.connect(commInterface) first!']); end - running = motor.brick.outputTest(0, motor.port_); - - if motor.debug - fprintf('(DEBUG) Motor::isRunning: Called outputReady on Port %s\n', motor.port); + pause(0.1); + while motor.isRunning + pause(0.03); end +% elseif ~motor.limitValue +% 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) @@ -526,102 +502,67 @@ classdef Motor < handle % motor is running with a tacho limit, internally it uses another counter than the % one read by tachoCount. This internal tacho count needs to be reset if you % 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 +571,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 +581,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.limitValue) && steps>motor.limitValue + warning(['Motor::set.smoothStart: Smooth start steps are greater than ',... + 'limitValue.']); end motor.smoothStart = steps; @@ -662,33 +597,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.limitValue) && steps>motor.limitValue + error(['Motor::set.smoothStop: Smooth stop steps are greater than ',... + 'limitValue.']); 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; @@ -698,7 +619,7 @@ classdef Motor < handle function set.limitMode(motor, limitMode) validModes = {'Time', 'Tacho'}; - if ~ischar(limitMode) || ~any(validatestring(limitMode, validModes)) + if ~ischar(limitMode) || ~ismember(limitMode, validModes) error('Motor::set.limitMode: Given parameter is not a valid limit mode.'); else motor.limitMode = limitMode; @@ -712,111 +633,101 @@ classdef Motor < handle 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; + if limitValue==0 && motor.limitValue~=0 + motor.sendPowerOnNextStart = true; + motor.limitSetToZero = true; + motor.sendPowerOnSet = true; + elseif limitValue~=0 && motor.limitValue==0 + motor.sendPowerOnSet = false; end end - motor.limitValue = limitValue; + + motor.limitValue= limitValue; end - function set.brick(motor, brick) - if ~isBrickValid(brick) - error('Motor::set.brick: Handle to brick not valid.'); - else - motor.brick = brick; + function set.port(motor, port) + try + motor.port = str2PortParam(class(motor), port); + catch ME + error('Motor::set.port: Given parameter is not valid port string.'); 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.commInterface(motor, comm) + if ~isCommInterfaceValid(comm) + error('Motor::set.commInterface: Handle to commInterface not valid.'); + end + + motor.commInterface = comm; + end + + 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, limitValue] + % * 'smoothStop', numeric in [0, limitValue] + % * '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 + % * 'limitValue', numeric > 0 + % * 'power', numeric in [-100,100] % * 'batteryMode', 'Voltage'/'Percentage' % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); - % b.motorA.setProperties('debug', 'on', 'power', 50, 'limit', 720, 'speedRegulation', 'on'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); + % b.motorA.setProperties('debug', 'on', 'power', 50, 'limitValue', 720, 'speedRegulation', 'on'); % % Instead of: b.motorA.debug = 'on'; % % b.motorA.power = 50; - % % b.motorA.limit = 720; + % % b.motorA.limitValue = 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; 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('limitMode', defaultLimitMode); + p.addOptional('limitValue', defaultLimit); + p.addOptional('power', defaultPower); + p.addOptional('smoothStart', defaultSmoothStart); + p.addOptional('smoothStop', defaultSmoothStop); % Parse... p.parse(varargin{:}); @@ -825,51 +736,71 @@ classdef Motor < handle if motor.init motor.port = p.Results.port; end - motor.limitValue = p.Results.limitValue; + 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.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; + + motor.sendPowerOnSet = false; + motor.power = p.Results.power; + motor.sendPowerOnSet = true; 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 + cnt = motor.getTachoCount(); + if isnan(cnt) + warning('Motor::get.tachoCount: Could not detect motor at port %s.', ... + port2str('Motor', motor.port)); + cnt = 0; + 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 + speed = motor.getSpeed(); + if isnan(speed) + warning('Motor::get.currentSpeed: Could not detect motor at port %s.', ... + port2str('Motor', motor.port)); + speed = 0; + 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 +811,9 @@ 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 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 +822,189 @@ 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() - warning(['Motor::setPower: Can''t set power if motor is running with a ',... - 'tacholimit. This would mess up the internal tacho state.']); - return; + assert(motor.limitValue==0); + + if motor.speedRegulation + motor.commInterface.outputSpeed(0, motor.port, power); + + if motor.debug + fprintf('(DEBUG) Motor::setPower: Called outputSpeed on Port %s\n', port2str('Motor', motor.port)); + end else - if motor.speedRegulation - motor.brick.outputSpeed(0, motor.port_, power); - - if motor.debug - fprintf('(DEBUG) Motor::setPower: Called outputSpeed on Port %s\n', motor.port); - end - else - motor.brick.outputPower(0, motor.port_, power); - - if motor.debug - fprintf('(DEBUG) Motor::setPower: Called outputPower on Port %s\n', motor.port); - end + motor.commInterface.outputPower(0, motor.port, power); + + if motor.debug + fprintf('(DEBUG) Motor::setPower: Called outputPower on Port %s\n', port2str('Motor', motor.port)); end end - motor.changed = 0; + motor.sendPowerOnNextStart = false; end - function setMode(motor, mode) - if ~motor.isConnected || ~motor.motorAtPort - return; + function setMode(motor, mode) %% DEPRECATED + 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 - % sets the mode. + 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!']); 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!']); end - if ~motor.speedRegulation - warning('Motor::getSpeed: Speed only valid with speed regulation turned on.'); + speed = motor.commInterface.inputReadSI(0, motor.portInput, DeviceMode.Motor.Speed); + + if motor.debug + fprintf('(DEBUG) Motor::getSpeed: Called inputReadSI on Port %s\n', port2str('Motor', motor.port)); end - speed = motor.brick.inputReadSI(0, motor.portInput, DeviceMode.Motor.Speed); + end + + function running = getMotorStatus(motor) + %getMotorStatus 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.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::getSpeed: Called inputReadSI on Port %s\n', motor.port); + fprintf('(DEBUG) Motor::isRunning: Called outputReady 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 c4e5386f79441d413f023b0fa51ee485b645f793..a25cf54630c46cbd0ffa6c3d5ba9132111fb636a 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 f794ae8663cfdca07813e7aacc0e91dc47abcef2..bf2c60e690c67d752a83b3581b0e69c3535cf40c 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. @@ -27,7 +24,7 @@ classdef Sensor < handle % % b = EV3(); % b = EV3('debug', 'on', 'batteryMode', 'Voltage'); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % b.sensor1.mode = DeviceMode.Touch.Bumps; % while value < 10 % pause(0.1); @@ -40,226 +37,120 @@ classdef Sensor < handle % Signature % Author: Tim Stadtmann % Date: 2016/05/20 + % Updated: 2016/08/15 properties % Standard properties to be set by user + %mode - Sensor mode in which the value will be read + % -> DeviceMode.[...] (except for DeviceMode.Motor.[...]) + mode; + %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') debug; - - %mode - Sensor mode in which the value will be read - % -> DeviceMode.[...] (except for DeviceMode.Motor.[...]) - mode; end - properties (Dependent) % Parameters to be read directly from physical brick - value % Value read from sensor - end - - properties (SetAccess = 'private') % Read-only properties that are set internally or in init-phase - %sensorAtPort - Is physical sensor actually connected to port? - % This property will be changed by Sensor.update() if changes have happened on the - % physical brick, depending on status, type and mode values. - % See also STATUS, TYPE, MODE, UPDATE - sensorAtPort = 0; - - %brick - Brick object from sublayer class Brick -> used as interface to the physical brick. - % See also BRICK - brick = 0; - + properties (SetAccess = 'private') %port - Sensor port % This is only the string representation of the sensor port to work with. - % Internally, SensorPort-enums are used. (This is a bit inconvenient but more - % consistent compared with Motor) + % Internally, SensorPort-enums are used. % -> '1' / '2' / '3' / '4' - port; + port; + end + + properties (Dependent) % Parameters to be read directly from physical brick + value; % Value read from sensor + type; % Physical sensor connected to this port? + end + + properties (Hidden, Access = 'private') % Hidden properties for internal use only + commInterface = 0; % Communication interface - %status - Connection status at port - % This property will be changed by Sensor.update() if changes have happened on the - % physical brick. - % See also CONNECTIONTYPE, UPDATE - status = DeviceMode.Error.Undefined; + %% Miscallenous flags - %type - Device type at port - % This property will be changed by Sensor.update() if changes have happened on the - % physical brick. - % See also DEVICEMODE.ERROR, UPDATE - type = DeviceMode.Error.Undefined; + connectedToBrick = false; % Does (virtual) sensor-object have a valid brick-handle? + init = true; % Indicates 'init-phase' (True as long as constructor is running) end - properties (Hidden, Access = 'private') % Hidden properties for internal use only - init = 1; % Is set to zero after initial 'creating' phase is done. - isConnected = 0; % Does (virtual) sensor-object have a valid brick-handle? - - %% Equivalent for Sensor.port in enumeration SensorPort - % Port is an actual parameter on the physical brick. To avoid using string - % comparisons each time it is used, the corresponding value (i.e. '1' -> SensorPort.Sensor1) - % to the given strings is saved (hidden from the user). - port_; + properties (Hidden, Dependent, Access = 'private') + physicalSensorConnected; % Physical sensor connected to this port? end methods % Standard methods %% Constructor function sensor = Sensor(varargin) sensor.setProperties(varargin{1:end}); - sensor.init = 0; - end - - %% Connection - function connect(sensor,brick) - %connect Connects Sensor-object to physical brick - if sensor.isConnected - if isBrickValid(sensor.brick) - error('Sensor::connect: Sensor-Object already has a brick handle.'); - else - warning(['Sensor::connect: Sensor.isConnected is set to ''True'', but ',... - 'brick handle is invalid. Deleting invalid handle and ' ,... - 'resetting Sensor.isConnected now...']); - - sensor.brick = 0; - sensor.isConnected = 0; - - error('Sensor::connect: Aborted connection.'); - end - end - - sensor.brick = brick; - sensor.isConnected = 1; - - if sensor.debug - fprintf('(DEBUG) Sensor-Object connected to brick handle.\n'); - end - - sensor.update(); - end - - function disconnect(sensor) - %disconnect Disconnects Sensor-object from physical brick -% if ~sensor.isConnected -% error('Sensor::disconnect: No brick connected.'); -% end - - sensor.brick = 0; % Note: actual deleting is done in EV3::disconnect. - sensor.isConnected = 0; - sensor.sensorAtPort = 0; - - sensor.status = DeviceMode.Error.Undefined; - sensor.type = DeviceMode.Error.Undefined; - end - - function update(sensor) - %update Updates Sensor-object to current status at its port. - if ~sensor.isConnected - error(['Sensor::update: Sensor-Object not connected to brick handle.',... - 'You have to call sensor.connect(brick) first!']); - end - - oldMode = sensor.mode; - - sensor.status = sensor.getStatus(); - [sensor.type, newMode] = sensor.getTypeMode(); - - if strcmp(class(oldMode),class(newMode)) && oldMode~=newMode - if ~strcmp(class(oldMode), 'DeviceMode.Error') && ... - ~strcmp(class(newMode), 'DeviceMode.Error') - %warning(['Sensor::update: Physical sensor''s mode was not ',... - % 'the specified one. Changing...']); - - sensor.setMode(oldMode); - sensor.mode = oldMode; - end - else - sensor.mode = newMode; - end - - validStatus = [ConnectionType.NXTColor, ConnectionType.NXTDumb, ConnectionType.NXTIIC, ... - ConnectionType.InputDumb, ConnectionType.InputUART]; - validTypes = [DeviceType.NXTTouch, DeviceType.NXTColor, ... - DeviceType.NXTLight, DeviceType.NXTSound, ... - DeviceType.NXTTemperature, DeviceType.NXTUltraSonic, ... - DeviceType.Color, DeviceType.Gyro, DeviceType.InfraRed, ... - DeviceType.Touch, DeviceType.UltraSonic]; - - sensor.sensorAtPort = 1; - if ~ismember(sensor.status, validStatus) || ~ismember(sensor.type, validTypes) || ... - strcmp(class(sensor.mode), 'DeviceMode.Error') - sensor.sensorAtPort = 0; - end + sensor.init = false; end %% Brick functions function reset(sensor) %reset Resets value on sensor % Note: This clears ALL the sensors right now, no other Op-Code available... :( - if ~sensor.isConnected - error(['Sensor::reset: Sensor-Object not connected to brick handle.',... - 'You have to call sensor.connect(brick) first!']); - elseif ~sensor.sensorAtPort - error('Sensor::reset: No physical sensor connected to Port %s.',... - sensor.port); + if ~sensor.connectedToBrick + error(['Sensor::reset: Sensor-Object not connected to comm handle.',... + 'You have to call sensor.connect(commInterface) first!']); + elseif ~sensor.physicalSensorConnected + error('Sensor::reset: No physical sensor connected to Port %d.',... + sensor.port+1); end warning(['Sensor::reset: Current version of reset resets ALL devices, that is, ',... 'all motor tacho counts and all other sensor counters!']); - sensor.brick.inputDeviceClrAll(0); + sensor.commInterface.inputDeviceClrAll(0); if sensor.debug - fprintf('(DEBUG) Sensor::reset: Called inputReadSI on Port %s.\n',... - sensor.port); + fprintf('(DEBUG) Sensor::reset: Called inputReadSI on Port %d.\n',... + sensor.port+1); end end %% Setter - function set.brick(sensor, brick) - if ~isBrickValid(brick) - error('Sensor::set.brick: Handle to brick not valid.'); - else - sensor.brick = brick; - end - end - - function set.port(sensor, port) - if ~isPortValid(class(sensor),port) - error('Sensor::set.port: Given port is not a valid port.'); + function set.mode(sensor, mode) + if strcmp(class(mode),'DeviceMode.Default') && ~sensor.physicalSensorConnected + sensor.mode = mode; + return; end - if ischar(port) - sensor.port = port; - sensor.port_ = str2PortParam(class(sensor), port); + type = sensor.type; + if ~isModeValid(mode, type) + error('Sensor::set.mode: Invalid sensor mode.'); else - error('Sensor::set.port: Port has to be a string.'); + sensor.mode = mode; + + if ~strcmp(class(mode),'DeviceMode.Default') && sensor.connectedToBrick + try + sensor.setMode(mode); % Update physical brick's mode parameter + catch + % Ignore + end + end end end - + function set.debug(sensor, debug) % Check if debug is valid and set sensor.debug if it is. if ~isBool(debug) error('Sensor::set.debug: Given parameter is not a bool.'); end - if ischar(debug) - sensor.debug = str2bool(debug); + sensor.debug = str2bool(debug); + end + + function set.port(sensor, port) + if ~isPortStrValid(class(sensor),port) + error('Sensor::set.port: Given port is not a valid port.'); else - sensor.debug = debug; + sensor.port = str2PortParam(class(sensor), port); end end - - function set.mode(sensor, mode) - if strcmp(class(mode),'DeviceMode.Error') && ~sensor.sensorAtPort - sensor.mode = mode; - return; - end - - if strcmp(class(mode), 'uint8') || strcmp(class(mode), 'double') - mode = DeviceMode(sensor.type, uint8(mode)); - end - - if ~isModeValid(mode, sensor.type) - error('Sensor::set.mode: Sensor mode is not valid.'); + + function set.commInterface(sensor, comm) + if ~isCommInterfaceValid(comm) + error('Sensor::set.commInterface: Handle to commInterface not valid.'); else - sensor.mode = mode; - sensor.setMode(mode); + sensor.commInterface = comm; end end @@ -272,7 +163,7 @@ classdef Sensor < handle % % Example % b = EV3(); - % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); + % b.connect('bt', 'serPort', '/dev/rfcomm0'); % b.sensor1.setProperties('debug', 'on', 'mode', DeviceMode.Color.Ambient); % % Instead of: b.sensor1.debug = 'on'; % % b.sensor1.mode = DeviceMode.Color.Ambient; @@ -283,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; @@ -309,15 +200,37 @@ 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; - return; - end + value = 0; + defaultMode = -1; +% if ~isModeValid(sensor.mode, sensor.type) +% warning('Sensor::get.value: Cannot read sensor values in current mode.'); +% return; +% elseif strcmp(class(sensor.mode), 'DeviceMode.Default') +% defaultMode = 0; +% end - value = sensor.getValue(); + if sensor.connectedToBrick + value = sensor.getValue(defaultMode); + if isnan(value) + warning('Sensor::get.value: Could not detect sensor at port %d.', ... + sensor.port+1); + value = 0; + end + end + end + + function conn = get.physicalSensorConnected(sensor) + currentType = sensor.type; + conn = (currentType<DeviceType.Unknown && ... + (currentType~=DeviceType.MediumMotor && currentType~=DeviceType.LargeMotor)); + end + + function sensorType = get.type(sensor) + if sensor.connectedToBrick + [sensorType, ~] = sensor.getTypeMode(); + else + sensorType = DeviceType.Unknown; + end end %% Display @@ -330,20 +243,24 @@ 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 - function val = getValue(sensor) + function val = getValue(sensor, varargin) %getValue Reads value from sensor % % Notes @@ -351,15 +268,27 @@ 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 ~isempty(varargin) + defaultMode = varargin{1}; + + % 5 is numerically highest available number of modes for a sensor(NXT Color) + if ~isnumeric(defaultMode) || defaultMode > 5 + error('Sensor::getValue: Invalid mode'); + end + else + defaultMode = -1; + end + + if ~sensor.connectedToBrick + error(['Sensor::getValue: Sensor-Object not connected to comm handle.',... + 'You have to call sensor.connect(commInterface) first!']); end - val = sensor.brick.inputReadSI(0, sensor.port_, sensor.mode); + if defaultMode ~= -1 + val = sensor.commInterface.inputReadSI(0, sensor.port, defaultMode); + else + val = sensor.commInterface.inputReadSI(0, sensor.port, sensor.mode); + end if strcmp(class(sensor.mode), 'DeviceMode.Color') if sensor.mode == DeviceMode.Color.Col @@ -367,50 +296,90 @@ classdef Sensor < handle end end + % See note if isnan(val) - pause(0.01); - val = sensor.brick.inputReadSI(0, sensor.port_, sensor.mode); - if isnan(val) - warning('Sensor::getValue: Brick returned invalid value (NaN). Try again...'); - end + val = sensor.commInterface.inputReadSI(0, sensor.port, sensor.mode); + if sensor.debug + fprintf('(DEBUG) Sensor::getValue: Called inputReadSI on Port %d.\n',... + sensor.port+1); + end end if sensor.debug fprintf('(DEBUG) Sensor::getValue: Called inputReadSI on Port %d.\n',... - sensor.port); + sensor.port+1); end end function status = getStatus(sensor) - if ~sensor.isConnected - error(['Sensor::getStatus: Sensor-Object not connected to brick handle.',... - 'You have to call sensor.connect(brick) first!']); + if ~sensor.connectedToBrick + error(['Sensor::getStatus: Sensor-Object not connected to comm handle.',... + 'You have to call sensor.connect(commInterface) first!']); end - statusNo = sensor.brick.inputDeviceGetConnection(0, sensor.port_); + statusNo = sensor.commInterface.inputDeviceGetConnection(0, sensor.port); status = ConnectionType(statusNo); if sensor.debug - fprintf('(DEBUG) Sensor::getStatus: Called inputDeviceGetConnection on Port %s\n',... - sensor.port); + fprintf(['(DEBUG) Sensor::getStatus: Called inputDeviceGetConnection on ' ,... + 'Port %d.\n'], sensor.port+1); end end function [type,mode] = getTypeMode(sensor) - if ~sensor.isConnected - error(['Sensor::getTypeMode: Sensor-Object not connected to brick handle.',... - 'You have to call sensor.connect(brick) first!']); + if ~sensor.connectedToBrick + error(['Sensor::getTypeMode: Sensor-Object not connected to comm handle.',... + 'You have to call sensor.connect(commInterface) first!']); end - [typeNo,modeNo] = sensor.brick.inputDeviceGetTypeMode(0, sensor.port_); + [typeNo,modeNo] = sensor.commInterface.inputDeviceGetTypeMode(0, sensor.port); type = DeviceType(typeNo); - mode = DeviceMode(type,modeNo); + try + mode = DeviceMode(type,modeNo); + catch ME + mode = DeviceMode.Default.Undefined; + end if sensor.debug - fprintf('(DEBUG) Sensor::getTypeMode: Called inputDeviceGetConnection on Port %s\n',... - sensor.port); + fprintf(['(DEBUG) Sensor::getStatus: Called inputDeviceGetConnection on ' ,... + 'Port %d.\n'], sensor.port+1); end end end + + methods (Access = {?EV3}) + function connect(sensor,commInterface) + %connect Connects Sensor-object to physical brick + + if sensor.connectedToBrick + if isCommInterfaceValid(sensor.commInterface) + error('Sensor::connect: Sensor-Object already has a comm handle.'); + else + warning(['Sensor::connect: Sensor.connectedToBrick is set to ''True'', but ',... + 'comm handle is invalid. Deleting invalid handle and ' ,... + 'resetting Sensor.connectedToBrick now...']); + + sensor.commInterface = 0; + sensor.connectedToBrick = false; + + error('Sensor::connect: Disconnected due to internal error.'); + end + end + + sensor.commInterface = commInterface; + sensor.connectedToBrick = true; + + if sensor.debug + fprintf('(DEBUG) Sensor-Object connected to comm handle.\n'); + end + end + + function disconnect(sensor) + %disconnect Disconnects Sensor-object from physical brick + + sensor.commInterface = 0; % Note: actual deleting is done in EV3::disconnect. + sensor.connectedToBrick = false; + end + end end diff --git a/source/SyncMotor.m b/source/_SyncMotor.m similarity index 62% rename from source/SyncMotor.m rename to source/_SyncMotor.m index eef93e5845ab4d9c6943b976bc32453779901d00..8ef432218c55fee29bc13d5a682fdfea2ad8a146 100644 --- a/source/SyncMotor.m +++ b/source/_SyncMotor.m @@ -1,12 +1,12 @@ classdef SyncMotor < Motor % High-level class to synchronize two motors. % - % This class in combination with Motor.m is supposed to make the silmutaneous use of two + % This class in combination with Motor.m is supposed to make the silmutaneous use of two % motors easier. In order to have functionality like physically % blocking one of the two motors, while they are running, makes the other one stop, or - % starting and stopping two motors with one command, this class overrides some methods from + % starting and stopping two motors with one command, this class overrides some methods from % Motor to deliver a convenient interface for two Motor-objects at once. - % This proves especially useful when e.g. building some kind of LEGO-'car' that should run straight + % This proves especially useful when e.g. building some kind of LEGO-'car' that should run straight % on, and stop and start without delay on either motor. % % Properties: @@ -19,14 +19,14 @@ classdef SyncMotor < Motor % * An instance of this class affects the motor objects of an EV3 instance only when using % update(). Therefore, the type, status and mode parameters will be set on both the % motor objects AND the syncMotor object. Other parameters won't be affected. - % * Polling dependent values that cannot be read from both motors at once - % (tachoCount, speed) will only return the resp. value of the first given motor-object, - % instead of throwing an error. + % * Polling dependent values that cannot be read from both motors at once + % (tachoCount, speed) will only return the resp. value of the first given motor-object, + % instead of throwing an error. % % Example % % This small example should only roughly demonstrate how to work with synced motors. - % % Establish connection, couple motors A & B, set properties on synced motor and wait - % % until both motors are connected. After starting, output current speed every 100ms + % % Establish connection, couple motors A & B, set properties on synced motor and wait + % % until both motors are connected. After starting, output current speed every 100ms % % until motors are turned off. Then output current tachoCounts and disconnect. % % b = EV3(); @@ -59,7 +59,7 @@ classdef SyncMotor < Motor % Date: 2016/05/30 properties % Standard properties to be set by user - %turnRatio (Excerpt of c_output.c): Turn ratio is how tight you turn and to what direction + %turnRatio (Excerpt of c_output.c): Turn ratio is how tight you turn and to what direction % you turn (in [+-200]). % -> 0 value is moving straight forward % -> Negative values turn to the left @@ -80,8 +80,8 @@ classdef SyncMotor < Motor end properties (Hidden, Dependent, Access = 'protected') % Hidden, dependent properties for internal use only - portNo_sec; - portInput_sec; + portNo_sec; + portInput_sec; end methods % Standard methods @@ -100,7 +100,7 @@ classdef SyncMotor < Motor % SyncMotor is only counted as 'connected' if both synced physical motors are % connected. if motor.motor1.motorAtPort + motor.motor2.motorAtPort < 2 - motor.motorAtPort = 0; + motor.motorAtPort = 0; else motor.motorAtPort = 1; end @@ -108,7 +108,7 @@ classdef SyncMotor < Motor % Set parameters of SyncMotor-object randomly to the numerically 'higher' ones. if uint8(motor.motor1.status) > uint8(motor.motor2.status) motor.status = motor.motor1.status; - else + else motor.status = motor.motor2.status; end @@ -123,92 +123,92 @@ classdef SyncMotor < Motor else motor.mode = motor.motor2.mode; end - -% if ~motor.isConnected -% error(['Motor::update: Motor-Object not connected to brick handle.',... -% 'You have to call syncMotor.connect(brick) first!']); -% end -% -% % Get current status at both ports -% [status, status_sec] = motor.getStatus(); -% [type, type_sec, mode, mode_sec] = 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 motor1 valid? -% uint8(status_sec) >= uint8(ConnectionType.OutputDumb) && ... -% uint8(status) <= uint8(ConnectionType.OutputTacho) % Is status of motor2 valid? -% if (type == DeviceType.MediumMotor || type == DeviceType.LargeMotor) && ... % Is type of motor1 valid? -% (type_sec == DeviceType.MediumMotor || type_sec == DeviceType.LargeMotor) %Is type of motor2 valid? -% if strcmp(class(mode), 'DeviceMode.Motor') && ... % Is mode of motor1 valid? -% strcmp(class(mode_sec), 'DeviceMode.Motor') % Is mode of motor2 valid? -% motor.motorAtPort = 1; -% end -% end -% end -% -% % Set parameters of SyncMotor-object randomly to the numerically 'higher' ones. -% if uint8(status) > uint8(status_sec) -% motor.status = status; -% else -% motor.status = status_sec; -% end -% -% if uint8(type) > uint8(type_sec) -% motor.type = type; -% else -% motor.type = type_sec; -% end -% -% -% 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 -% if uint8(mode) > uint8(mode_sec) -% motor.mode = mode; -% else -% motor.mode = mode_sec; -% end + + % if ~motor.isConnected + % error(['Motor::update: Motor-Object not connected to brick handle.',... + % 'You have to call syncMotor.connect(brick) first!']); + % end + % + % % Get current status at both ports + % [status, status_sec] = motor.getStatus(); + % [type, type_sec, mode, mode_sec] = 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 motor1 valid? + % uint8(status_sec) >= uint8(ConnectionType.OutputDumb) && ... + % uint8(status) <= uint8(ConnectionType.OutputTacho) % Is status of motor2 valid? + % if (type == DeviceType.MediumMotor || type == DeviceType.LargeMotor) && ... % Is type of motor1 valid? + % (type_sec == DeviceType.MediumMotor || type_sec == DeviceType.LargeMotor) %Is type of motor2 valid? + % if strcmp(class(mode), 'DeviceMode.Motor') && ... % Is mode of motor1 valid? + % strcmp(class(mode_sec), 'DeviceMode.Motor') % Is mode of motor2 valid? + % motor.motorAtPort = 1; + % end + % end + % end + % + % % Set parameters of SyncMotor-object randomly to the numerically 'higher' ones. + % if uint8(status) > uint8(status_sec) + % motor.status = status; + % else + % motor.status = status_sec; + % end + % + % if uint8(type) > uint8(type_sec) + % motor.type = type; + % else + % motor.type = type_sec; + % end + % + % + % 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 + % if uint8(mode) > uint8(mode_sec) + % motor.mode = mode; + % else + % motor.mode = mode_sec; + % end end %% Brick functions function start(motor) if ~motor.isConnected error(['SyncMotor::start: Motor-Object not connected to brick handle. ',... - 'You have to call syncMotor.connect(brick) first!']); + 'You have to call syncMotor.connect(brick) first!']); elseif ~motor.motorAtPort error(['SyncMotor::start: One or more physical motors not connected to ',... - 'respective port.']); + 'respective port.']); end if motor.power == 0 - warning('Motor::start: Motor starting with power=0.'); + warning('Motor::start: Motor starting with power=0.'); end if strcmpi(motor.limitMode, 'Tacho') - motor.brick.outputStepSync(0, motor.port_, ... - motor.power, motor.turnRatio, ... - motor.limitValue, motor.brakeMode_); - if motor.debug - fprintf('(DEBUG) SyncMotor::start: Called outputStepSync on Port %s\n',... - motor.port); - end + motor.brick.outputStepSync(0, motor.port_, ... + motor.power, motor.turnRatio, ... + motor.limitValue, motor.brakeMode_); + if motor.debug + fprintf('(DEBUG) SyncMotor::start: Called outputStepSync on Port %s\n',... + motor.port); + end elseif strcmpi(motor.limitMode, 'Time') motor.brick.outputTimeSync(0, motor.port_, ... - motor.power, motor.turnRatio, ... - motor.limitValue, motor.brakeMode_); - if motor.debug - fprintf('(DEBUG) SyncMotor::start: Called outputStepSync on Port %s\n',... - motor.port); - end + motor.power, motor.turnRatio, ... + motor.limitValue, motor.brakeMode_); + if motor.debug + fprintf('(DEBUG) SyncMotor::start: Called outputStepSync on Port %s\n',... + motor.port); + end end motor.motor1.changed = 1; motor.motor2.changed = 1; @@ -216,19 +216,19 @@ classdef SyncMotor < Motor function waitFor(motor) %waitFor Stops execution of program as long as motor is running with tacholimit. - % + % % See also: MOTOR.waitFor if ~motor.isConnected error(['Motor::waitFor: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); + 'You have to call motor.connect(brick) first!']); elseif ~motor.motorAtPort error('Motor::waitFor: No physical motor connected to Port %s',... - motor.port); + motor.port); elseif ~motor.limitValue error(['Motor::waitFor: Motor has no tacho limit. ' ,... - 'Can''t reliably determine whether it is running or not.']); - end + 'Can''t reliably determine whether it is running or not.']); + end tic; while 1 @@ -239,8 +239,8 @@ classdef SyncMotor < Motor t = toc; if t < 1 - while motor.isRunning() % If outputReady correctly returned in less - % than a second, isRunning should instantly send 0. + while motor.isRunning() % If outputReady correctly returned in less + % than a second, isRunning should instantly send 0. end end @@ -261,10 +261,10 @@ classdef SyncMotor < Motor if ~motor.isConnected error(['Motor::isRunning: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); + 'You have to call motor.connect(brick) first!']); elseif ~motor.motorAtPort error('Motor::isRunning: No physical motor connected to Port %s',... - motor.port); + motor.port); end running = motor.brick.outputTest(0, motor.port_); @@ -288,18 +288,18 @@ classdef SyncMotor < Motor syncMotor.turnRatio = turnRatio; end -% function set.keepSynchronized(syncMotor, keepSync) -% % Check if keepSync is a valid bool and set syncedMotor.keepSynchronized if it is. -% if ~isBool(keepSync) -% error('SyncedMotor::set.keepSynchronized: Given parameter is not a bool.'); -% end -% -% if ischar(keepSync) -% syncMotor.keepSynchronized = str2bool(keepSync); -% else -% syncMotor.keepSynchronized = keepSync; -% end -% end + % function set.keepSynchronized(syncMotor, keepSync) + % % Check if keepSync is a valid bool and set syncedMotor.keepSynchronized if it is. + % if ~isBool(keepSync) + % error('SyncedMotor::set.keepSynchronized: Given parameter is not a bool.'); + % end + % + % if ischar(keepSync) + % syncMotor.keepSynchronized = str2bool(keepSync); + % else + % syncMotor.keepSynchronized = keepSync; + % end + % end function set.motor1(syncMotor, motor1) % Check if motor1 is valid and set syncMotor.motor1 if it is. @@ -320,22 +320,22 @@ classdef SyncMotor < Motor syncMotor.motor2 = motor2; end end - + function setProperties(motor, varargin) %setProperties Sets multiple Motor properties at once using MATLAB's inputParser. % % Arguments % * 'motor1', instance of Motor-class % * 'motor2', instance of Motor-class - % * 'turnRatio', integer or double in [-200, 200] + % * 'turnRatio', integer or double in [-200, 200] % * all Motor.setProperties arguments % % Example - % b = EV3(); + % b = EV3(); % b.connect('ioType', 'bt', 'serPort', '/dev/rfcomm0'); % s = b.coupleMotors('AB', 'useMotorParams', 0); % s.setProperties('power', 70, 'debug', 'true', 'turnRatio', 100, 'brakeMode', 'Coast'); - % + % p = inputParser(); p.KeepUnmatched = 1; % Unmatched parameters are (probably) Motor-parameters. @@ -356,13 +356,13 @@ classdef SyncMotor < Motor % Parse... if motor.init - p.parse(varargin{1:2}, varargin{4:end}); % Skip 'port' which is only needed - % for Motor::setProperties. + p.parse(varargin{1:2}, varargin{4:end}); % Skip 'port' which is only needed + % for Motor::setProperties. else p.parse(varargin{:}); end - % Set properties + % Set properties if motor.init motor.motor1 = p.Results.motor1; motor.motor2 = p.Results.motor2; @@ -371,9 +371,9 @@ classdef SyncMotor < Motor % Set Motor-parameters, SyncMotor-parameters will be ignored. if motor.init - setProperties@Motor(motor, varargin{3:end}); % Skip 'motor1' and 'motor2' - % which are only needed for - % SyncMotor::setProperties + setProperties@Motor(motor, varargin{3:end}); % Skip 'motor1' and 'motor2' + % which are only needed for + % SyncMotor::setProperties else setProperties@Motor(motor, varargin{:}); end @@ -395,33 +395,33 @@ classdef SyncMotor < Motor return; end - % Save in Motor-objects that their power does not equal the physical Brick's power + % Save in Motor-objects that their power does not equal the physical Brick's power % anymore if ~motor.init % In init-phase, motors 1&2 have not been set yet - motor.motor1.changed = 1; + motor.motor1.changed = 1; motor.motor2.changed = 1; motor.changed = 0; end - + if motor.isRunning() warning(['SyncMotor::setPower: Can''t set power if two motors are running ',... - 'synchronized. Changes will be effective on next start.']); + 'synchronized. Changes will be effective on next start.']); return; end -% setPower@Motor(motor,power); + % setPower@Motor(motor,power); end function setMode(motor, mode) setMode@Motor(motor, mode); - motor.brick.inputReadSI(0, motor.portInput_sec, mode); % Reading a value implicitly - % sets the mode. - + motor.brick.inputReadSI(0, motor.portInput_sec, 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_sec)); - end + uint8(motor.portInput_sec)); + end % oldMode = motor.mode; % if oldMode~=newMode % if ~strcmp(class(oldMode), 'DeviceMode.Error') && ... @@ -444,7 +444,7 @@ classdef SyncMotor < Motor function [type,type_sec,mode,mode_sec] = getTypeMode(motor) if ~motor.isConnected error(['SyncMotor::getTypeMode: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); + 'You have to call motor.connect(brick) first!']); end [typeNo,modeNo] = motor.brick.inputDeviceGetTypeMode(0, motor.motor1.portInput); @@ -457,14 +457,14 @@ classdef SyncMotor < Motor if motor.debug fprintf(['(DEBUG) SyncMotor::getTypeMode: Called inputDeviceGetTypeMode on Ports ',... - '%s.\n'], motor.port); + '%s.\n'], motor.port); end end function [status, status_sec] = getStatus(motor) if ~motor.isConnected error(['SyncMotor::getStatus: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); + 'You have to call motor.connect(brick) first!']); end statusNo = motor.brick.inputDeviceGetConnection(0, motor.motor1.portInput); @@ -475,88 +475,90 @@ classdef SyncMotor < Motor if motor.debug fprintf('(DEBUG) SyncMotor::getStatus: Called inputDeviceGetConnection on Ports %s\n',... - motor.port); + motor.port); end end function cnt = getTachoCount(motor) warning(['SyncMotor::getTachoCount: Tacho count is not valid for SyncMotor! ',... - 'Return corresponding value of first motor...']); + 'Return corresponding value of first motor...']); cnt = getTachoCount@Motor(motor); -% if motor.mode == DeviceMode.Motor.Degrees -% cnt_sec = motor.brick.outputGetCount(0, motor.portNo_sec); -% if motor.debug -% fprintf(['(DEBUG) Motor::getTachoCount: Called outputGetCount on port ' ,... -% 'no %d\n'], motor.portNo_sec); -% end -% elseif motor.mode == DeviceMode.Motor.Rotations -% cnt_sec = motor.brick.inputReadSI(0, motor.portInput_sec, motor.mode); -% if motor.debug -% fprintf('(DEBUG) Motor::getSpeed: Called inputReadSI on input port %d\n', ... -% motor.portInput_sec); -% end -% end + % if motor.mode == DeviceMode.Motor.Degrees + % cnt_sec = motor.brick.outputGetCount(0, motor.portNo_sec); + % if motor.debug + % fprintf(['(DEBUG) Motor::getTachoCount: Called outputGetCount on port ' ,... + % 'no %d\n'], motor.portNo_sec); + % end + % elseif motor.mode == DeviceMode.Motor.Rotations + % cnt_sec = motor.brick.inputReadSI(0, motor.portInput_sec, motor.mode); + % if motor.debug + % fprintf('(DEBUG) Motor::getSpeed: Called inputReadSI on input port %d\n', ... + % motor.portInput_sec); + % end + % end end function speed = getSpeed(motor) warning(['SyncMotor::getSpeed: Speed is not a valid parameter of SyncMotor!',... - 'Return corresponding value of first motor...']); + 'Return corresponding value of first motor...']); speed = getSpeed@Motor(motor); -% speed_sec = motor.brick.inputReadSI(0, motor.portInput_sec, DeviceMode.Motor.Speed); -% if motor.debug -% fprintf('(DEBUG) Motor::getSpeed: Called inputReadSI on input port %d\n', ... -% motor.portInput_sec); -% end + % speed_sec = motor.brick.inputReadSI(0, motor.portInput_sec, DeviceMode.Motor.Speed); + % if motor.debug + % fprintf('(DEBUG) Motor::getSpeed: Called inputReadSI on input port %d\n', ... + % motor.portInput_sec); + % end end end methods (Hidden, Access = 'protected') % Wrapper for utility functions, hidden from user function portNo = getPortNo(motor) -% warning(['SyncMotor::getPortNo: Port number is not valid for SyncMotor!',... -% 'Return corresponding value of first motor...']); + % warning(['SyncMotor::getPortNo: Port number is not valid for SyncMotor!',... + % 'Return corresponding value of first motor...']); portNo = motor.motor1.portNo; -% if isempty(motor.port) -% error(['SyncMotor::portNo: This method should only be called AFTER ',... -% 'setting motor.port.']); -% end -% -% [~, portNo, ~, ~, ~] = str2PortParam(class(motor), motor.port); + % if isempty(motor.port) + % error(['SyncMotor::portNo: This method should only be called AFTER ',... + % 'setting motor.port.']); + % end + % + % [~, portNo, ~, ~, ~] = str2PortParam(class(motor), motor.port); end function portInput = getPortInput(motor) -% warning(['SyncMotor::getPortInput: Port input number is not valid for SyncMotor!',... -% 'Return corresponding value of first motor...']); + % warning(['SyncMotor::getPortInput: Port input number is not valid for SyncMotor!',... + % 'Return corresponding value of first motor...']); portInput = motor.motor1.portInput; -% if isempty(motor.port) -% error(['SyncMotor::portInput: This method should only be called AFTER ',... -% 'setting motor.port.']); -% end -% -% [~, ~, portInput, ~, ~] = str2PortParam(class(motor), motor.port); + % if isempty(motor.port) + % error(['SyncMotor::portInput: This method should only be called AFTER ',... + % 'setting motor.port.']); + % end + % + % [~, ~, portInput, ~, ~] = str2PortParam(class(motor), motor.port); end function portNo_sec = getPortNo_sec(motor) portNo_sec = motor.motor2.portNo; -% if isempty(motor.port) -% error(['SyncMotor::portNo: This method should only be called AFTER ',... -% 'setting motor.port.']); -% end -% -% [~, ~, ~, portNo_sec, ~] = str2PortParam(class(motor), motor.port); + % if isempty(motor.port) + % error(['SyncMotor::portNo: This method should only be called AFTER ',... + % 'setting motor.port.']); + % end + % + % [~, ~, ~, portNo_sec, ~] = str2PortParam(class(motor), motor.port); end function portInput_sec = getPortInput_sec(motor) portInput_sec = motor.motor2.portInput; -% if isempty(motor.port) -% error(['SyncMotor::portInput: This method should only be called AFTER ',... -% 'setting motor.port.']); -% end -% -% [~, ~, ~, ~, portInput_sec] = str2PortParam(class(motor), motor.port); + % if isempty(motor.port) + % error(['SyncMotor::portInput: This method should only be called AFTER ',... + % 'setting motor.port.']); + % end + % + % [~, ~, ~, ~, portInput_sec] = str2PortParam(class(motor), motor.port); end end end + + diff --git a/source/bitfield2input.m b/source/bitfield2input.m new file mode 100644 index 0000000000000000000000000000000000000000..2f59bb0151480c5a940484ff5c9fee9fdb8f48fc --- /dev/null +++ b/source/bitfield2input.m @@ -0,0 +1,10 @@ +function inputPort = bitfield2input(bitfield) +% Converts a motor-bitfield to motor-inputport-number(s). + [isSynced, ~, ~] = isSyncedBitfield(bitfield); + if isSynced + ports = bitfield2port(bitfield); + inputPort = [port2input(ports(1)), port2input(ports(2))]; + else + inputPort = port2input(bitfield2port(bitfield)); + end +end \ No newline at end of file diff --git a/source/bitfield2port.m b/source/bitfield2port.m index 1b5ab929ce9cf5c1f7abf484ce087e9965c40f3d..85f52b0f046d51cb9393d89ab85d4dbbe839d460 100644 --- a/source/bitfield2port.m +++ b/source/bitfield2port.m @@ -1,4 +1,9 @@ function port = bitfield2port(bitfield) -% Converts motorBitfield-enum to motorPort-enum. - port = MotorPort(log2(double(bitfield))); +% Converts motor-bitfield to motor-port-number(s). + [isSynced, b1, b2] = isSyncedBitfield(bitfield); + if isSynced + port = [b1-1, b2-1]; + else + port = uint8(log2(double(bitfield))); + end end \ No newline at end of file diff --git a/source/bool2str.m b/source/bool2str.m index e8200152f05a8a02974fa817506215797423fcc1..f397f84234c4cf30949d92ffb176ebe1dcd0065c 100644 --- a/source/bool2str.m +++ b/source/bool2str.m @@ -10,6 +10,10 @@ function bool = bool2str(inp) elseif inp~=0 error('bool2str: Not a valid boolean.'); end + elseif islogical(inp) + if inp==true + bool = 'on'; + end end end diff --git a/source/input2bitfield.m b/source/input2bitfield.m new file mode 100644 index 0000000000000000000000000000000000000000..cbc3d9e67dfbdd359bef99d856e52e2218db8e6f --- /dev/null +++ b/source/input2bitfield.m @@ -0,0 +1,8 @@ +function bitfield = input2bitfield(varargin) +% Converts motor-inputport-number(s) to motor-bitfield. + if nargin==1 + bitfield = port2bitfield(input2port(varargin{1})); + elseif nargin==2 + bitfield = port2bitfield(input2port(varargin{1}), input2port(varargin{2})); + end +end diff --git a/source/input2port.m b/source/input2port.m index 98bc1b230eec9984307e62bb8c74659e1c8ab3fe..4765c3add7bc160ab7c41ede5266defe74b7ee53 100644 --- a/source/input2port.m +++ b/source/input2port.m @@ -1,4 +1,4 @@ function port = input2port(input) -% Converts motorInput-enum to motorPort-enum. - port = MotorPort(input-16); +% Converts a motor-inputport-number to motor-port-number. + port = uint8(input)-16; end \ No newline at end of file diff --git a/source/isBool.m b/source/isBool.m index b7cbb7106c7c2a0eae7744387b2c9f68895536db..c1711a916d6a6a9f9ebe71b019ada707b9d20175 100644 --- a/source/isBool.m +++ b/source/isBool.m @@ -1,15 +1,11 @@ function isValid = isBool(inp) % Returns whether given boolean is valid or not. - isValid = 0; if ischar(inp) - if strcmpi(inp, 'on') || strcmpi(inp, 'true') ... - || strcmpi(inp, 'off') || strcmpi(inp, 'false') - isValid = 1; - end + isValid = strcmpi(inp, 'on') || strcmpi(inp, 'off'); elseif isnumeric(inp) - if inp==1 || inp==0 - isValid = 1; - end + isValid = inp==1 || inp==0; + else + isValid = islogical(inp); end end diff --git a/source/isBrickValid.m b/source/isCommInterfaceValid.m similarity index 54% rename from source/isBrickValid.m rename to source/isCommInterfaceValid.m index 24447ec6f346555416f553e895b6f00c334f8afd..77d7dc2195b732f57c11cbee25378b75f98ffb0f 100644 --- a/source/isBrickValid.m +++ b/source/isCommInterfaceValid.m @@ -1,10 +1,10 @@ -function isValid = isBrickValid(testBrick) +function isValid = isCommInterfaceValid(comm) % Returns whether given brick object is valid or not. isValid = 0; - if ~isempty(testBrick) + if ~isempty(comm) % The second case (after the '||') is allowed as a default value (think of it as a nullptr). - if (isa(testBrick, 'Brick') && testBrick.isvalid) || ... - (isnumeric(testBrick) && testBrick==0) + if (isa(comm, 'CommunicationInterface') && comm.isvalid) || ... + (isnumeric(comm) && comm==0) isValid = 1; end end diff --git a/source/isDeviceValid.m b/source/isDeviceValid.m new file mode 100644 index 0000000000000000000000000000000000000000..a0bcc933a8b351529de553c8ee818dfe9edd58b2 --- /dev/null +++ b/source/isDeviceValid.m @@ -0,0 +1,13 @@ +function isValid = isDeviceValid(deviceType, device) +% Returns whether given device object is valid or not. + isValid = 0; + try + if ~isempty(device) + if isa(device, deviceType) && device.isvalid + isValid = 1; + end + end + catch ME + % Ignore + end +end diff --git a/source/isModeValid.m b/source/isModeValid.m index ef88e572ea6a8519b795b3f424caacc014f28cf6..debc72a9bd4a9687e418b51490f00f387e743b6e 100644 --- a/source/isModeValid.m +++ b/source/isModeValid.m @@ -1,64 +1,66 @@ function isValid = isModeValid(mode, type) % Returns whether given mode is a valid mode in given type. - isValid = 1; + isValid = true; - if strcmp(class(mode), 'DeviceMode.Error') + if strcmp(class(mode), 'DeviceMode.Default') return; end switch type case DeviceType.NXTTouch if ~strcmp(class(mode), 'DeviceMode.NXTTouch') - isValid = 0; + isValid = false; end case DeviceType.NXTLight if ~strcmp(class(mode), 'DeviceMode.NXTLight') - isValid = 0; + isValid = false; end case DeviceType.NXTSound if ~strcmp(class(mode), 'DeviceMode.NXTSound') - isValid = 0; + isValid = false; end case DeviceType.NXTColor if ~strcmp(class(mode), 'DeviceMode.NXTColor') - isValid = 0; + isValid = false; end case DeviceType.NXTUltraSonic if ~strcmp(class(mode), 'DeviceMode.NXTUltraSonic') - isValid = 0; + isValid = false; end case DeviceType.NXTTemperature if ~strcmp(class(mode), 'DeviceMode.NXTTemperature') - isValid = 0; + isValid = false; end case DeviceType.LargeMotor if ~strcmp(class(mode), 'DeviceMode.Motor') - isValid = 0; + isValid = false; end case DeviceType.MediumMotor if ~strcmp(class(mode), 'DeviceMode.Motor') - isValid = 0; + isValid = false; end case DeviceType.Touch if ~strcmp(class(mode), 'DeviceMode.Touch') - isValid = 0; + isValid = false; end case DeviceType.Color if ~strcmp(class(mode), 'DeviceMode.Color') - isValid = 0; + isValid = false; end case DeviceType.UltraSonic if ~strcmp(class(mode), 'DeviceMode.UltraSonic') - isValid = 0; + isValid = false; end case DeviceType.Gyro if ~strcmp(class(mode), 'DeviceMode.Gyro') - isValid = 0; + isValid = false; end case DeviceType.InfraRed if ~strcmp(class(mode), 'DeviceMode.InfraRed') - isValid = 0; + isValid = false; end + otherwise + isValid = false; end end diff --git a/source/isMotorValid.m b/source/isMotorValid.m deleted file mode 100644 index a6451077e2014ab9b13ee6397b8869252b1ecbc3..0000000000000000000000000000000000000000 --- a/source/isMotorValid.m +++ /dev/null @@ -1,9 +0,0 @@ -function isValid = isMotorValid(motor) -% Returns whether given motor object is valid or not. - isValid = 0; - if ~isempty(motor) - if isa(motor, 'Motor') && motor.isvalid - isValid = 1; - end - end -end diff --git a/source/isPortEnumValid.m b/source/isPortEnumValid.m new file mode 100644 index 0000000000000000000000000000000000000000..009c995e2d6c120fa48bb4434f67f0c57b7fe9db --- /dev/null +++ b/source/isPortEnumValid.m @@ -0,0 +1,27 @@ +function isValid = isPortEnumValid(device, port) +% Returns whether given port-number is valid for given device + isValid = false; + + if ~ischar(device) + error(['isPortValid: First argument has to be a string (''Sensor'', ',... + '''Motor'' or ''SyncMotor'')']); + end + + try + if strcmpi(device, 'Motor') + if ~isa(port, 'MotorBitfield') + MotorBitfield(port); + end + isValid = true; % Otherwise, an error would have already been thrown + elseif strcmpi(device, 'Sensor') + if ~isa(port, 'SensorPort') + SensorPort(port); + end + isValid = true; % Otherwise, an error would have already been thrown + elseif strcmpi(device, 'SyncMotor') + isValid = isSyncedBitfield(port); + end + catch ME + % Ignore + end +end diff --git a/source/isPortStrValid.m b/source/isPortStrValid.m new file mode 100644 index 0000000000000000000000000000000000000000..4b2398bae51c87a99b612c9a7b41d8e55525c411 --- /dev/null +++ b/source/isPortStrValid.m @@ -0,0 +1,19 @@ +function isValid = isPortStrValid(device, port) +% Returns whether given port-number is valid for given device + if strcmpi(device, 'Motor') + validPorts = {'A', 'B', 'C', 'D'}; + elseif strcmpi(device, 'Sensor') + validPorts = {'1', '2', '3', '4'}; + elseif strcmpi(device, 'SyncMotor') + validPorts = {'AB', 'AC', 'AD', 'BA', 'BC', 'BD', ... + 'CA', 'CB', 'CD', 'DA', 'DB', 'DC'}; + else + error(['isPortValid: First argument has to be either ''Sensor'', ',... + '''Motor'' or ''SyncMotor''']); + end + + if ischar(port) + isValid = ismember(port, validPorts); + end +end + diff --git a/source/isPortValid.m b/source/isPortValid.m deleted file mode 100644 index b2050fe09d265ae4bf7cf994be2ad454b415ef23..0000000000000000000000000000000000000000 --- a/source/isPortValid.m +++ /dev/null @@ -1,36 +0,0 @@ -function isValid = isPortValid(device, port) -% Returns whether given port-string is valid or not. - isValid = 0; - - if ~ischar(device) - error(['isPortValid: First argument has to be a string (''Sensor'', ',... - '''Motor'' or ''SyncMotor'')']); - end - - if strcmpi(device, 'Motor') - validPorts = {'A', 'B', 'C', 'D'}; - elseif strcmpi(device, 'Sensor') - validPorts = {'1', '2', '3', '4'}; - elseif strcmpi(device, 'SyncMotor') - validPorts = {'AB', 'AC', 'AD', 'BA', 'BC', 'BD', ... - 'CA', 'CB', 'CD', 'DA', 'DB', 'DC'}; - else - error(['isPortValid: First argument has to be either ''Sensor'', ',... - '''Motor'' or ''SyncMotor''']); - end - - % Note: Both port2str() and validatestring() generate errors if the input - % argument is 'wrong'. This method here isn't supposed to do this, - % it's only supposed to return whether given port is valid or not. - try - if ~ischar(port) - port = port2str(device, port); - end - - if any(validatestring(port, validPorts)) - isValid = 1; - end - catch - return; - end -end diff --git a/source/isSensorValid.m b/source/isSensorValid.m deleted file mode 100644 index fb01ec7450066457e9698d622f0f1dbedccf00b4..0000000000000000000000000000000000000000 --- a/source/isSensorValid.m +++ /dev/null @@ -1,9 +0,0 @@ -function isValid = isSensorValid(sensor) -% Returns whether given sensor object is valid or not. - isValid = 0; - if ~isempty(sensor) - if isa(sensor, 'Sensor') && sensor.isvalid - isValid = 1; - end - end -end diff --git a/source/isSyncedBitfield.m b/source/isSyncedBitfield.m new file mode 100644 index 0000000000000000000000000000000000000000..08ae85cd8e4c6d92a420b0e3f4591125b084d0fd --- /dev/null +++ b/source/isSyncedBitfield.m @@ -0,0 +1,21 @@ +function [isSynced, bit1, bit2] = isSyncedBitfield(bitfield) +% A SyncMotor-port is the sum of the two ports to sync +% -> As motor ports are represented by bitfields, check how many bits are set (2 +% bits = 2 ports). +% -> Just checking 'port' against allowed values would have been too easy :) + bit1 = 0; bit2 = 0; + + setBits = []; + for i=1:4 + if bitget(bitfield, i) == 1 + setBits = [setBits, i]; + end + end + + isSynced = (bitfield<=15)&&(length(setBits)==2); + if isSynced + bit1 = setBits(1); + bit2 = setBits(2); + end +end + diff --git a/source/port2bitfield.m b/source/port2bitfield.m index b4e5de428fc1bd46ea7a41bd3c9a0745c49afca3..9f8d2c7400167d075ca76bca03ef29a91f2d94b6 100644 --- a/source/port2bitfield.m +++ b/source/port2bitfield.m @@ -1,8 +1,8 @@ function bitfield = port2bitfield(varargin) -% Converts a motorPort-enum to motorBitfield-enum. +% Converts motor-port-number(s) to motor-bitfield. if nargin==1 - bitfield = MotorBitfield(2 ^ varargin{1}); + bitfield = uint8(2 ^ varargin{1}); elseif nargin==2 - bitfield = MotorBitfield(2 ^ (varargin{1}+varargin{2})); + bitfield = uint8(2 ^varargin{1}+2^varargin{2}); end end diff --git a/source/port2input.m b/source/port2input.m index cd3bbadf8fdac156638db5df37d4df4e40a95a7d..58c3076dcfbcf7cf8d6c0879022fadba9bc3fbbc 100644 --- a/source/port2input.m +++ b/source/port2input.m @@ -1,4 +1,4 @@ function input = port2input(port) -% Converts a motorPort-enum to motorInput-enum. - input = MotorInput(port+16); +% Converts a motor-port-number to motor-inputport-number. + input = uint8(port)+16; end \ No newline at end of file diff --git a/source/port2str.m b/source/port2str.m index 696b990dac243c314e87182cc9bc481d2298d1af..048613d4287fe11a98eb2065fa914883e91dd814 100644 --- a/source/port2str.m +++ b/source/port2str.m @@ -1,72 +1,82 @@ -function port = port2str(device, inp) +function portStr = port2str(varargin) % Converts a port-enum to corresponding-string. - if ~ischar(device) - error(['port2str: First argument has to be a string (''Sensor'', ',... - '''Motor'' or ''SyncMotor'')']); + p = inputParser(); + + validDevices = {'Motor', 'Sensor', 'SyncMotor'}; + validPortTypes = {'Bitfield', 'PortNo', 'InputPortNo'}; + checkDevice = @(x) ismember(x, validDevices); + checkPortType = @(x) ismember(x, validPortTypes); + + p.addRequired('device', checkDevice); + p.addRequired('port'); + p.addOptional('portType', 'Bitfield', checkPortType); + + p.parse(varargin{:}); + + device = p.Results.device; + port = p.Results.port; + portType = p.Results.portType; + + if ~isPortEnumValid(device, port) + error('port2str: Given parameter is not a valid port-enum'); end if strcmpi(device, 'Motor') - if ~ischar(inp) - if inp == MotorBitfield.MotorA || inp == MotorPort.MotorA || ... - inp == MotorInput.MotorA - port = 'A'; - elseif inp == MotorBitfield.MotorB || inp == MotorPort.MotorB || ... - inp == MotorInput.MotorB - port = 'B'; - elseif inp == MotorBitfield.MotorC || inp == MotorPort.MotorC || ... - inp == MotorInput.MotorC - port = 'C'; - elseif inp == MotorBitfield.MotorD || inp == MotorPort.MotorD || ... - inp == MotorInput.MotorD - port = 'D'; - else - error('port2str: Given parameter is not a valid motor port.' ); + if strcmpi(portType, 'Bitfield') + if port == MotorBitfield.MotorA + portStr = 'A'; + elseif port == MotorBitfield.MotorB + portStr = 'B'; + elseif port == MotorBitfield.MotorC + portStr = 'C'; + elseif port == MotorBitfield.MotorD + portStr = 'D'; + end + elseif strcmpi(portType, 'PortNo') + if port == MotorPort.MotorA + portStr = 'A'; + elseif port == MotorPort.MotorB + portStr = 'B'; + elseif port == MotorPort.MotorC + portStr = 'C'; + elseif port == MotorPort.MotorD + portStr = 'D'; + end + elseif strcmpi(portType, 'InputPortNo') + if port == MotorInput.MotorA + portStr = 'A'; + elseif port == MotorInput.MotorB + portStr = 'B'; + elseif port == MotorInput.MotorC + portStr = 'C'; + elseif port == MotorInput.MotorD + portStr = 'D'; end - else - warning('port2str: Given parameter already is a string.'); - port = inp; end elseif strcmpi(device, 'Sensor') - if ~ischar(inp) - if inp == SensorPort.Sensor1 - port = '1'; - elseif inp == SensorPort.Sensor2 - port = '2'; - elseif inp == SensorPort.Sensor3 - port = '3'; - elseif inp == SensorPort.Sensor4 - port = '4'; - else - error('port2str: Given parameter is not a valid sensor port.' ); - end - else - warning('port2str: Given parameter already is a string.'); - port = inp; + if port == SensorPort.Sensor1 + portStr = '1'; + elseif port == SensorPort.Sensor2 + portStr = '2'; + elseif port == SensorPort.Sensor3 + portStr = '3'; + elseif port == SensorPort.Sensor4 + portStr = '4'; end elseif strcmpi(device, 'SyncMotor') - if ~ischar(inp) - switch inp - case MotorBitfield.MotorA+MotorBitfield.MotorB - port = 'AB'; - case MotorBitfield.MotorA+MotorBitfield.MotorC - port = 'AC'; - case MotorBitfield.MotorA+MotorBitfield.MotorD - port = 'AD'; - case MotorBitfield.MotorB+MotorBitfield.MotorC - port = 'BC'; - case MotorBitfield.MotorB+MotorBitfield.MotorD - port = 'BD'; - case MotorBitfield.MotorC+MotorBitfield.MotorD - port = 'CD'; - otherwise - error('port2str: Given parameter is not a valid motor port.' ); - end - else - warning('port2str: Given parameter already is a string.'); - port = inp; + switch port + case MotorBitfield.MotorA+MotorBitfield.MotorB + portStr = 'AB'; + case MotorBitfield.MotorA+MotorBitfield.MotorC + portStr = 'AC'; + case MotorBitfield.MotorA+MotorBitfield.MotorD + portStr = 'AD'; + case MotorBitfield.MotorB+MotorBitfield.MotorC + portStr = 'BC'; + case MotorBitfield.MotorB+MotorBitfield.MotorD + portStr = 'BD'; + case MotorBitfield.MotorC+MotorBitfield.MotorD + portStr = 'CD'; end - else - error(['port2str: First argument has to be either ''Sensor'', ',... - '''Motor'' or ''SyncMotor''']); end end \ No newline at end of file diff --git a/source/str2PortParam.m b/source/str2PortParam.m index 75fdb6d3eb5708f8e7350da4b14aad3a68b3a4e0..0f2a139b78b6e717f3fb9cadb7c7af676ed21163 100644 --- a/source/str2PortParam.m +++ b/source/str2PortParam.m @@ -34,109 +34,109 @@ function varargout = str2PortParam(device, port) if strcmpi(device, 'Motor') if strcmpi(port, 'A') - varargout{1} = uint8(MotorBitfield.MotorA); - varargout{2} = uint8(MotorPort.MotorA); - varargout{3} = uint8(MotorInput.MotorA); + varargout{1} = MotorBitfield.MotorA; + varargout{2} = MotorPort.MotorA; + varargout{3} = MotorInput.MotorA; elseif strcmpi(port, 'B') - varargout{1} = uint8(MotorBitfield.MotorB); - varargout{2} = uint8(MotorPort.MotorB); - varargout{3} = uint8(MotorInput.MotorB); + varargout{1} = MotorBitfield.MotorB; + varargout{2} = MotorPort.MotorB; + varargout{3} = MotorInput.MotorB; elseif strcmpi(port, 'C') - varargout{1} = uint8(MotorBitfield.MotorC); - varargout{2} = uint8(MotorPort.MotorC); - varargout{3} = uint8(MotorInput.MotorC); + varargout{1} = MotorBitfield.MotorC; + varargout{2} = MotorPort.MotorC; + varargout{3} = MotorInput.MotorC; elseif strcmpi(port, 'D') - varargout{1} = uint8(MotorBitfield.MotorD); - varargout{2} = uint8(MotorPort.MotorD); - varargout{3} = uint8(MotorInput.MotorD); + varargout{1} = MotorBitfield.MotorD; + varargout{2} = MotorPort.MotorD; + varargout{3} = MotorInput.MotorD; else error('str2PortParam: Given port is not a valid motor port.'); end elseif strcmpi(device, 'Sensor') if strcmpi(port, '1') - varargout{1} = uint8(SensorPort.Sensor1); + varargout{1} = SensorPort.Sensor1; elseif strcmpi(port, '2') - varargout{1} = uint8(SensorPort.Sensor2); + varargout{1} = SensorPort.Sensor2; elseif strcmpi(port, '3') - varargout{1} = uint8(SensorPort.Sensor3); + varargout{1} = SensorPort.Sensor3; elseif strcmpi(port, '4') - varargout{1} = uint8(SensorPort.Sensor4); + varargout{1} = SensorPort.Sensor4; else error('str2PortParam: Given parameter not a valid sensor port.'); end elseif strcmpi(device, 'SyncMotor') if strcmpi(port,'AB') varargout{1} = MotorBitfield.MotorA+MotorBitfield.MotorB; - varargout{2} = uint8(MotorPort.MotorA); - varargout{3} = uint8(MotorInput.MotorA); - varargout{4} = uint8(MotorPort.MotorB); - varargout{5} = uint8(MotorInput.MotorB); + varargout{2} = MotorPort.MotorA; + varargout{3} = MotorInput.MotorA; + varargout{4} = MotorPort.MotorB; + varargout{5} = MotorInput.MotorB; elseif strcmpi(port,'AC') varargout{1} = MotorBitfield.MotorA+MotorBitfield.MotorC; - varargout{2} = uint8(MotorPort.MotorA); - varargout{3} = uint8(MotorInput.MotorA); - varargout{4} = uint8(MotorPort.MotorC); - varargout{5} = uint8(MotorInput.MotorC); + varargout{2} = MotorPort.MotorA; + varargout{3} = MotorInput.MotorA; + varargout{4} = MotorPort.MotorC; + varargout{5} = MotorInput.MotorC; elseif strcmpi(port,'AD') varargout{1} = MotorBitfield.MotorA+MotorBitfield.MotorD; - varargout{2} = uint8(MotorPort.MotorA); - varargout{3} = uint8(MotorInput.MotorA); - varargout{4} = uint8(MotorPort.MotorD); - varargout{5} = uint8(MotorInput.MotorD); + varargout{2} = MotorPort.MotorA; + varargout{3} = MotorInput.MotorA; + varargout{4} = MotorPort.MotorD; + varargout{5} = MotorInput.MotorD; elseif strcmpi(port,'BA') varargout{1} = MotorBitfield.MotorB+MotorBitfield.MotorA; - varargout{2} = uint8(MotorPort.MotorB); - varargout{3} = uint8(MotorInput.MotorB); - varargout{4} = uint8(MotorPort.MotorA); - varargout{5} = uint8(MotorInput.MotorA); + varargout{2} = MotorPort.MotorB; + varargout{3} = MotorInput.MotorB; + varargout{4} = MotorPort.MotorA; + varargout{5} = MotorInput.MotorA; elseif strcmpi(port,'BC') varargout{1} = MotorBitfield.MotorB+MotorBitfield.MotorC; - varargout{2} = uint8(MotorPort.MotorB); - varargout{3} = uint8(MotorInput.MotorB); - varargout{4} = uint8(MotorPort.MotorC); - varargout{5} = uint8(MotorInput.MotorC); + varargout{2} = MotorPort.MotorB; + varargout{3} = MotorInput.MotorB; + varargout{4} = MotorPort.MotorC; + varargout{5} = MotorInput.MotorC; elseif strcmpi(port,'BD') varargout{1} = MotorBitfield.MotorB+MotorBitfield.MotorD; - varargout{2} = uint8(MotorPort.MotorB); - varargout{3} = uint8(MotorInput.MotorB); - varargout{4} = uint8(MotorPort.MotorD); - varargout{5} = uint8(MotorInput.MotorD); + varargout{2} = MotorPort.MotorB; + varargout{3} = MotorInput.MotorB; + varargout{4} = MotorPort.MotorD; + varargout{5} = MotorInput.MotorD; elseif strcmpi(port,'CA') varargout{1} = MotorBitfield.MotorC+MotorBitfield.MotorA; - varargout{2} = uint8(MotorPort.MotorC); - varargout{3} = uint8(MotorInput.MotorC); - varargout{4} = uint8(MotorPort.MotorA); - varargout{5} = uint8(MotorInput.MotorA); + varargout{2} = MotorPort.MotorC; + varargout{3} = MotorInput.MotorC; + varargout{4} = MotorPort.MotorA; + varargout{5} = MotorInput.MotorA; elseif strcmpi(port,'CB') varargout{1} = MotorBitfield.MotorC+MotorBitfield.MotorB; - varargout{2} = uint8(MotorPort.MotorC); - varargout{3} = uint8(MotorInput.MotorC); - varargout{4} = uint8(MotorPort.MotorB); - varargout{5} = uint8(MotorInput.MotorB); + varargout{2} = MotorPort.MotorC; + varargout{3} = MotorInput.MotorC; + varargout{4} = MotorPort.MotorB; + varargout{5} = MotorInput.MotorB; elseif strcmpi(port,'CD') varargout{1} = MotorBitfield.MotorC+MotorBitfield.MotorD; - varargout{2} = uint8(MotorPort.MotorC); - varargout{3} = uint8(MotorInput.MotorC); - varargout{4} = uint8(MotorPort.MotorD); - varargout{5} = uint8(MotorInput.MotorD); + varargout{2} = MotorPort.MotorC; + varargout{3} = MotorInput.MotorC; + varargout{4} = MotorPort.MotorD; + varargout{5} = MotorInput.MotorD; elseif strcmpi(port,'DA') varargout{1} = MotorBitfield.MotorD+MotorBitfield.MotorA; - varargout{2} = uint8(MotorPort.MotorD); - varargout{3} = uint8(MotorInput.MotorD); - varargout{4} = uint8(MotorPort.MotorA); - varargout{5} = uint8(MotorInput.MotorA); + varargout{2} = MotorPort.MotorD; + varargout{3} = MotorInput.MotorD; + varargout{4} = MotorPort.MotorA; + varargout{5} = MotorInput.MotorA; elseif strcmpi(port,'DB') varargout{1} = MotorBitfield.MotorD+MotorBitfield.MotorB; - varargout{2} = uint8(MotorPort.MotorD); - varargout{3} = uint8(MotorInput.MotorD); - varargout{4} = uint8(MotorPort.MotorB); - varargout{5} = uint8(MotorInput.MotorB); + varargout{2} = MotorPort.MotorD; + varargout{3} = MotorInput.MotorD; + varargout{4} = MotorPort.MotorB; + varargout{5} = MotorInput.MotorB; elseif strcmpi(port,'DC') varargout{1} = MotorBitfield.MotorD+MotorBitfield.MotorC; - varargout{2} = uint8(MotorPort.MotorD); - varargout{3} = uint8(MotorInput.MotorD); - varargout{4} = uint8(MotorPort.MotorC); - varargout{5} = uint8(MotorInput.MotorC); + varargout{2} = MotorPort.MotorD; + varargout{3} = MotorInput.MotorD; + varargout{4} = MotorPort.MotorC; + varargout{5} = MotorInput.MotorC; else error('str2PortParam: Given port is not a valid sync motor port.'); end diff --git a/source/str2bool.m b/source/str2bool.m index 0b88252174b60c6a3d5898713acc55e473354077..cc656222399c3bb8957f7aa11119e2919f7baa7e 100644 --- a/source/str2bool.m +++ b/source/str2bool.m @@ -3,15 +3,12 @@ function bool = str2bool(inp) bool = 0; if ischar(inp) - if strcmpi(inp, 'on') || strcmpi(inp, 'true') + if strcmpi(inp, 'on') bool = 1; - elseif ~strcmpi(inp, 'off') && ~strcmpi(inp, 'false') + elseif ~strcmpi(inp, 'off') error('str2bool: Given parameter is not a valid string.'); end - elseif isnumeric(inp) - % warning('str2bool: Given parameter already is a numeric. Returning...'); - if bool==0 || bool==1 - bool = inp; - end + elseif isnumeric(inp) || islogical(inp) + bool = inp; end end