EV3.m 20.1 KB
Newer Older
Tim Stadtmann's avatar
Tim Stadtmann committed
1
classdef EV3 < MaskedHandle
='s avatar
= committed
2
3
4
5
    % High-level class to work with physical bricks.
    %
    % This is the 'central' class (from user's view) when working with this toolbox. It
    % delivers a convenient interface for creating a connection to the brick and sending
6
    % commands to it. An EV3-object creates 4 Motor- and 4 Sensor-objects, one for each port.
7
8
    %
    %
9
10
11
    % 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.
12
13
    %     * When an input argument of a method is marked as optional, the argument needs to be
    %       'announced' by a preceding 2nd argument, which is a string containing the name of the argument.
14
15
    %       For example, Motor.setProperties may be given a power-parameter. The syntax would be as
    %       follows: *brickObject.motorA.setProperties('power', 50);*
16
    %
='s avatar
= committed
17
    %
Tim Stadtmann's avatar
Tim Stadtmann committed
18
    %
19
    % Attributes:
20
21
22
23
24
25
26
27
    %     motorA (Motor): Motor-object interfacing port A. See also :class:`Motor`.
    %     motorB (Motor): Motor-object interfacing port B. See also :class:`Motor`.
    %     motorC (Motor): Motor-object interfacing port C. See also :class:`Motor`.
    %     motorD (Motor): Motor-object interfacing port D. See also :class:`Motor`.
    %     sensor1 (Sensor): Motor-object interfacing port 1. See also :class:`Sensor`.
    %     sensor2 (Sensor): Motor-object interfacing port 2. See also :class:`Sensor`.
    %     sensor3 (Sensor): Motor-object interfacing port 3. See also :class:`Sensor`.
    %     sensor4 (Sensor): Motor-object interfacing port 4. See also :class:`Sensor`.
Tim Stadtmann's avatar
Tim Stadtmann committed
28
    %     debug (numeric in {0,1,2}): Debug mode. *[WRITABLE]*
29
30
31
    %
    %         - 0: Debug turned off
    %         - 1: Debug turned on for EV3-object -> enables feedback in the console about what firmware-commands have been called when using a method
Tim Stadtmann's avatar
Tim Stadtmann committed
32
    %         - 2: Low-level-Debug turned on -> each packet sent and received is printed to the console
33
    %
34
35
    %     batteryMode (string in {'Percentage', 'Voltage'}): Mode for reading battery charge. See also :attr:`batteryValue`. *[WRITABLE]*
    %     batteryValue (numeric): Current battery charge. Depending on batteryMode, the reading is either in percentage or voltage. See also :attr:`batteryMode`. *[READ-ONLY]*
Tim Stadtmann's avatar
Tim Stadtmann committed
36
37
    %     isConnected (bool): True if virtual brick-object is connected to physical one. *[READ-ONLY]*
    %
38
39
40
41
42
43
44
45
46
47
48
49
50
    % :: 
    %
    %     Example:
    %         # This example expects a motor at port A and a (random) sensor at port 1
    %          brick = EV3();
    %          brick.connect('usb');
    %          motorA = brick.motorA;
    %          motorA.setProperties('power', 50, 'limitValue', 720);
    %          motorA.start();
    %          motorA.waitFor();
    %          disp(brick.sensor1.value);
    %          brick.beep();
    %          delete brick;
51
    %
52

53
54
55
    properties
        % batteryMode (string in {'Percentage', 'Voltage'}): Mode for reading battery charge. [WRITABLE]
        % See also BATTERYVALUE
56
        batteryMode;
57

58
        % debug (numeric in {0,1,2}): Debug mode. [WRITABLE]
59
        %     - 0: Debug turned off
60
        %     - 1: (High-level-) Debug turned on for EV3-object - enables feedback in the
61
        %          console about what firmware-commands have been called when using a method
62
        %     - 2: Low-level-Debug turned on - each packet sent and received is printed to the
63
        %          console
64
        debug;
='s avatar
= committed
65
66
67
    end

    properties (Dependent)  % Parameters to be read directly from physical brick
68
69
        % batteryValue (numeric): Current battery charge. Depending on batteryMode, the reading
        %     is either in percentage or voltage. [READ-ONLY]
Tim Stadtmann's avatar
Tim Stadtmann committed
70
        % See also BATTERYMODE
71
        batteryValue;
='s avatar
= committed
72
    end
73

74
75
    properties (SetAccess = private)  % Read-only properties that are set internally
        % isConnected (bool): True if virtual brick-object is connected to physical one. [READ-ONLY]
76
77
        isConnected = false;

78
        % motorA (Motor): Motor-object interfacing port A.
Tim Stadtmann's avatar
Tim Stadtmann committed
79
        % See also MOTOR
='s avatar
= committed
80
        motorA;
81
        % motorB (Motor): Motor-object interfacing port B.
Tim Stadtmann's avatar
Tim Stadtmann committed
82
        % See also MOTOR
='s avatar
= committed
83
        motorB;
84
        % motorC (Motor): Motor-object interfacing port C.
Tim Stadtmann's avatar
Tim Stadtmann committed
85
        % See also MOTOR
='s avatar
= committed
86
        motorC;
87
        % motorD (Motor): Motor-object interfacing port D.
Tim Stadtmann's avatar
Tim Stadtmann committed
88
        % See also MOTOR
='s avatar
= committed
89
        motorD;
90

91
        % sensor1 (Sensor): Sensor-object interfacing port 1.
Tim Stadtmann's avatar
Tim Stadtmann committed
92
        % See also SENSOR
='s avatar
= committed
93
        sensor1;
94
        % sensor2 (Sensor): Sensor-object interfacing port 2.
Tim Stadtmann's avatar
Tim Stadtmann committed
95
        % See also SENSOR
='s avatar
= committed
96
        sensor2;
97
        % sensor3 (Sensor): Sensor-object interfacing port 3.
Tim Stadtmann's avatar
Tim Stadtmann committed
98
        % See also SENSOR
='s avatar
= committed
99
        sensor3;
100
        % sensor4 (Sensor): Sensor-object interfacing port 4.
Tim Stadtmann's avatar
Tim Stadtmann committed
101
        % See also SENSOR
102
        sensor4;
='s avatar
= committed
103
    end
104

105
106
107
108
    properties (Access = private)
        % commInterface (CommunicationInterface): Interface to communication layer
        %     All commands sent to the Brick are created and written through this object. Each
        %     Motor- and Sensor-object has a reference to it.
Tim Stadtmann's avatar
Tim Stadtmann committed
109
110
        commInterface = 0;
    end
111

112
113
114
    properties (Hidden, Access = private)  % Hidden properties for internal use only
        % init (bool): Indicates init-phase (i.e. constructor is running).
        init = true;
='s avatar
= committed
115
    end
116

='s avatar
= committed
117
118
119
    methods  % Standard methods
        %% Constructor
        function ev3 = EV3(varargin)
120
            % Sets properties of EV3-object and creates Motor- and Sensor-objects with default
='s avatar
= committed
121
122
            % parameters.
            %
123
            % Arguments:
124
            %     varargin: see setProperties(ev3, varargin).
='s avatar
= committed
125
            %
126
            % See also SETPROPERTIES / :meth:`setProperties(ev3, varargin)`
127

='s avatar
= committed
128
            ev3.setProperties(varargin{:});
129

130
131
132
133
            ev3.motorA = Motor('A', ev3, 'Debug', ev3.debug>0);
            ev3.motorB = Motor('B', ev3, 'Debug', ev3.debug>0);
            ev3.motorC = Motor('C', ev3, 'Debug', ev3.debug>0);
            ev3.motorD = Motor('D', ev3, 'Debug', ev3.debug>0);
134
135


136
137
138
139
            ev3.sensor1 = Sensor('1', ev3, 'Debug', ev3.debug>0);
            ev3.sensor2 = Sensor('2', ev3, 'Debug', ev3.debug>0);
            ev3.sensor3 = Sensor('3', ev3, 'Debug', ev3.debug>0);
            ev3.sensor4 = Sensor('4', ev3, 'Debug', ev3.debug>0);
140

141
            ev3.init = false;
='s avatar
= committed
142
        end
143

144
        function delete(ev3)
145
            % Disconnects from physical brick and deletes this instance.
146

147
148
149
            if ev3.isConnected
                ev3.disconnect();
            end
150
        end
151
152

        %% Connection
='s avatar
= committed
153
        function connect(ev3, varargin)
154
            % Connects EV3-object and its Motors and Sensors to physical brick.
='s avatar
= committed
155
            %
156
157
            % Arguments:
            %     connectionType (string in {'bt', 'usb'}): Connection type
158
            %     serPort (string in {'/dev/rfcomm1', '/dev/rfcomm2', ...}): Path to serial port
159
160
            %         (necessary if connectionType is 'bt'). *[OPTIONAL]*
            %     beep (bool): If true, EV3 beeps if connection has been established. *[OPTIONAL]*
='s avatar
= committed
161
            %
162
163
164
165
166
167
168
169
170
171
            %
            % ::
            %
            %     Example:
            %          % Setup bluetooth connection via com-port 0 
            %          brick = EV3();
            %          brick.connect('bt', 'serPort', '/dev/rfcomm0');
            %          % Setup usb connection, beep when connection has been established
            %          brick = EV3();
            %          brick.connect('usb', 'beep', 'on', );
172
            %
173
            % See also ISCONNECTED / :attr:`isConnected`
174

='s avatar
= committed
175
            if ev3.isConnected
176
                if isCommInterfaceValid(ev3.commInterface)
177
178
179
                    % Warning lead to confusion of students. Since brick connection is still working
		            % even if calling connect() multiple times, it is disabled. 
		            % warning('EV3::connect: Already connected. Resetting connection now...');
Tim Stadtmann's avatar
Tim Stadtmann committed
180
                    ev3.disconnect();
='s avatar
= committed
181
182
                else
                    warning(['EV3::connect: EV3.isConnected is set to ''True'', but ',...
183
                             'comm handle is invalid. Deleting invalid handle and ' ,...
='s avatar
= committed
184
                             'resetting EV3.isConnected now...']);
185

186
187
                    ev3.commInterface = 0;
                    ev3.isConnected = false;
='s avatar
= committed
188
189
                end
            end
190

191
            if nargin < 2
Tim Stadtmann's avatar
Tim Stadtmann committed
192
193
                 error('EV3::connect: Wrong number of input arguments.');
            end
194

195
196
197
198
199
200
            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.');
='s avatar
= committed
201
                end
202
203
            else
                beep = false;
='s avatar
= committed
204
            end
205

='s avatar
= committed
206
            % Try to connect
207
            try
='s avatar
= committed
208
                % Connect to physical brick
209
210
211
                % -> Creating communication-handle implicitly establishes connection
                ev3.commInterface = CommunicationInterface(varargin{:}, 'debug', ev3.debug>=2);
                ev3.isConnected = true;
212

='s avatar
= committed
213
214
215
                if beep
                    ev3.beep();
                end
216
                
='s avatar
= committed
217
218
            catch ME
                % Something went wrong...
219
220
221
222
                ev3.isConnected = false;
                if isCommInterfaceValid(ev3.commInterface) && ev3.commInterface ~= 0
                    ev3.commInterface.delete();
                    ev3.commInterface = 0;
='s avatar
= committed
223
                end
224

Tim Stadtmann's avatar
Tim Stadtmann committed
225
                rethrow(ME);
='s avatar
= committed
226
227
            end
        end
228

='s avatar
= committed
229
        function disconnect(ev3)
230
            % Disconnects EV3-object and its Motors and Sensors from physical brick.
='s avatar
= committed
231
            %
232
233
            % Notes:
            %     * Gets called automatically when EV3-object is destroyed.
='s avatar
= committed
234
            %
235
236
237
238
239
240
241
242
            %
            % ::
            %
            %     Example:
            %          brick = EV3();
            %          brick.connect('bt', 'serPort', '/dev/rfcomm0');
            %          % do stuff 
            %          brick.disconnect();
Tim Stadtmann's avatar
Tim Stadtmann committed
243
244
245
246
247
248
249
250
251
252
            
            % Resetting needs a working connection in order to send reset-commands
            % to the Brick. If the connection has been aborted (e.g. by pulling the
            % USB-cord), the reset-methods would fail -> catch this error and for
            % now do nothing.
            try
                ev3.resetPhysicalBrick();
            catch ME
                % For now: ignore
            end
253

254
255
256
            % Delete handle to comm-interface
            if isCommInterfaceValid(ev3.commInterface) && ev3.commInterface ~= 0
                ev3.commInterface.delete();
257
            end
258
            ev3.commInterface = 0;
259

260
            ev3.isConnected = false;
='s avatar
= committed
261
        end
262

263
        %% Device functions
Tim Stadtmann's avatar
Tim Stadtmann committed
264
265
266
267
268
269
270
%         function stopAllMotors(ev3)
%             % Sends a stop-command to all motor-ports.
%             ev3.handleCommand(@outputStop, 0, MotorBitfield.MotorA ...
%                                              +MotorBitfield.MotorB ...
%                                              +MotorBitfield.MotorC ...
%                                              +MotorBitfield.MotorD, 0);
%         end
271
272
273
        
        function resetAllDeviceValues(ev3)
            % Resets values of all sensors on all ports (including tacho counter on motors)
274
            ev3.handleCommand(@inputDeviceClrAll, 0);
275
        end
276

='s avatar
= committed
277
278
        %% Sound functions
        function beep(ev3)
279
            % Plays a 'beep'-tone on brick.
='s avatar
= committed
280
            %
281
            % Notes:
282
            %     * This equals playTone(10, 1000, 100).
='s avatar
= committed
283
            %
284
285
286
287
288
289
290
            %
            % ::
            %
            %     Example:
            %          brick = EV3();
            %          brick.connect('bt', 'serPort', '/dev/rfcomm0');
            %          brick.beep();
='s avatar
= committed
291
            %
292
            ev3.handleCommand(@soundPlayTone, 10, 1000, 100);
='s avatar
= committed
293
        end
294

='s avatar
= committed
295
        function playTone(ev3, volume, frequency, duration)
296
            % Plays tone on brick.
='s avatar
= committed
297
            %
298
            % Arguments:
Tim Stadtmann's avatar
Tim Stadtmann committed
299
300
            %     volume (numeric in [0, 100]): in percent
            %     frequency (numeric in [250, 10000]): in Hertz
Tim Stadtmann's avatar
Tim Stadtmann committed
301
            %     duration (numeric > 0): in milliseconds
='s avatar
= committed
302
            %
303
304
305
306
307
308
309
            %
            % ::
            %
            %    Example:
            %         brick = EV3();
            %         brick.connect('bt', 'serPort', '/dev/rfcomm0');
            %         brick.playTone(40, 5000, 1000);  % Plays tone with 40% volume and 5000Hz for 1 second.
='s avatar
= committed
310
            %
311
            ev3.handleCommand(@soundPlayTone, volume, frequency, duration);
='s avatar
= committed
312
        end
313

='s avatar
= committed
314
        function stopTone(ev3)
315
            % Stops tone currently played.
='s avatar
= committed
316
            %
317
318
319
320
321
322
323
324
            %
            % ::
            %
            %     Example:
            %          brick = EV3();
            %          brick.connect('bt', 'serPort', '/dev/rfcomm0');
            %          brick.playTone(10,100,100000000);
            %          brick.stopTone();  % Stops tone immediately.
='s avatar
= committed
325
            %
326
            ev3.handleCommand(@soundStopTone);
='s avatar
= committed
327
        end
328

='s avatar
= committed
329
        function status = tonePlayed(ev3)
330
            % Tests if tone is currently played.
='s avatar
= committed
331
            %
332
            % Returns:
333
            %     status (bool): True if a tone is being played
334
            %
335
336
337
338
339
340
341
342
343
344
            %
            % ::
            %
            %     Example:
            %          brick = EV3();
            %          brick.connect('bt', 'serPort', '/dev/rfcomm0');
            %          brick.playTone(10, 100, 1000);
            %          pause(0.5); 
            %          % Small pause necessary since tone not startong immediately 
            %          brick.tonePlayed(); % -> Outputs 1 to console. 
='s avatar
= committed
345
            %
346
            status = ev3.handleCommand(@soundTest);
='s avatar
= committed
347
        end
348

='s avatar
= committed
349
        %% Setter
350
351
352
        function set.commInterface(ev3, comm)
            if ~isCommInterfaceValid(comm)
                error('EV3::set.commInterface: Handle to Brick-object not valid.');
='s avatar
= committed
353
            end
354

355
            ev3.commInterface = comm;
='s avatar
= committed
356
        end
357

='s avatar
= committed
358
359
        function set.batteryMode(ev3, batteryMode)
            validModes = {'Voltage', 'Percentage'};
360
            if ~ischar(batteryMode) || ~ismember(batteryMode, validModes)
='s avatar
= committed
361
362
                error('EV3::set.batteryMode: Given parameter is not a valid battery mode.');
            end
363

364
            ev3.batteryMode = batteryMode;
='s avatar
= committed
365
        end
366

='s avatar
= committed
367
368
369
370
        function set.debug(ev3, debug)
            if ~isBool(debug) && debug ~= 2
                error('EV3::set.debug: Given parameter is not a bool.');
            end
371

372
            ev3.debug = str2bool(debug);
373

374
            if ev3.isConnected
375
                ev3.commInterface.debug = (ev3.debug >= 2);
='s avatar
= committed
376
            end
377

Tim Stadtmann's avatar
Tim Stadtmann committed
378
379
380
381
            ev3.motorA.debug = (ev3.debug > 0);
            ev3.motorB.debug = (ev3.debug > 0);
            ev3.motorC.debug = (ev3.debug > 0);
            ev3.motorD.debug = (ev3.debug > 0);
382

Tim Stadtmann's avatar
Tim Stadtmann committed
383
384
385
386
            ev3.sensor1.debug = (ev3.debug > 0);
            ev3.sensor2.debug = (ev3.debug > 0);
            ev3.sensor3.debug = (ev3.debug > 0);
            ev3.sensor4.debug = (ev3.debug > 0);
='s avatar
= committed
387
        end
388

='s avatar
= committed
389
        function setProperties(ev3, varargin)
390
            % Set multiple EV3 properties at once using MATLAB's inputParser.
='s avatar
= committed
391
            %
392
            % Arguments:
Tim Stadtmann's avatar
Tim Stadtmann committed
393
394
            %     debug (numeric in {0,1,2}): see EV3.debug *[OPTIONAL]*
            %     batteryMode (string in {'Voltage'/'Percentage'}): see EV3.batteryMode *[OPTIONAL]*
='s avatar
= committed
395
            %
396
397
398
399
400
401
402
403
            %
            % ::
            %
            %     Example:
            %          brick = EV3();
            %          brick.connect('bt', 'serPort', '/dev/rfcomm0');
            %          brick.setProperties('debug', 'on', 'batteryMode', 'Voltage');
            %          % Instead of: b.debug = 'on'; b.batteryMode = 'Voltage';
='s avatar
= committed
404
            %
405
            % See also EV3.DEBUG, EV3.BATTERYMODE / :attr:`debug`, :attr:`batteryMode`
406

='s avatar
= committed
407
            p = inputParser();
408

='s avatar
= committed
409
410
            % Set default values
            if ev3.init
411
                defaultDebug = false;
='s avatar
= committed
412
413
414
415
416
                defaultBatteryMode = 'Percentage';
            else
                defaultDebug = ev3.debug;
                defaultBatteryMode = ev3.batteryMode;
            end
417

='s avatar
= committed
418
419
420
            % Add parameter
            p.addOptional('debug', defaultDebug);
            p.addOptional('batteryMode', defaultBatteryMode);
421

='s avatar
= committed
422
423
            % Parse...
            p.parse(varargin{:});
424

='s avatar
= committed
425
426
427
428
            % Set properties
            ev3.batteryMode = p.Results.batteryMode;
            ev3.debug = p.Results.debug;
        end
429

='s avatar
= committed
430
431
432
        %% Getter
        function bat = get.batteryValue(ev3)
            if ~ev3.isConnected
433
                warning(ID('noConnection'), 'EV3-Object not connected to physical EV3.');
434

='s avatar
= committed
435
436
437
                bat = 0;
                return;
            end
438

='s avatar
= committed
439
440
            bat = ev3.getBattery();
        end
441

='s avatar
= committed
442
        function display(ev3)
443
            % Displays EV3-properties and its devices.
444
445
446

            displayProperties(ev3);

Tim Stadtmann's avatar
Tim Stadtmann committed
447
448
            fprintf('\n\tDevices\n');
            props = properties(ev3);
449

Tim Stadtmann's avatar
Tim Stadtmann committed
450
451
452
            warning('off', 'all');  % Turn off warnings while reading values
            for i = 1:length(props)
                p = props{i};
453
454
455
456
                member = ev3.(p);
                
                if strcmp(class(member),'Sensor') || strcmp(class(member), 'Motor')
                    fprintf('\t%15s [Type: %s]\n', p, char(member.type));
Tim Stadtmann's avatar
Tim Stadtmann committed
457
458
459
                end
            end
            warning('on', 'all');
='s avatar
= committed
460
461
        end
    end
462

463
    methods (Access = private)  % Private brick functions that are wrapped by dependent params
464
        function varargout = handleCommand(ev3, command, varargin)
465
466
467
468
            % Execute a CommunicationInterface-method given as a handle
            %
            % As those methods have different, fixed numbers of output arguments, this quantity
            % has to be retrieved first.
469
470
471
472
473
474
475
476
477
            
            if ~ev3.isConnected
                msg = ['Brick-Object not connected physical brick. ',...
                       'You have to call connect(...) first!'];
                id = [ID(), ':', 'NotConnected'];
                
                throw(MException(id, msg));
            end            
            
478
            if ev3.debug
479
                fprintf('(DEBUG) Sending %s\n', func2str(command));
480
            end
481

482
            % Note: Arrg. MATLAB does not support nargout for class methods directly, so I have to
483
            % do this ugly workaround using strings. See
484
            % https://de.mathworks.com/matlabcentral/answers/96617-how-can-i-use-nargin-nargout-to-determine-the-number-of-input-output-arguments-of-an-object-method
485
            nOut = nargout(strcat('CommunicationInterface>CommunicationInterface.', func2str(command)));
486
            [varargout{1:nOut}] = ev3.dispatch(command, nOut, varargin{:});
487
        end
488
        
='s avatar
= committed
489
        function bat = getBattery(ev3)
490
491
            % Retrieve batteryValue from brick in current mode. (Wrapped by get.batteryValue)
            
='s avatar
= committed
492
            if strcmpi(ev3.batteryMode, 'Percentage')
493
                bat = ev3.handleCommand(@uiReadLbatt);
494
            else
495
                bat = ev3.handleCommand(@uiReadVbatt);
='s avatar
= committed
496
497
            end
        end
498

499
        function resetPhysicalBrick(ev3)
500
501
502
            % Resets Motors and Sensors.
            %
            % Notes:
Tim Stadtmann's avatar
Tim Stadtmann committed
503
            %     * Gets called automatically by EV3.disconnect.
504
505
            %
            % See also MOTOR.RESETPHYSICALMOTOR, SENSOR.RESETPHYSICALSENSOR
506

507
508
            sensors = {'sensor1', 'sensor2', 'sensor3', 'sensor4'};
            motors = {'motorA', 'motorB', 'motorC', 'motorD'};
509

510
511
512
            for i = 1:4
                motor = motors{i};
                sensor = sensors{i};
513
                ev3.(motor).resetPhysicalMotor();
514
515
516
                ev3.(sensor).resetPhysicalSensor();
            end
        end
='s avatar
= committed
517
    end
518
519
520
521
522
523
524
525
    
    methods (Access = {?Sensor, ?Motor})
        function varargout = dispatch(ev3, command, noOutputArguments, varargin)
            try
                [varargout{1:noOutputArguments}] = command(ev3.commInterface, varargin{:});
            catch ME
                if ~isempty(strfind(ME.identifier, 'CommError'))
                    warning('Lost connection to the Brick!');
526
                    %ev3.disconnect();
527
528
529
530
531
532
533
534
                else
                    warning('Something went wrong. Try to reset the connection.');
                end

                throw(ME);
            end
        end
    end
535
end