Select Git revision
createLinkedHDF5.m
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
EV3.m 17.30 KiB
classdef EV3 < handle
% 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.
%
% Properties:
% Standard
% motorA[,B,C,D]
% sensor1[,2,3,4]
% debug - Debug turned on or off
% batteryMode - Mode for reading battery charge
% Dependent
% batteryValue - Current battery charge level
% get-only
% isConnected - Is virtual brick connected to physical one?
% commInterface - Interface to communication layer
%
% Methods:
% General
% EV3
% connect - Connects EV3-object and its Motors and Sensors to physical brick via bt or usb
% disconnect - Disconnects EV3-object and its Motors and Sensors from physical brick
% setProperties - Set multiple EV3 properties at once using MATLAB's inputParser
% Brick functions
% beep - Plays a 'beep' tone on brick
% playTone - Plays tone on brick
% stopTone - Stops tone currently played
% tonePlayed - Tests if a tone is currently played
%
%
% Notes
% * Creating multiple EV3 objects and connecting them to different physical bricks has not
% been thoroughly tested yet, but seems to work on a first glance.
% * When referring to an instance of this class the term 'virtual brick' is used from now
% on. The LEGO EV3 brick itself is referred to as 'physical brick'.
%
% Example
% % This small example connects to the brick, starts motorA with power 50 and time-limit
% % of 5 seconds. As long as it's running, sensor 1 is read and its readings are plotted
% % the end.
%
%%%%%%%%%%
% brickObject = EV3('debug', 'on', 'batteryMode', 'Voltage');
% brickObject.connect('bt', 'serPort', '/dev/rfcomm0');
%
% m = brickObject.motorA;
% s = brickObject.sensor1;
%
% m.setProperties('Power', 50, 'BrakeMode', 'Brake', 'TachoLimitMode', 'Time', ...
% 'TachoLimit', 5000);
%
% if s.type ~= DeviceType.Color
% fprintf('Please connect EV3-color-sensor to port 1!\n');
% else
% s.mode = DeviceMode.Color.Ambient;
%
% readings = [];
% time = [];
%
% tic;
% m.start();
% pause(0.5);
% while m.isRunning
% readings = [readings, s.value];
% time = [time toc];
% end
%
% plot(time, readings);
% end
%
% brickObject.disconnect();
% brickObject.delete();
% clear all
%%%%%%%%%%
%
% Signature
% Author: Tim Stadtmann
% Date: 2016/05/19
% Updated: 2016/08/15
properties % Standard properties
%debug - Debug turned on or off
% In debug mode 1 (debug=1/'on'/true), everytime a command is passed to the sublayer
% ('communication layer'), there is feedback in the console about what command has been
% called. Debug mode 2 (debug=2) enables debug mode in the lower layers. Each packet
% sent and received is printed to the console.
% -> Any valid boolean (0/1/'on'/'off'/true/false) or 2 (for enabling debugging in
% lower layers)
debug;
%% Modi
%batteryMode - Mode for reading battery charge
% -> 'Percentage' / 'Voltage'
% See also EV3.BATTERYVALUE
batteryMode;
end
properties (Dependent) % Parameters to be read directly from physical brick
%batteryValue - Current battery charge
% Depending on batteryMode, the reading is either in percentage or voltage.
% See also EV3.BATTERYMODE
batteryValue;
end
properties (SetAccess = 'private') % Read-only properties that are set internally
%isConnected - Virtual brick connected to physical one?
isConnected = false;
%commInterface - Interface to communication layer
commInterface = 0;
%% Motors and Sensors
motorA;
motorB;
motorC;
motorD;
sensor1;
sensor2;
sensor3;
sensor4;
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();
%
% 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
%% Display
function display(ev3)
% WIP
warning('off','all');
builtin('disp', ev3);
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
end
end