Something went wrong on our end
Select Git revision
-
Tim Stadtmann authoredTim Stadtmann authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
EV3.m 17.81 KiB
classdef EV3 < MaskedHandle
% 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
% commands to it.
%
% 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 EV3 brick itself is referred to as 'physical brick'.
%
% Signature
% Author: Tim Stadtmann
% Date: 2016/05/19
% Updated: 2016/08/15
properties % Standard properties
%debug - [Writable] Debug turned on or off (0/1/off/on/'false'/'true'/2)
% 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.
debug;
%batteryMode - [Writable] Mode for reading battery charge ('Percentage'/'Voltage')
% See also EV3.BATTERYVALUE
batteryMode;
end
properties (Dependent) % Parameters to be read directly from physical brick
%batteryValue - [Read-only] 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 - [Read-only] Virtual brick connected to physical one?
% See also EV3.CONNECT, EV3.DISCONNECT
isConnected = false;
%motorA - [Read-only]
% See also MOTOR
motorA;
%motorB - [Read-only]
% See also MOTOR
motorB;
%motorC - [Read-only]
% See also MOTOR
motorC;
%motorD - [Read-only]
% See also MOTOR
motorD;
%sensor1 - [Read-only]
% See also SENSOR
sensor1;
%sensor2 - [Read-only]
% See also SENSOR
sensor2;
%sensor3 - [Read-only]
% See also SENSOR
sensor3;
%sensor4 - [Read-only]
% See also SENSOR
sensor4;
end
properties (Access = 'private')
%commInterface - Interface to communication layer
commInterface = 0;
end
properties (Hidden, Access = 'private') % Hidden properties for internal use only
init = true; % Indicates 'init-phase' (Set to 1 as long as constructor is running)
end
methods % Standard methods
%% Constructor
function ev3 = EV3(varargin)
%EV3 Sets properties of EV3-object and creates Motor- and Sensor-objects with default
% parameters.
%
% Arguments
% * varargin: see EV3::setProperties(ev3, varargin)
%
ev3.setProperties(varargin{:});
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>=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 = false;
end
function delete(ev3)
%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
% * '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('bt', 'serPort', '/dev/rfcomm0');
% b = EV3(); b.connect('usb', 'beep', 'on', );
%
% Check connection
if ev3.isConnected
if isCommInterfaceValid(ev3.commInterface)
error('EV3::connect: Already connected.');
else
warning(['EV3::connect: EV3.isConnected is set to ''True'', but ',...
'comm handle is invalid. Deleting invalid handle and ' ,...
'resetting EV3.isConnected now...']);
ev3.commInterface = 0;
ev3.isConnected = false;
end
end
if nargin < 2
error('EV3::connect: Wrong number of input arguments.');
end
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
else
beep = false;
end
% Try to connect
try
% Connect to physical brick
% -> 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.commInterface);
ev3.motorB.connect(ev3.commInterface);
ev3.motorC.connect(ev3.commInterface);
ev3.motorD.connect(ev3.commInterface);
% Connect sensors
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 = false;
if isCommInterfaceValid(ev3.commInterface) && ev3.commInterface ~= 0
ev3.commInterface.delete();
ev3.commInterface = 0;
end
rethrow(ME);
end
end
function disconnect(ev3)
%disconnect Disconnects EV3-object and its Motors and Sensors from physical brick.
%
% Example
% b = EV3();
% b.connect('bt', 'serPort', '/dev/rfcomm0');
% % do stuff
% b.disconnect();
%
% Reset motors and sensors before disconnecting
ev3.resetPhysicalBrick();
% Disconnect motors and sensors
% -> set references to comm handle to 0
ev3.motorA.disconnect();
ev3.motorB.disconnect();
ev3.motorC.disconnect();
ev3.motorD.disconnect();
ev3.sensor1.disconnect();
ev3.sensor2.disconnect();
ev3.sensor3.disconnect();
ev3.sensor4.disconnect();
% Delete handle to comm-interface
if isCommInterfaceValid(ev3.commInterface) && ev3.commInterface ~= 0
ev3.commInterface.delete();
end
ev3.commInterface = 0;
ev3.isConnected = false;
end
%% Device functions
function stopAllMotors(ev3)
%stopAllMotors Sends a stop-command to all motor-ports
if ~ev3.isConnected
stopAllMotors(['EV3::beep: Brick-Object not connected physical brick. ',...
'You have to call ev3.connect(...) first!']);
end
ev3.commInterface.outputStopAll();
end
%% Sound functions
function beep(ev3)
%beep Plays a 'beep' tone on brick.
%
% Notes
% * This equals playTone(10, 1000, 100) (Wraps the same opCode in comm-layer)
%
% Example
% b = EV3();
% b.connect('bt', 'serPort', '/dev/rfcomm0');
% b.beep();
%
if ~ev3.isConnected
error(['EV3::beep: Brick-Object not connected physical brick. ',...
'You have to call ev3.connect(...) first!']);
end
ev3.commInterface.beep();
if ev3.debug
fprintf('(DEBUG) EV3::beep: Called beep on brick\n');
end
end
function playTone(ev3, volume, frequency, duration)
%playTone Plays tone on brick.
%
% Arguments
% * volume (0...100)
% * frequency (250...10000)
% * duration (>0, in milliseconds)
%
% Example
% b = EV3();
% b.connect('bt', 'serPort', '/dev/rfcomm0');
% b.playTone(50, 5000, 1000); % Plays tone with 50% volume and 5000Hz for 1
% % second.
%
if ~ev3.isConnected
error(['EV3::isConnected: Brick-Object not connected physical brick. ',...
'You have to call ev3.connect(...) first!']);
end
ev3.commInterface.soundPlayTone(volume, frequency, duration);
if ev3.debug
fprintf('(DEBUG) EV3::beep: Called soundPlayTone on brick\n');
end
end
function stopTone(ev3)
%stopTone Stops tone currently played.
%
% Example
% b = EV3();
% b.connect('bt', 'serPort', '/dev/rfcomm0');
% b.playTone(10,100,100000000); % Accidentally given wrong tone duration.
% b.stopTone(); % Stops tone immediately.
%
if ~ev3.isConnected
error(['EV3::stopTone: Brick-Object not connected physical brick. ',...
'You have to call ev3.connect(...) first!']);
end
ev3.commInterface.soundStopTone();
if ev3.debug
fprintf('(DEBUG) EV3::beep: Called soundStopTone on brick\n');
end
end
function status = tonePlayed(ev3)
%tonePlayed Tests if tone is currently played.
%
% Output
% * status: True for a tone being played
%
% Example
% b = EV3();
% b.connect('bt', 'serPort', '/dev/rfcomm0');
% b.playTone(10, 100, 1000);
% pause(0.5);
% b.tonePlayed() -> Outputs 1 to console.
%
if ~ev3.isConnected
error(['EV3::tonePlayed: Brick-Object not connected physical brick. ',...
'You have to call ev3.connect(...) first!']);
end
status = ev3.commInterface.soundTest;
if ev3.debug
fprintf('(DEBUG) EV3::beep: Called soundTest on brick\n');
end
end
%% Setter
function set.commInterface(ev3, comm)
if ~isCommInterfaceValid(comm)
error('EV3::set.commInterface: Handle to Brick-object not valid.');
else
ev3.commInterface = comm;
end
end
function set.batteryMode(ev3, batteryMode)
validModes = {'Voltage', 'Percentage'};
if ~ischar(batteryMode) || ~ismember(batteryMode, validModes)
error('EV3::set.batteryMode: Given parameter is not a valid battery mode.');
else
ev3.batteryMode = batteryMode;
end
end
function set.debug(ev3, debug)
if ~isBool(debug) && debug ~= 2
error('EV3::set.debug: Given parameter is not a bool.');
end
ev3.debug = str2bool(debug);
if ev3.isConnected
ev3.commInterface.debug = (ev3.debug >= 2);
end
end
function setProperties(ev3, varargin)
%setProperties Set multiple EV3 properties at once using MATLAB's inputParser.
%
% Arguments
% * 'debug', 0/1/'on'/'off'/'true'/'false'/2
% * 'batteryMode', 'Voltage'/'Percentage': Mode in which batteryValue will be read.
%
% Example
% b = EV3();
% 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 = false;
defaultBatteryMode = 'Percentage';
else
defaultDebug = ev3.debug;
defaultBatteryMode = ev3.batteryMode;
end
% Add parameter
p.addOptional('debug', defaultDebug);
p.addOptional('batteryMode', defaultBatteryMode);
% Parse...
p.parse(varargin{:});
% Set properties
ev3.batteryMode = p.Results.batteryMode;
ev3.debug = p.Results.debug;
end
%% Getter
function bat = get.batteryValue(ev3)
if ~ev3.isConnected
warning('EV3::getBattery: EV3-Object not connected to physical EV3.');
bat = 0;
return;
end
bat = ev3.getBattery();
end
function display(ev3)
displayProperties(ev3);
fprintf('\n\tDevices\n');
props = properties(ev3);
warning('off', 'all'); % Turn off warnings while reading values
for i = 1:length(props)
p = props{i};
if strcmp(class(ev3.(p)),'Sensor') || strcmp(class(ev3.(p)), 'Motor')
fprintf('\t%15s [Type: %s]\n', p, char(ev3.(p).type));
end
end
warning('on', 'all');
end
% function display(ev3)
% warning('off','all');
% %builtin('disp', ev3);
%
%
% props = properties(ev3); % Save list with property names as strings
% fprintf(' <a href="matlab:helpPopup EV3">EV3</a> with properties:\n\n');
% for i = 1:length(props)
% p = props{i};
%
% if isa(ev3.(p), 'handle') && ev3.(p).isvalid % Valid handle?
% if isa(ev3.(p), 'Brick') % Communication interface?
% fprintf('\t%15s: Brick-object (Interface for comm-layer)\n');
% else % Motor or Sensor object?
% fprintf('\t%15s: %s-object for port %s\n', p, class(ev3.(p)), ...
% port2str(class(ev3.(p)), ev3.(p).port));
% end
% elseif isnumeric(ev3.(p))
% fprintf('\t%15s: %d\n', p, ev3.(p));
% elseif ischar(ev3.(p))
% fprintf('\t%15s: %s\n', p, ev3.(p));
% else
% warning('EV3::display: Unsuspected parameter type.'); % Not really useful
% end
% end
%
% warning('on','all');
%
% end
end
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!']);
end
if strcmpi(ev3.batteryMode, 'Percentage')
bat = ev3.commInterface.uiReadLbatt();
if ev3.debug
fprintf('(DEBUG) EV3::getBattery: Called uiReadLBatt.\n');
end
elseif strcmpi(ev3.batteryMode, 'Voltage')
bat = ev3.commInterface.uiReadVbatt();
if ev3.debug
fprintf('(DEBUG) EV3::getBattery: Called uiReadVBatt.\n');
end
end
end
function resetPhysicalBrick(ev3)
sensors = {'sensor1', 'sensor2', 'sensor3', 'sensor4'};
motors = {'motorA', 'motorB', 'motorC', 'motorD'};
for i = 1:4
motor = motors{i};
ev3.(motor).resetPhysicalMotor();
end
for i = 1:4
sensor = sensors{i};
ev3.(sensor).resetPhysicalSensor();
end
end
end
end