diff --git a/examples/testConnSpeed.m b/examples/testConnSpeed.m index 8ea14f20dcb0f23a2ead05e0936fbd979a879657..ea2a6df794a50bcb74e2fa5bdaff8146c55440ed 100644 --- a/examples/testConnSpeed.m +++ b/examples/testConnSpeed.m @@ -20,7 +20,7 @@ function testConnSpeed(ioType, serPort, it, withResponse) m = b.motorB; m.setProperties('power', 10, 'limitMode', 'Time', 'limitValue', 15000); - m.resetTachoCount(); + m.tachoCount = 0; if withResponse for i = 0:it-1 diff --git a/source/Brick.m b/source/Brick.m index 71ed0963961fd268ff5a2b84a827f4d081300c97..6927b8a298b77dacd7bd1a517597063c3418efbf 100644 --- a/source/Brick.m +++ b/source/Brick.m @@ -93,9 +93,6 @@ % % threeToneByteCode Generates the bytecode for the playThreeTone function % -% TO DO:: -% - outputReset, outputRead -% % Example:: % b = Brick('ioType','usb') % b = Brick('ioType','wifi','wfAddr','192.168.1.104','wfPort',5555,'wfSN','0016533dbaf5') @@ -105,23 +102,23 @@ classdef Brick < handle properties - % connection handle + % Connection handle conn; - % debug input + % Debug debug; % IO connection type ioType; - % bluetooth brick device name + % Bluetooth brick device name btDevice; - % bluetooth brick communication channel + % Bluetooth brick communication channel btChannel; - % wifi brick IP address + % Wifi brick IP address wfAddr; - % wifi brick TCP port + % Wifi brick TCP port wfPort; - % brick serial number + % Brick serial number wfSN; - % bluetooth serial port + % Bluetooth serial port serPort; end @@ -156,7 +153,7 @@ classdef Brick < handle % - For instrBrickIO (bluetooth) % b = Brick('ioType','instrbt','btDevice','EV3','btChannel',1) - % init the properties + % Init the properties opt.debug = 0; opt.btDevice = 'EV3'; opt.btChannel = 1; @@ -165,66 +162,52 @@ classdef Brick < handle opt.wfSN = '0016533dbaf5'; opt.ioType = 'usb'; opt.serPort = '/dev/rfcomm0'; - % read in the options + + % Read in the options opt = tb_optparse(opt, varargin); - % select the connection interface - connect = 0; - % usb - if(strcmp(opt.ioType,'usb')) - brick.debug = opt.debug; - brick.ioType = opt.ioType; - brick.conn = usbBrickIO(brick.debug); - connect = 1; - end - % wifi - if(strcmp(opt.ioType,'wifi')) - brick.debug = opt.debug; - brick.ioType = opt.ioType; - brick.wfAddr = opt.wfAddr; - brick.wfPort = opt.wfPort; - brick.wfSN = opt.wfSN; - brick.conn = wfBrickIO(brick.debug,brick.wfAddr,brick.wfPort,brick.wfSN); - connect = 1; - end - % bluetooth - if(strcmp(opt.ioType,'bt')) - brick.debug = opt.debug; - brick.ioType = opt.ioType; - brick.serPort = opt.serPort; - brick.conn = btBrickIO(brick.debug,brick.serPort); - connect = 1; - end - % instrumentation and control wifi - if(strcmp(opt.ioType,'instrwifi')) - brick.debug = opt.debug; - brick.ioType = opt.ioType; - brick.wfAddr = opt.wfAddr; - brick.wfPort = opt.wfPort; - brick.wfSN = opt.wfSN; - brick.conn = instrBrickIO(brick.debug,'wf',brick.wfAddr,brick.wfPort,brick.wfSN); - connect = 1; - end - % instrumentation and control bluetooth - if(strcmp(opt.ioType,'instrbt')) - brick.debug = opt.debug; - brick.ioType = opt.ioType; - brick.btDevice = opt.btDevice; - brick.btChannel = opt.btChannel; - brick.conn = instrBrickIO(brick.debug,'bt',brick.btDevice,brick.btChannel); - connect = 1; - end - % error - if(~connect) - fprintf('Please specify a serConn option: ''usb'',''wifi'',''bt'',''instrwifi'' or ''instrbt''.\n'); + brick.debug = opt.debug; + brick.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); + end + catch ME + brick.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 % % delete(b) closes the connection to the brick - brick.conn.close(); + if isa(brick.conn, 'handle') && isvalid(brick.conn) + brick.conn.delete(); + end end function send(brick, cmd) @@ -239,15 +222,17 @@ classdef Brick < handle % Example:: % b.send(cmd) - % send the message through the brickIO write function - + % Send the message through the brickIO write function brick.conn.write(cmd.msg); + % (MMI) When spamming the brick with commands, at some point, it will start % behaving 'strange'. Sometimes, commands will be executed only with % 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); + + % Verbose output if brick.debug > 0 fprintf('sent (hex): [ '); for ii=1:length(cmd.msg) @@ -268,12 +253,63 @@ classdef Brick < handle % rmsg = Brick.receive() receives data from the brick through % the connection handle. % + % Notes: + % - If received packet is corrupt, up to five new packets are read (if all are + % corrupt, an error is thrown) + % % Example:: % rmsg = b.receive() - - % read the message through the brickIO read function + % + % Read the message through the brickIO read function rmsg = brick.conn.read(); + + % Check if reply is corrupt or error byte is set + try + reply = Command(rmsg); + catch ME + corrupt = 1; + if ~isempty(strfind(ME.identifier, 'CorruptPacket')) + % Read packet was corrupt - retry + id = [ID(), ':', 'CorruptPacket']; + warning(id, 'Read corrupt packet. Retrying...'); + if brick.debug + fprintf('received (corrupt) (hex): [ '); + for ii=1:length(rmsg) + fprintf('%s ',dec2hex(rmsg(ii))) + end + fprintf(']\n'); + fprintf('received (corrupt) (dec): [ '); + for ii=1:length(rmsg) + fprintf('%d ',rmsg(ii)) + end + fprintf(']\n'); + end + + retries = 5; + while corrupt && retries + rmsg = brick.conn.read(); + try + reply = Command(rmsg); + corrupt = 0; + catch + retries = retries-1; + end + end + end + + if corrupt + rethrow(ME); + end + end + + if reply.checkForError() + msg = 'Error byte is set. The Brick couldn''t handle the last packet'; + id = [ID(), ':', 'CommandError']; + warning(id, msg); + end + + % Verbose output if brick.debug > 0 fprintf('received (hex): [ '); for ii=1:length(rmsg) @@ -614,8 +650,6 @@ classdef Brick < handle cmd.opINPUT_DEVICE_SET_TYPEMODE(oldType,oldMode,newType,newMode); cmd.addLength(); brick.send(cmd); - % receive the command - msg = brick.receive()'; end % Implemented @ MMI @@ -1510,7 +1544,7 @@ classdef Brick < handle % b.outputReset(0,MotorBitfield.MotorA) cmd = Command(); - cmd.addHeaderDirectReply(42,0,0); + cmd.addHeaderDirect(42,0,0); cmd.opOUTPUT_RESET(layer,nos); cmd.addLength(); brick.send(cmd); @@ -1548,7 +1582,7 @@ classdef Brick < handle % Implemented @ MMI % Still buggy (WIP) - function [speed tacho] = outputRead(brick,layer,no) + function [speed, tacho] = outputRead(brick,layer,no) % Brick.outputRead(layer,no) Get tacho count and speed. % % [speed, tacho] = Brick.outputRead(layer,no) returns the tachometer diff --git a/source/BrickIO.m b/source/BrickIO.m index a4c00c212d61a3ca3792a126ae411fb2fcf89f96..585eb6d9f54c32615c5331124bad6c1109142956 100644 --- a/source/BrickIO.m +++ b/source/BrickIO.m @@ -11,8 +11,8 @@ % - The read function should return a uint8 datatype % - The write function should be given a uint8 datatype as a parameter -classdef BrickIO - properties (Abstract) +classdef BrickIO < handle + properties (Abstract, Access = 'protected') % connection handle handle end diff --git a/source/Command.m b/source/Command.m index fa20a019e9cd319f61de337630b116194669f6ac..94d1fb069b66cdd97ff1192fb619017e3f959890 100644 --- a/source/Command.m +++ b/source/Command.m @@ -12,7 +12,8 @@ % addSystemCommand Adds a system command to the command object % addDirectCommand Adds a direct command to the command object % -% checkForError @MMI Checks error byte in received package. +% checkForError @MMI Checks error byte in received package +% isCorrupt @MMI Checks whether reply packet is corrupt % % clear Clears the command msg % display Displays the command msg (decimal) @@ -286,12 +287,45 @@ classdef Command < handle function cmd = Command(varargin) % Command.cmd Create an empty command % - % c = Command(OPTIONS) is an object that represents an EV3 command + % c = Command() is an object that represents an EV3 command + % c = Command([...]) is an object that represents an EV3 reply % % Example:: % c = Command(); - cmd.msg = uint8([]); + if nargin > 0 % Packet is a reply + try + cmd.msg = uint8(varargin{1}); + catch ME + id = [ID(), ':', 'InvalidParameter']; + msg = 'Failed to create reply-packet with Command-class.'; + baseException = MException(id, msg); + baseException = addCause(baseException, ME); + throw(baseException); + end + + % If corrupt, throw error + corrupt = cmd.isValidReply(); + if corrupt < 1 + msg = 'Invalid reply packet'; + id = [ID(), ':', 'CorruptPacket']; + ME = MException(id, msg); + switch corrupt + case 0 + causeMsg = 'Reply packet too short'; + case -1 + causeMsg = ['Specified packet length (bytes 1&2) does not equal ',... + 'actual packet length']; + case -2 + causeMsg = 'Error type (byte 5) is not valid'; + end + cause = MException(id, causeMsg); + ME = addCause(ME, cause); + throw(ME); + end + else % Packet is a command + cmd.msg = uint8([]); + end end function delete(cmd) @@ -322,6 +356,43 @@ classdef Command < handle end end + function state = isValidReply(cmd) + % Command.isValidReply Check if reply-packet is valid + % + % state = cmd.isValidReply checks cmd against reply-packet-format and returns + % whether if cmd is valid (that is, does not fit the + % format). + % Notes: + % - state = 1, if reply is not corrupted (at least not in a way that can be + % tested) + % - state = 0, if reply is too short (minimum length = 5 bytes) + % - state = -1, if reply is corrupted due to invalid length/length indicator + % - state = -2, if reply is corrupted due to invalid error type byte + % - IMPORTANT: Of course, the packet can still contain invalid information which + % cannot be tested properly without context. This corruption-test + % only checks the contextless information. + % + state = 1; + + % Minimum length of a reply packet is 5, for a packet without response buffer + minLength = 5; + if length(cmd.msg) < minLength + state = 0; + return + end + + % Note: The two things that can be checked without context are the length and the + % command type, the remainder varies from packet to packet. + pLength = double(typecast(cmd.msg(1:2),'uint16')) + 2; + pCmdType = cmd.msg(5); + + if pLength ~= length(cmd.msg) + state = -1; + elseif pCmdType ~= 2 && pCmdType ~=4 + state = -2; + end + end + function addHeaderSystem(cmd,counter) % Command.addHeaderSystem Add a system header with no reply % @@ -581,7 +652,7 @@ classdef Command < handle end function GV0(cmd,i) - % Command.GV0 Add a global constant 0 + % Command.GV0 Add a global variable 0 % % Command.GV0(i) adds a global variable 0 to the command % object. diff --git a/source/ConnectionType.m b/source/ConnectionType.m index a2d18c69c091832765f863f91ad6f534fdea7a3b..df8a552fb4c5040ee230d52adf4937202b1c4bcc 100644 --- a/source/ConnectionType.m +++ b/source/ConnectionType.m @@ -16,4 +16,4 @@ classdef ConnectionType < uint8 None (126) Error (127) end -end \ No newline at end of file +end diff --git a/source/EV3.m b/source/EV3.m index f263011d5f4a8e5a7b366cb124b88fe91f0d18c7..2c9032c15b3cc1cf37b0db909e742e64203424cc 100644 --- a/source/EV3.m +++ b/source/EV3.m @@ -99,7 +99,6 @@ classdef EV3 < handle properties (Hidden, Access = 'private') % Hidden properties for internal use only init = 1; % Indicates 'init-phase' (Set to 1 as long as constructor is running) - verbose = 0; % Debug mode for low level layers end methods % Standard methods @@ -114,20 +113,25 @@ classdef EV3 < handle ev3.setProperties(varargin{:}); - ev3.motorA = Motor('A', 'Debug', ev3.debug-ev3.verbose); - ev3.motorB = Motor('B', 'Debug', ev3.debug-ev3.verbose); - ev3.motorC = Motor('C', 'Debug', ev3.debug-ev3.verbose); - ev3.motorD = Motor('D', 'Debug', ev3.debug-ev3.verbose); + 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.sensor1 = Sensor('1', 'Debug', ev3.debug-ev3.verbose); - ev3.sensor2 = Sensor('2', 'Debug', ev3.debug-ev3.verbose); - ev3.sensor3 = Sensor('3', 'Debug', ev3.debug-ev3.verbose); - ev3.sensor4 = Sensor('4', 'Debug', ev3.debug-ev3.verbose); + 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.init = 0; end + function delete(ev3) + if ev3.isConnected + ev3.disconnect(); + end + end %% Connection function connect(ev3, varargin) %connect Connects EV3-object and its Motors and Sensors to physical brick. @@ -153,8 +157,6 @@ classdef EV3 < handle ev3.brick = 0; ev3.isConnected = 0; - - % error('EV3::connect: Aborted connection.'); end end @@ -179,8 +181,8 @@ classdef EV3 < handle % Try to connect try % Connect to physical brick - ev3.brick = Brick('debug', ev3.verbose, varargin{1:end}); % Creating Brick-object implicitly - % establishes connection + % -> Creating Brick-object implicitly establishes connection + ev3.brick = Brick('debug', ev3.debug>=2, varargin{1:end}); ev3.isConnected = 1; if beep @@ -205,8 +207,8 @@ classdef EV3 < handle ev3.brick.delete(); ev3.brick = 0; end - error(['Brick::connect: Something went wrong, try again...\n', ... - 'Error identifier: %s'], ME.identifier); + + rethrow(ME); end end @@ -220,9 +222,9 @@ classdef EV3 < handle % b.disconnect(); % - if ~ev3.isConnected - error('EV3::connect: No brick connected.'); - end +% if ~ev3.isConnected +% error('EV3::connect: No brick connected.'); +% end % Disconnect motors % -> set references to brick object to 0 @@ -239,7 +241,9 @@ classdef EV3 < handle ev3.sensor4.disconnect(); % Delete handle to brick object - ev3.brick.delete(); + if isBrickValid(ev3.brick) && ev3.brick ~= 0 + ev3.brick.delete(); + end ev3.brick = 0; ev3.isConnected = 0; @@ -506,8 +510,8 @@ classdef EV3 < handle ev3.debug = debug; end - if debug == 2 - ev3.verbose = 1; + if ev3.isConnected() + ev3.brick.debug = (ev3.debug >= 2); end end @@ -515,7 +519,7 @@ classdef EV3 < handle %setProperties Set multiple EV3 properties at once using MATLAB's inputParser. % % Arguments - % * 'debug', 0/1/'on'/'off'/'true'/'false' + % * 'debug', 0/1/'on'/'off'/'true'/'false'/2 % * 'batteryMode', 'Voltage'/'Percentage': Mode in which batteryValue will be read. % % Example @@ -560,7 +564,6 @@ classdef EV3 < handle bat = ev3.getBattery(); end - %% Display %% Display function display(ev3) % WIP diff --git a/source/ID.m b/source/ID.m new file mode 100644 index 0000000000000000000000000000000000000000..b108a1c633f4cf667b31f2c46e37b18fcea9521b --- /dev/null +++ b/source/ID.m @@ -0,0 +1,31 @@ +function id = ID() +% Generates a string that serves as an error-ID for a calling function + +toolbox = 'RWTHMindstormsEV3'; + +% Get stack trace +stackTrace = dbstack(); + +% Element on top of stack is this function (ID()), second element is caller +% If no second element, caller is probably console -> not valid +if length(stackTrace) <= 1 + ME = MException('RWTHMindstormsEV3:ID', ... + ['ID() is only function on stack - can not create ID. (You can''t call ID() from ',... + 'the console).']); + throw(ME); +end +callerList = strsplit(stackTrace(2).name, '.'); + +% The anticipated format of the caller is classname.functionname +functionName = callerList{length(callerList)}; + +% Create id +if length(callerList) > 1 + className = callerList{length(callerList)-1}; + id = [toolbox, ':', className, ':', functionName]; +else + id = [toolbox, ':', functionName]; +end + +end + diff --git a/source/Motor.m b/source/Motor.m index f0866b121227fe600173e860d879742d5bdade67..86257623e871f8fc8b14ead2371e50c78002c391 100644 --- a/source/Motor.m +++ b/source/Motor.m @@ -196,7 +196,7 @@ classdef Motor < handle motor.setProperties(varargin{:}); motor.init = 0; end - + %% Connection function connect(motor,brick) %connect Connects Motor-object to physical brick. @@ -226,7 +226,7 @@ classdef Motor < handle motor.brick = 0; motor.isConnected = 0; - error('Motor::connect: Aborted connection.'); + error('Motor::connect: Disconnected due to internal error.'); end end @@ -256,9 +256,9 @@ classdef Motor < handle % brickInterface.delete(); % Actual disconnecting!!! % - if ~motor.isConnected - error('Motor::disconnect: No brick connected.'); - end +% if ~motor.isConnected +% error('Motor::disconnect: No brick connected.'); +% end motor.brick = 0; % Note: actual deleting is done in EV3::disconnect. motor.isConnected = 0; @@ -272,7 +272,7 @@ classdef Motor < handle %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 theses parameters if there is a physical motor currently connected + % concludes from these parameters if there is a physical motor currently connected % to the port (Motor.motorAtPort). % % Examples @@ -286,7 +286,6 @@ classdef Motor < handle % 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!']); @@ -322,33 +321,6 @@ classdef Motor < handle end end motor.mode = mode; - -% oldMode = motor.mode; -% -% motor.status = motor.getStatus(); -% [motor.type,newMode] = motor.getTypeMode(); -% -% motor.motorAtPort = 1; -% if motor.status~=ConnectionType.OutputTacho || ... % Regular Tacho-Motor connected? -% (motor.type~=DeviceType.LargeMotor && motor.type~=DeviceType.MediumMotor) || ... -% strcmp(class(motor.mode), 'DeviceMode.Error') -% motor.motorAtPort = 0; -% end -% -% -% if strcmp(class(oldMode),class(newMode)) && oldMode~=newMode -% if ~strcmp(class(oldMode), 'DeviceMode.Error') && ... -% ~strcmp(class(newMode), 'DeviceMode.Error') -% if newMode ~= DeviceMode.Motor.Speed -% warning(['Motor::update: Physical motor''s mode was not ',... -% 'the specified one. Changing...']); -% end -% -% motor.mode = oldMode; -% end -% else -% motor.mode = newMode; -% end end %% Brick functions @@ -572,28 +544,6 @@ classdef Motor < handle 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 - error(['Motor::resetTachoCount: Motor-Object not connected to brick handle.',... - 'You have to call motor.connect(brick) first!']); - elseif ~motor.motorAtPort - error('Motor::resetTachoCount: No physical motor connected to Port %s',... - motor.port); - 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 togglePolarity(motor) %togglePolarity Switches the direction in which the motor turns. if ~motor.isConnected @@ -650,7 +600,7 @@ classdef Motor < handle mode = DeviceMode(motor.type, uint8(mode)); end - if isModeValid(mode, DeviceType.LargeMotor) + 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. ' ,... @@ -777,6 +727,14 @@ classdef Motor < handle 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(); + end + function setProperties(motor, varargin) %setProperties Sets multiple Motor properties at once using MATLAB's inputParser. % @@ -938,16 +896,7 @@ classdef Motor < handle return; end -% if ~isnumeric(power) -% error('Motor::setPower: Given parameter is not a numeric.'); -% elseif power<-100 || power>100 -% warning('Motor::setPower: Motor power has to be an element of [-100,100]!'); -% error('Motor::setPower: Given motor power is out of bounds.'); -% end - if motor.limitValue~=0 && motor.isRunning() - % motor.start(); - warning(['Motor::setPower: Can''t set power if motor is running with a ',... 'tacholimit. This would mess up the internal tacho state.']); return; @@ -983,6 +932,25 @@ classdef Motor < handle 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.',... diff --git a/source/Sensor.m b/source/Sensor.m index f5ad914e43674ac8341a724a3428d3362f375239..f794ae8663cfdca07813e7aacc0e91dc47abcef2 100644 --- a/source/Sensor.m +++ b/source/Sensor.m @@ -136,9 +136,9 @@ classdef Sensor < handle function disconnect(sensor) %disconnect Disconnects Sensor-object from physical brick - if ~sensor.isConnected - error('Sensor::disconnect: No brick connected.'); - end +% if ~sensor.isConnected +% error('Sensor::disconnect: No brick connected.'); +% end sensor.brick = 0; % Note: actual deleting is done in EV3::disconnect. sensor.isConnected = 0; @@ -163,8 +163,8 @@ classdef Sensor < handle 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...']); + %warning(['Sensor::update: Physical sensor''s mode was not ',... + % 'the specified one. Changing...']); sensor.setMode(oldMode); sensor.mode = oldMode; @@ -173,7 +173,7 @@ classdef Sensor < handle sensor.mode = newMode; end - validStatus = [ConnectionType.NXTColor, ConnectionType.NXTDumb, ... + validStatus = [ConnectionType.NXTColor, ConnectionType.NXTDumb, ConnectionType.NXTIIC, ... ConnectionType.InputDumb, ConnectionType.InputUART]; validTypes = [DeviceType.NXTTouch, DeviceType.NXTColor, ... DeviceType.NXTLight, DeviceType.NXTSound, ... @@ -330,10 +330,6 @@ classdef Sensor < handle methods (Access = 'private') % Private brick functions that are wrapped by dependent params function setMode(sensor, mode) - % if ~sensor.isConnected - % error(['Sensor::setMode: Sensor-Object not connected to brick handle.',... - % 'You have to call sensor.connect(brick) first!']); - % end if ~sensor.isConnected || ~sensor.sensorAtPort return end diff --git a/source/btBrickIO.m b/source/btBrickIO.m index 3b9163f2872f39dd2e153594771681ffa0cfb3d5..90a3e51ccbff4545bd74a1ea5ddc5410f50ecd8b 100644 --- a/source/btBrickIO.m +++ b/source/btBrickIO.m @@ -26,16 +26,18 @@ classdef btBrickIO < BrickIO properties - % connection handle - handle % debug input debug = 0; % bluetooth serial port serialPort = '/dev/rfcomm0' end + properties (Access = 'protected') + % connection handle + handle + end + methods - function brickIO = btBrickIO(debug,serialPort) %btBrickIO.btBrickIO Create a btBrickIO object % @@ -50,12 +52,31 @@ classdef btBrickIO < BrickIO brickIO.debug = debug; brickIO.serialPort = serialPort; end + if brickIO.debug > 0 fprintf('btBrickIO init\n'); end - % set the connection handle - brickIO.handle = serial(brickIO.serialPort); - % open the connection handle + + % Set the connection handle + try + brickIO.handle = serial(brickIO.serialPort); + catch ME + if ~isempty(strfind(ME.identifier, 'invalidPORT')) + % Throw a clean InvalidSerialPort to avoid confusion in upper layers + msg = 'Couldn''t connect to BT-device because given serial port is invalid.'; + id = [ID(), ':', 'InvalidSerialPort']; + throw(MException(id, msg)); + else + % Throw combined error because error did not happen due to known reasons... + msg = 'Unknown error occurred while creating serial-port-object for BT connection.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); + end + end + + % Open the connection handle brickIO.open; end @@ -63,11 +84,17 @@ classdef btBrickIO < BrickIO %btBrickIO.delete Delete the btBrickIO object % % delete(brickIO) closes the bluetooth connection handle + if brickIO.debug > 0 fprintf('btBrickIO delete\n'); end - % delete the bt handle - brickIO.close; + + % Disconnect + try + brickIO.close; + catch + % Connection already closed (probably due to an error) - do nothing + end end function open(brickIO) @@ -79,8 +106,26 @@ classdef btBrickIO < BrickIO if brickIO.debug > 0 fprintf('btBrickIO open\n'); end - % open the bt handle - fopen(brickIO.handle); + + % Open the bt handle + try + fopen(brickIO.handle); + catch ME + if strcmp(ME.identifier, 'MATLAB:serial:fopen:opfailed') + % Throw only clean CommError to avoid confusion in upper layers + msg = 'Failed to open connection to Brick via Bluetooth.'; + id = [ID(), ':', 'CommError']; + throw(MException(id, msg)); + else + % Throw combined error because error did not happen due to communication + % failure + msg = 'Unknown error occurred while connecting to the Brick via Bluetooth.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); + end + end end function close(brickIO) @@ -92,8 +137,19 @@ classdef btBrickIO < BrickIO if brickIO.debug > 0 fprintf('btBrickIO close\n'); end - % close the close handle - fclose(brickIO.handle); + + try + % Close the close handle + fclose(brickIO.handle); + catch ME + % Throw combined error because error did not happen due to communication + % failure + msg = 'Unknown error occurred while disconnecting from the Brick via Bluetooth.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); + end end function rmsg = read(brickIO) @@ -105,11 +161,30 @@ classdef btBrickIO < BrickIO if brickIO.debug > 0 fprintf('btBrickIO read\n'); end - % get the number of bytes to be read from the bt handle - nLength = fread(brickIO.handle,2); - % read the remaining bytes - rmsg = fread(brickIO.handle,double(typecast(uint8(nLength),'uint16'))); - % append the reply size to the return message + + try + % Get the number of bytes to be read from the bt handle + nLength = fread(brickIO.handle,2); + + % Read the remaining bytes + rmsg = fread(brickIO.handle,double(typecast(uint8(nLength),'uint16'))); + catch ME + if strcmp(ME.identifier, 'MATLAB:serial:fread:opfailed') + % Throw only clean CommError to avoid confusion in upper layers + msg = 'Failed to read data from Brick via Bluetooth.'; + id = [ID(), ':', 'CommError']; + throw(MException(id, msg)); + else + % Throw combined error because error did not happen due to known reasons... + msg = 'Unknown error occurred while reading data from the Brick via BT.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException);b.bee + end + end + + % Append the reply size to the return message rmsg = uint8([nLength' rmsg']); end @@ -126,7 +201,25 @@ classdef btBrickIO < BrickIO if brickIO.debug > 0 fprintf('btBrickIO write\n'); end - fwrite(brickIO.handle,wmsg); + + try + % Write to the bluetooth handle + fwrite(brickIO.handle,wmsg); + catch ME + if strcmp(ME.identifier, 'MATLAB:serial:fwrite:opfailed') + % Throw only clean CommError to avoid confusion in upper layers + msg = 'Failed to send data to Brick via Bluetooth.'; + id = [ID(), ':', 'CommError']; + throw(MException(id, msg)); + else + % Throw combined error because error did not happen due to known reasons... + msg = 'Unknown error occurred while sending data to the Brick via BT.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); + end + end end end -end \ No newline at end of file +end diff --git a/source/hidapi.m b/source/hidapi.m index fe1fd000042cd84c608d224ceee575b699e6eaff..014c4a15bd580b3e4ae956fbc1c9c32919832593 100644 --- a/source/hidapi.m +++ b/source/hidapi.m @@ -1,7 +1,7 @@ %hidpi Interface to the hidapi library % % Methods:: -% hidpi Constructor, loads the hidapi library +% hidapi Constructor, loads the hidapi library % delete Destructor, closes any open hid connection % % open Open the hid device with vendor and product ID @@ -12,13 +12,14 @@ % getHIDInfoString Get the relevant hid info from the hid device % getManufacturersString Get the manufacturers string from the hid device % getProductString Get the product string from the hid device -% getSerialNumberString Get the serial number from the hid device +% getSerialNumberString Get the serial number from the hid device % setNonBlocking Set non blocking hid read % init Init the hidapi (executed in open by default) % exit Exit the hidapi -% error Return the error string +% error Return the error string % enumerate Enumerate the connected hid devices % +% % Example:: % hid = hidapi(1,1684,0005,1024,1025) % @@ -26,7 +27,7 @@ % - Developed from the hidapi available from http://www.signal11.us/oss/hidapi/ % - Windows: need the hidapi.dll file % - Mac: need the hidapi.dylib file. Will also need Xcode installed to run load library -% - Linux: will need to compile on host system and copy the resulting .so file +% - Linux: will need to compile on host system and copy the resulting .so file classdef hidapi < handle properties @@ -46,21 +47,26 @@ classdef hidapi < handle slib = 'hidapi'; % shared library header sheader = 'hidapi.h'; - % isOpen flag - isOpen = 0; + end methods + %% Constructor function hid = hidapi(debug,vendorID,productID,nReadBuffer,nWriteBuffer) %hidapi.hidapi Create a hidapi library interface object - % + % % hid = hidapi(debug,vendorID,productID,nReadBuffer,nWriteButter) % is an object which initialises the hidapi from the corresponding % OS library. Other parameters are also initialised. Some OS % checking is required in this function to load the correct % library. % + % Throws:: + % LoadingLibraryError Could not load .dll/.dylib/.so-file of hidapi + % InvalidFileNameOrFileMissing Either file names given were wrong or the files + % are missing (thunk files, proto files, ...) + % % Notes:: % - debug is a flag specifying output printing (0 or 1). % - vendorID is the vendor ID of the hid device (decimal not hex). @@ -68,71 +74,118 @@ classdef hidapi < handle % - nReadBuffer is the length of the read buffer. % - nWriteBuffer is the length of the write buffer. + hid.debug = debug; + if hid.debug > 0 fprintf('hidapi init\n'); end + if nargin > 1 hid.vendorID = vendorID; hid.productID = productID; hid.nReadBuffer = nReadBuffer; hid.nWriteBuffer = nWriteBuffer; end - % disable the type not found for structure warning + + % Disable warnings warning('off','MATLAB:loadlibrary:TypeNotFoundForStructure'); - % check if the library is loaded - if ~libisloaded('hidapiusb') - % check the operating system type and load slib + warning('off', 'MATLAB:loadlibrary:ClassIsLoaded'); + + try + % Check the operating system type and load slib if (ispc == 1) - % check the bit version + % Check the bit version if (strcmp(mexext,'mexw32')) hid.slib = 'hidapi32'; - % load the library via the proto file + % Load the library via the proto file loadlibrary(hid.slib,@hidapi32_proto,'alias','hidapiusb') - end - - if (strcmp(mexext,'mexw64')) + elseif (strcmp(mexext,'mexw64')) hid.slib = 'hidapi64'; - % load the library via the proto file + % Load the library via the proto file loadlibrary(hid.slib,@hidapi64_proto,'alias','hidapiusb') - end + end elseif (ismac == 1) - hid.slib = 'hidapi64'; - % load the library via the proto file - loadlibrary(hid.slib,@hidapi64mac_proto,'alias','hidapiusb'); + hid.slib = 'hidapi64'; + % Load the library via the proto file + loadlibrary(hid.slib,@hidapi64mac_proto,'alias','hidapiusb'); elseif (isunix == 1) - hid.slib = 'libhidapi-libusb'; - % load the shared library - loadlibrary(hid.slib,@hidapi_libusb_proto,'alias','hidapiusb'); - end + hid.slib = 'libhidapi-libusb'; + % Load the shared library + loadlibrary(hid.slib,@hidapi_libusb_proto,'alias','hidapiusb'); + end + catch ME + % Create own exception for clarification + id = [ID(), ':', 'LoadingLibraryError']; + msg = strcat({'Could not load library '}, {hid.slib}, {'.'}); + exception = MException(id, msg); + + % Try to narrow down loading failure + if isempty(findstr(ME.identifier, 'LoadFailed')) ... + && isempty(findstr(ME.identifier, 'ErrorLoadingLibrary')) ... + && isempty(findstr(ME.identifier, 'ErrorInHeader')) + id = [ID(), ':', 'InvalidFileNameOrFileMissing']; + msg = 'Invalid file names were given or files are not available.'; + cause = MException(id, msg); + exception = addCause(exception, cause); + end + + throw(exception); end - + % Remove the library extension + hid.slib = 'hidapiusb'; + if hid.debug > 0 libfunctionsview('hidapiusb'); end - - % remove the library extension - hid.slib = 'hidapiusb'; end function delete(hid) %hidapi.delete Delete hid object % - % delete(hid) closes an open hid device connection. + % delete(hid) closes an open hid device connection. This function is called + % automatically when deleting. % % Notes:: - % - You cannot unloadlibrary in this function as the object is + % - You cannot unloadlibrary in this function as the object is % still present in the MATLAB work space. if hid.debug > 0 fprintf('hidapi delete\n'); end - if hid.isOpen == 1 - % close the open connection - hid.close(); - end end - + + %% Wrapper + + function str = getManufacturersString(hid) + %hidapi.getManufacturersString get manufacturers string from hid object + % + % hid.getManufacturersString() returns the manufacturers string + % from the hid device using getHIDInfoString. + + str = getHIDInfoString(hid,'hid_get_manufacturer_string'); + end + + function str = getProductString(hid) + %hidapi.getProductString get product string from hid object + % + % hid.getProductString() returns the product string from the + % hid device using getProductString. + + str = getHIDInfoString(hid,'hid_get_product_string'); + end + + function str = getSerialNumberString(hid) + %hidapi.getSerialNumberString get product string from hid object + % + % hid.getSerialNumberString() returns the serial number string + % from the hid device using getSerialNumberString. + + str = getHIDInfoString(hid,'hid_get_serial_number_string'); + end + + %% Wrapped HIDAPI-Functions + function open(hid) %hidapi.open Open a hid object % @@ -140,10 +193,13 @@ classdef hidapi < handle % initialised values of vendorID and productID from the hidapi % constructor. % + % Throws:: + % CommError Error during communication with device + % % Notes:: % - The pointer return value from this library call is always % null so it is not possible to know if the open was - % successful. + % successful. % - The final parameter to the open hidapi library call has % different types depending on OS. In windows it is uint16 but % linux/mac it is int32. @@ -151,87 +207,144 @@ classdef hidapi < handle if hid.debug > 0 fprintf('hidapi open\n'); end - % create a null pointer for the hid_open function (depends on OS) + + % Create a null pointer for the hid_open function (depends on OS) if (ispc == 1) pNull = libpointer('uint16Ptr'); - end - if ((ismac == 1) || (isunix == 1)) + elseif ((ismac == 1) || (isunix == 1)) pNull = libpointer('int32Ptr'); end - % open the hid interface - [hid.handle,value] = calllib(hid.slib,'hid_open',uint16(hid.vendorID),uint16(hid.productID),pNull); - % set open flag - hid.isOpen = 1; + + % Open the hid interface + [newHandle,value] = calllib(hid.slib,'hid_open',uint16(hid.vendorID), ... + uint16(hid.productID),pNull); + + % (MMI) Assert error case (hid_open returns null-pointer in error case) + assert(isLibPointerValid(newHandle)==1, ... + [ID(), ':', 'CommError'], ... + 'Failed to connect to USB device.'); + + hid.handle = newHandle; end function close(hid) %hidapi.close Close hid object % - % hid.close() closes the connection to a hid device. + % hid.close() closes the connection to a hid device. Gets called automatically + % when deleting the hid instance. + % + % Throws:: + % InvalidHandle Handle to USB-device not valid + % if hid.debug > 0 fprintf('hidapi close\n'); end - if hid.isOpen == 1 - % close the connect - calllib(hid.slib,'hid_close',hid.handle); - % clear open flag - hid.isOpen = 0; - end + + % (MMI) Check if pointer is (unexpectedly) already invalidated + assert(isLibPointerValid(hid.handle)==1, ... + [ID(), ':', 'InvalidHandle'], ... + 'Failed to close USB-connection because pointer to USB-device is already invalidated.'); + + % Close the connection + calllib(hid.slib,'hid_close',hid.handle); + + % Invalidate the pointer + hid.handle = []; end - function rmsg = read(hid) %hidapi.rmsg Read from hid object % % rmsg = hid.read() reads from a hid device and returns the % read bytes. Will print an error if no data was read. - + % + % Throws:: + % CommError Error during communication with device + % InvalidHandle Handle to USB-device not valid + % + if hid.debug > 0 fprintf('hidapi read\n'); end - % read buffer of nReadBuffer length + + % Read buffer of nReadBuffer length buffer = zeros(1,hid.nReadBuffer); - % create a unit8 pointer + % Create a uint8 pointer pbuffer = libpointer('uint8Ptr', uint8(buffer)); - % read data from HID deivce + + % (MMI) Check if pointer is (unexpectedly) already invalidated + assert(isLibPointerValid(hid.handle)==1, ... + [ID(), ':', 'InvalidHandle'], ... + 'Failed to read USB-data because pointer to USB-device is invalidated.'); + + % Read data from HID device [res,h] = calllib(hid.slib,'hid_read',hid.handle,pbuffer,uint64(length(buffer))); - % check the response - if res == 0 - fprintf('hidapi read returned no data\n'); + + % (MMI) Check the response (No assert as there are multiple cases) + if res < 1 + % Error occurred + id = [ID(), ':', 'CommError']; + % Narrow error down + if res == -1 + msg = 'Connection error (probably lost connection to device)'; + elseif res == 0 + msg = ['Could not read data from device (device is still connected, ',... + 'but does not react)']; + else + msg = 'Unexpected connection error'; + end + causeException = MException(id, msg); + ME = MException(id, 'Failed to read data via USB.'); + addCause(ME, causeException); + throw(ME); end - % return the string value + + % Return the string value rmsg = pbuffer.Value; end - + function write(hid,wmsg,reportID) %hidapi.write Write to hid object % % hid.write() writes to a hid device. Will print an error if % there is a mismatch between the buffer size and the reported % number of bytes written. - + % + % Throws:: + % CommError Error during communication with device + % InvalidHandle Handle to USB-device not valid + % + if hid.debug > 0 fprintf('hidapi write\n'); end - % append a 0 at the front for HID report ID + % Append a 0 at the front for HID report ID wmsg = [reportID wmsg]; - % pad with zeros for nWriteBuffer length - % (MMI) Note:: The following line does not seem to be necessary; - % wmsg does not need to be the max packet size. Uncommenting this doesn't affect + + % Pad with zeros for nWriteBuffer length + % (MMI) Note:: The following line does not seem to be necessary; + % wmsg does not need to be the max packet size. Uncommenting this doesn't affect % anything, and I would prefer sending short messages over long ones. % Further testing may be required, so for now I don't change a thing. - %wmsg(end+(hid.nWriteBuffer-length(wmsg))) = 0; - % create a unit8 pointer + wmsg(end+(hid.nWriteBuffer-length(wmsg))) = 0; + + % Create a uint8 pointer pbuffer = libpointer('uint8Ptr', uint8(wmsg)); - % write the message - [res,h] = calllib(hid.slib,'hid_write',hid.handle,wmsg,length(wmsg)); - % check the response - if res ~= length(wmsg) - fprintf('hidapi write error: wrote %d, sent %d\n',(length(wmsg)-1),res); - end + % (MMI) Check if pointer is (unexpectedly) already invalidated + assert(isLibPointerValid(hid.handle)==1, ... + [ID(), ':', 'InvalidHandle'], ... + 'Failed to write to USB because pointer to USB-device is invalidated.'); + + % Write the message + [res,h] = calllib(hid.slib,'hid_write',hid.handle,pbuffer,uint64(length(wmsg))); + + % (MMI) Check the response + assert(res == length(wmsg), ... + [ID(), ':', 'CommError'], ... + 'Failed to write data via USB.'); end function str = getHIDInfoString(hid,info) @@ -240,57 +353,41 @@ classdef hidapi < handle % hid.getHIDInfoString(info) gets the corresponding hid info % from the hid device % + % Throws:: + % CommError Error during communication with device + % InvalidHandle Handle to USB-device not valid + % % Notes:: % - info is the hid information string. if hid.debug > 0 fprintf(['hidapi ' info '\n']); end - % read buffer nReadBuffer length + % Read buffer nReadBuffer length buffer = zeros(1,hid.nReadBuffer); - % create a unit16 pointer (depends on OS) + % Create a libpointer (depends on OS) if (ispc == 1) pbuffer = libpointer('uint16Ptr', uint16(buffer)); - else if ((ismac == 1) || (isunix == 1)) - pbuffer = libpointer('int32Ptr', int32(buffer)); - end - end - % get the HID info string - [res,h] = calllib(hid.slib,info,hid.handle,pbuffer,uint64(length(buffer))); - % check the response - if res ~= 0 - fprintf(['hidapi ' info ' error\n']); + elseif ((ismac == 1) || (isunix == 1)) + pbuffer = libpointer('int32Ptr', uint32(buffer)); end - % return the string value - str = sprintf('%s',char(pbuffer.Value)); - end - - function str = getManufacturersString(hid) - %hidapi.getManufacturersString get manufacturers string from hid object - % - % hid.getManufacturersString() returns the manufacturers string - % from the hid device using getHIDInfoString. - str = getHIDInfoString(hid,'hid_get_manufacturer_string'); - end - - function str = getProductString(hid) - %hidapi.getProductString get product string from hid object - % - % hid.getProductString() returns the product string from the - % hid device using getProductString. + % (MMI) Check if pointer is (unexpectedly) already invalidated + assert(isLibPointerValid(hid.handle)==1, ... + [ID(), ':', 'InvalidHandle'], ... + 'Failed to read USB-data because pointer to USB-device is invalidated.'); - str = getHIDInfoString(hid,'hid_get_product_string'); - end - - function str = getSerialNumberString(hid) - %hidapi.getSerialNumberString get product string from hid object - % - % hid.getSerialNumberString() returns the serial number string - % from the hid device using getSerialNumberString. + % Get the HID info string + [res,h] = calllib(hid.slib,info,hid.handle,pbuffer,uint32(length(buffer))); - str = getHIDInfoString(hid,'hid_get_serial_number_string'); - end + % (MMI) Check the response + assert(res~=-1, ... + [ID(), ':', 'CommError'], ... + 'Failed to read HID info string.'); + + % Return the string value + str = sprintf('%s',char(pbuffer.Value)); + end function setNonBlocking(hid,nonblock) %hidapi.setNonBlocking sets non blocking on the hid object @@ -298,18 +395,29 @@ classdef hidapi < handle % hid.setNonBlocking(nonblock) sets the non blocking flag on % the hid device connection. % + % Throws:: + % CommError Error during communication with device + % InvalidHandle Handle to USB-device not valid + % % Notes:: % nonblock - 0 disables nonblocking, 1 enables nonblocking if hid.debug > 0 fprintf('hidapi setNonBlocking\n'); end - % set non blocking - [res,h] = calllib(hid.slib,'hid_set_nonblocking',hid.handle,uint64(nonblock)); - % check the response - if res ~= 0 - fprintf('hidapi setNonBlocking error\n'); - end + + % (MMI) Check if pointer is (unexpectedly) already invalidated + assert(isLibPointerValid(hid.handle)==1, ... + [ID(), ':', 'InvalidHandle'], ... + 'Failed to set USB-read-mode to non-blocking because pointer to USB-device is invalidated.'); + + % Set non blocking + [res,h] = calllib(hid.slib,'hid_set_nonblocking',hid.handle,uint32(nonblock)); + + % (MMI) Check the response + assert(res~=-1, ... + [ID(), ':', 'CommError'], ... + 'Failed to set USB-read-mode to non-blocking.'); end function init(hid) @@ -318,22 +426,35 @@ classdef hidapi < handle % hid.init() inits the hidapi library. This is called % automatically in the library itself with the open function. % + % Throws:: + % CommError Error during communication with device + % % Notes:: % - You should not have to call this function directly. if hid.debug > 0 fprintf('hidapi init\n'); end + + warning([ID(), ':', 'RedundantCall'], ... + 'The init-function gets called automatically when connecting!'); + + % Init device res = calllib(hid.slib,'hid_init'); - if res ~= 0 - fprintf('hidapi init error\n'); - end + + % (MMI) Check the response + assert(res~=-1, ... + [ID(), ':', 'CommError'], ... + 'Failed to init USB-device.'); end - + function exit(hid) %hidapi.exit Exit hidapi % - % hid.exit() exits the hidapi library. + % hid.exit() exits the hidapi library. + % + % Throws:: + % CommError Error during communication with device % % Notes:: % - You should not have to call this function directly. @@ -341,10 +462,17 @@ classdef hidapi < handle if hid.debug > 0 fprintf('hidapi exit\n'); end + + warning([ID(), ':', 'RedundantCall'], ... + 'The exit-function gets called automatically when disconnecting!'); + + % Exit device res = calllib(hid.slib,'hid_exit'); - if res ~= 0 - fprintf('hidapi exit error\n'); - end + + % (MMI) Check the response + assert(res~=-1, ... + [ID(), ':', 'CommError'], ... + 'Failed to exit USB-device.'); end function str = error(hid) @@ -353,6 +481,9 @@ classdef hidapi < handle % hid.error() returns the hid device error string if a function % produced an error. % + % Throws:: + % InvalidHandle Handle to USB-device not valid + % % Notes:: % - This function must be called explicitly if you think an % error was generated from the hid device. @@ -360,13 +491,19 @@ classdef hidapi < handle if hid.debug > 0 fprintf('hidapi error\n'); end - [h,str] = calllib(hid.slib,'hid_error',hid.handle); + + % (MMI) Check if pointer is (unexpectedly) already invalidated + assert(isLibPointerValid(hid.handle)==1, ... + [ID(), ':', 'InvalidHandle'], ... + 'Failed to read USB-error-data because pointer to USB-device is invalidated.'); + + [~,str] = calllib(hid.slib,'hid_error',hid.handle); end function str = enumerate(hid,vendorID,productID) %hidapi.enumerate Enumerates the hid object % - % str = hid.enumerate(vendorID,productID) enumerates the hid + % str = hid.enumerate(vendorID,productID) enumerates the hid % device with the given vendorID and productID and returns a % string with the returned hid information. % @@ -382,8 +519,21 @@ classdef hidapi < handle if hid.debug > 0 fprintf('hidapi enumerate\n'); end - % enumerate the hid devices + + % Enumerate the hid devices str = calllib(u.slib,'hid_enumerate',uint16(vendorID),uint16(productID)); end - end + end +end + + +function valid = isLibPointerValid(handle) + %isHandleValid Check whether hid.handle is valid libpointer + + valid = 0; + if ~isempty(handle) + if isa(handle, 'handle') && ~isNull(handle) + valid = 1; + end + end end diff --git a/source/usbBrickIO.m b/source/usbBrickIO.m index 2c6de0c295a9287a538ce93fc46884ec014f6b85..06fd285c036c5086feb854475603e1da532de990 100644 --- a/source/usbBrickIO.m +++ b/source/usbBrickIO.m @@ -18,8 +18,6 @@ classdef usbBrickIO < BrickIO properties - % connection handle - handle % debug input debug = 0; % vendor ID (EV3 = 0x0694) @@ -32,8 +30,12 @@ classdef usbBrickIO < BrickIO nWriteBuffer = 1024; end + properties (Access = 'protected') + % connection handle + handle + end + methods - function brickIO = usbBrickIO(varargin) %usbBrickIO.usbBrickIO Create a usbBrickIO object % @@ -47,16 +49,42 @@ classdef usbBrickIO < BrickIO if nargin == 0 brickIO.debug = 0; - end - if nargin > 0 + else brickIO.debug = varargin{1}; end + if brickIO.debug > 0 fprintf('usbBrickIO init\n'); end - % create the usb handle - brickIO.handle = hidapi(0,brickIO.vendorID,brickIO.productID,brickIO.nReadBuffer,brickIO.nWriteBuffer); - % open the brick Io connection + + % Create the usb handle + try + brickIO.handle = hidapi(0,brickIO.vendorID,brickIO.productID, ... + brickIO.nReadBuffer,brickIO.nWriteBuffer); + catch ME + if ~isempty(strfind(ME.identifier, 'InvalidParameterOrFileMissing')) + % Throw a clean InvalidParameterOrFileMissing to avoid confusion in upper layers + msg = ['Couldn''t load hidapi-library for USB connection due to a ' ... + 'missing file. Make sure the correct hidapi-library and its ' ... + 'corresponding thunk- and proto-files are available.']; + id = [ID(), ':', 'InvalidParameterOrFileMissing']; + throw(MException(id, msg)); + elseif ~isempty(strfind(ME.identifier, 'LoadingLibraryError')) + % Throw a clean LoadingLibraryError to avoid confusion in upper layers + msg = 'Failed to load hidapi-library for USB connection.'; + id = [ID(), ':', 'LoadingLibraryError']; + throw(MException(id, msg)); + else + % Throw combined error because error did not happen due to known reasons... + msg = 'Unknown error occurred while trying to load the HIDAPI-lib for USB.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); + end + end + + % Open the brick IO connection brickIO.open; end @@ -68,11 +96,15 @@ classdef usbBrickIO < BrickIO if brickIO.debug > 0 fprintf('usbBrickIO delete\n'); end - % delete the usb handle - delete(brickIO.handle) + + % Disconnect + try + brickIO.close; + catch + % Connection already closed (probably due to an error) - do nothing + end end - % open the brick IO connection function open(brickIO) %usbBrickIO.open Open the usbBrickIO object % @@ -82,8 +114,25 @@ classdef usbBrickIO < BrickIO if brickIO.debug > 0 fprintf('usbBrickIO open\n'); end - % open the usb handle - brickIO.handle.open; + + % Open the usb handle (MMI: and handle possible errors) + try + brickIO.handle.open; + catch ME + if ~isempty(strfind(ME.identifier, 'CommError')) + % Throw a clean CommError to avoid confusion in upper layers + msg = 'Failed to open connection to Brick via USB.'; + id = [ID(), ':', 'CommError']; + throw(MException(id, msg)); + else + % Throw combined error because error did not happen due to known reasons... + msg = 'Unknown error occurred while trying to connect to the Brick via USB.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); + end + end end function close(brickIO) @@ -94,8 +143,18 @@ classdef usbBrickIO < BrickIO if brickIO.debug > 0 fprintf('usbBrickIO close\n'); end - % close the usb handle - brickIO.handle.close; + + try + % Close the usb handle + brickIO.handle.close; + catch ME + % Throw combined error because error did not happen due to known reasons... + msg = 'Unknown error occurred while closing the USB connection.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); + end end function rmsg = read(brickIO) @@ -111,28 +170,38 @@ classdef usbBrickIO < BrickIO if brickIO.debug > 0 fprintf('usbBrickIO read\n'); end - % read from the usb handle - rmsg = brickIO.handle.read; - % (MMI) Sometimes, right after connecting via usb, the returned packets are of no - % recognized format. To avoid dealing with those for now, they are simply discarded - % and a new packet is requested. - % This workaround is NOT safe as you can see. It is just temporary and works most - % of the times. - for i=0:5 - if rmsg(5) ~= 2 && rms(5) ~= 4 - rmsg = brickIO.handle.read; + % Read from the usb handle + try + rmsg = brickIO.handle.read; + catch ME + if ~isempty(strfind(ME.identifier, 'CommError')) + % Throw a clean CommError to avoid confusion in upper layers + msg = 'Failed to read data from the Brick via USB due to connection-error.'; + id = [ID(), ':', 'CommError']; + throw(MException(id, msg)); + elseif ~isempty(strfind(ME.identifier, 'InvalidHandle')) + % Throw a clean InvalidHandle to avoid confusion in upper layers + msg = 'Failed to read data from the Brick via USB due to invalid handle to USB-device.'; + id = [ID(), ':', 'InvalidHandle']; + throw(MException(id, msg)); + else + % Throw combined error because error did not happen due to known reasons... + msg = 'Unknown error occurred while reading data from the Brick via USB.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); end - pause(0.005); end - % get the number of read bytes - nLength = double(typecast(uint8(rmsg(1:2)),'uint16')); - % format the read message (2 byte length plus message) - if nLength+2 < length(rmsg) % If not, there has been an error -> dealt with in higher layer - rmsg = rmsg(1:nLength+2); + % Get the number of read bytes + pLength = double(typecast(uint8(rmsg(1:2)),'uint16')) + 2; + + % Format the read message (2 byte length plus message) + if pLength < length(rmsg) + rmsg = rmsg(1:pLength); end - end function write(brickIO,wmsg) @@ -147,8 +216,30 @@ classdef usbBrickIO < BrickIO if brickIO.debug > 0 fprintf('usbBrickIO write\n'); end - % write to the usb handle using report ID 0 - brickIO.handle.write(wmsg,0); + + % Write to the usb handle using report ID 0 + try + brickIO.handle.write(wmsg,0); + catch ME + if ~isempty(strfind(ME.identifier, 'CommError')) + % Throw a clean CommError to avoid confusion in upper layers + msg = 'Failed to send data to Brick via USB due to connection-error.'; + id = 'RWTHMindstormsEV3:usbBrickIO:write:CommError'; + throw(MException(id, msg)); + elseif ~isempty(strfind(ME.identifier, 'InvalidHandle')) + % Throw a clean InvalidHandle to avoid confusion in upper layers + msg = 'Failed to send data to Brick via USB due to invalid handle to USB-device.'; + id = [ID(), ':', 'InvalidHandle']; + throw(MException(id, msg)); + else + % Throw combined error because error did not happen due to known reasons... + msg = 'Unknown error occurred while sending data to the Brick via USB.'; + id = [ID(), ':', 'UnknownError']; + newException = MException(id, msg); + newException = addCause(newException, ME); + throw(newException); + end + end end end -end \ No newline at end of file +end diff --git a/source/wfBrickIO.m b/source/wfBrickIO.m index e469c9b1931152e35f7befc18099d120ecdee0d5..b234c566e96d57a98539f5b2182997912d2f317d 100644 --- a/source/wfBrickIO.m +++ b/source/wfBrickIO.m @@ -24,8 +24,6 @@ classdef wfBrickIO < BrickIO properties - % connection handle - handle % debug input debug = 0; % socket input strem @@ -40,6 +38,11 @@ classdef wfBrickIO < BrickIO serialNum = '0016533dbaf5'; end + properties (Access = 'protected') + % connection handle + handle + end + methods function brickIO = wfBrickIO(debug,addr,port,serialNum)