classdef Equipment < handle %EQUIPMENT Summary of this class goes here % Detailed explanation goes here properties (SetAccess=protected) name tcp channel locked end properties (Dependent, SetAccess=private) error end properties (Hidden) debugbool end methods function ecq = Equipment(ipAddress,port,channel) %EQUIPMENT Construct an instance of this class. % This functions opens the required TCP connection % for this device. Channel is GPIB-channel on prologix % converter if used, otherwise set channel to -1. % Name is queried from device via '*IDN?' command. ecq.tcp = Equipment.getTCP(ipAddress,port); ecq.locked = false; ecq.channel = channel; ecq.name = ecq.idn(); ecq.debugbool = false; if channel >= 0 ecq.setPrologix; end end function id = idn(ecq) %IDN Queries identificantion from device via '*IDN?'. id = ecq.query_unsafe('*idn?'); end function clear(ecq) %CLEAR Sends clear command to device via '*CLS'. %This function also clears the TCP inputbuffer; ecq.write_unsafe('*cls'); flushinput(ecq.tcp); end function reset(ecq) %RESET Sends reset command to device via '*RST'. ecq.write_unsafe('*rst'); end function debug(ecq) ecq.debugbool = ~ecq.debugbool; end function flush(ecq) flushinput(ecq.tcp); flushoutput(ecq.tcp); end function opc(ecq) %OPC executes 'operation complete query' to device. %Function holds untill device returns '1'. Must be %used to avoid interrupting busy device. ecq.write_unsafe('*OPC?'); for i = 1:10 ack = ecq.read; if strcmp(ack(1),'1') return end end error('Device is not ready'); end function unlock(ecq) %UNLOCK Sets the device back to 'local control' %This function only supports devices via the Prologix. if ecq.channel >= 0 fprintf(ecq.tcp,'++loc'); ecq.locked = false; end end function lock(ecq) %UNLOCK Sets the device back to 'remote control' %This function only supports devices via the Prologix. if ecq.channel >= 0 fprintf(ecq.tcp,'++llo'); ecq.locked = true; else warning('Device does not support locking') end end function beep(ecq) %BEEP Sends the beep command to the device ecq.write('SYST:BEEP') end function write(ecq,message) %WRITE Sends a command to the channel. %The function will first check if the device is ready to %recieve a command via the OPC function. Then send the command %and eventually check if the device has thrown an error via %the ERROR function. % %See also QUERY ecq.opc; ecq.write_unsafe(message); ecq.error; end function data = bin_read(ecq,datalength,type,typesize) try if nargin < 4 typesize = 8; end data = zeros(1,datalength); totalread = 0; maxlength = floor(ecq.tcp.InputBufferSize/typesize)-16; while datalength > 0 readlength = min(datalength,maxlength); lastcount = 0; [lastdata,lastcount] = fread(ecq.tcp,[1,readlength],type); if lastcount == 0 error('Data stream from device shorter than expected'); end data(totalread+1:totalread+lastcount) = lastdata; totalread = totalread+lastcount; datalength = datalength - lastcount; end flushinput(ecq.tcp) ecq.clear catch ME flushinput(ecq.tcp); rethrow(ME); end end function data = bin_read_float(ecq,datalength) data = bin_read(ecq,datalength,'float',4); end function output = read(ecq) %READ Extracts recieved data from the TCP-buffer. %This function sends the '++read'-command to the Prologix if %needed. Will always read the TCP-buffer in matlab. if ecq.channel >= 0 ecq.write_unsafe('++read'); end output = fscanf(ecq.tcp); if ecq.debugbool fprintf(['<< ',output]); end end function output = query(ecq,message) %QUERY Sends command to the device and read the respond. %The command is send via the WRITE and the respond is collected %from the device. % %See also WRITE, READ ecq.opc; output = ecq.query_unsafe(message); ecq.error; end function errorlist = get.error(ecq) %ERROR Checks for errors on device. If any, throws a warning. %The function will pull all errors from the device error stack. %This is up to a maximum of 20 errors. If errors have occured %the function will throw a warning with all the error messages. for i = 1:20 output = ecq.query_unsafe('SYSTem:ERRor?'); %Query error message from device. % [msgstr, msgid] = lastwarn; % if strcmp(msgid,'instrument:fscanf:unsuccessfulRead') % error(['Lost connection with ' ecq.name]); % end errorlist(i,1:(length(output))) = output; %Store the error message in the errorlist array. %GPIB protocol states that the error code '0' means no %error. The for loop will break if the last recieved error %code is zero. if str2double(regexp(output,'\d*','match','once'))==0 %Check if last recieved error code is '0' % If the 'no error' message is the only error then no % warning will be trown. However if there is more than % one error in the list. The function gives a warning % with all error messages. if i>1 %check for more than one error message. warning('Error from device: %s %s',ecq.name,reshape(errorlist(1:i-1,:)',1,[])); end ecq.clear; break; end end end end methods% (Access = protected) function write_unsafe(ecq,message) %WRITE_UNSAFE Sends command to device. %This function does not check if device is ready or check if an %error occured after writing. If possible one should always use %the WRITE function. % %See also WRITE, QUERY if ecq.channel >= 0 fprintf(ecq.tcp,['++addr ', num2str(ecq.channel)]); if ecq.debugbool fprintf(['>> ++addr ', num2str(ecq.channel),'\n']); end end fprintf(ecq.tcp, message); if ecq.debugbool fprintf(['>> ',message,'\n']); end end function write_noerror(ecq,message) %WRITE Sends a command to the channel. %The function will first check if the device is ready to %recieve a command via the OPC function. Then send the command. % %See also WRITE ecq.opc; ecq.write_unsafe(message); end function output = query_unsafe(ecq,message) %QUERY_UNSAFE Sends command to device and reads device output. %This function does not check if device is ready or check if an %error occured after writing. If possible one should always use %the QUERY function. % %See also QUERY, WRITE, READ ecq.write_unsafe(message); output = read(ecq); end end methods (Access = protected, Hidden) function setPrologix(ecq) ecq.write_unsafe('++mode 1'); %set device in controller mode ecq.write_unsafe('++auto 0'); %disable automatic datapull. this avoids errors on equipment. end function delete(ecq) %DELETE Destructs the current object. ecq.unlock; Equipment.getTCP(ecq.tcp.RemoteHost,-ecq.tcp.RemotePort); end end methods (Static) function tcpobject = getTCP(ipAddress,port) %GETTCP Returns TCP-object for TCP-connection. %Function makes a TCP-connection for specific ipAddress and %port. If TCP-connection already exist no new connection is %made and only existing handle is returned. %For existing connections the function will store the amount of %handles in use. Via the DELETE function the connection will be %closed if the connection is not in use anymore. %The connection will be removed if the port is negative number. persistent tcpconnection; %make variable persistent to share tcp-handles across multiple function calls. if isempty(tcpconnection) %if the tcpconnection is empty (first time function %call) make a struct in the variable. tcpconnection = struct; end [ipname,ipAddress] = Equipment.ip2structname(ipAddress,abs(port)); %Get a structname and a cleaned ipaddress. if port > 0 %if port number is positive a connection is made. if ~isfield(tcpconnection, ipname) %check if the handle is already made before. tcpconnection.(ipname).tcp = tcpip(ipAddress,port); %Make TCP-connection tcpconnection.(ipname).nopen = 1; %Set number of connections in use to 1 tcpconnection.(ipname).tcp.InputBufferSize = 2^24; %make really large buffer size 64MB. To acquire complete waveforms. tcpconnection.(ipname).tcp.Timeout = 5; %set timeout to 5 seconds. fopen(tcpconnection.(ipname).tcp); %Open the TCP-connection else %If connection already exist. Increase number of connections in use by 1. tcpconnection.(ipname).nopen = tcpconnection.(ipname).nopen + 1; end tcpobject = tcpconnection.(ipname).tcp; %return the TCP-connection handle. elseif port < 0 %If the portnumber is negative the connection is removed. if ~isfield(tcpconnection, ipname) return; elseif tcpconnection.(ipname).nopen > 1 %If more than one object uses this tcp-handle. Decrease %the number of connections in use by 1. tcpconnection.(ipname).nopen = tcpconnection.(ipname).nopen - 1; %Decrease the number by 1. else %If only one handle uses this connection. The %connection is closed and the field is removed from the %tcpconnection struct. flushinput(tcpconnection.(ipname).tcp); fclose(tcpconnection.(ipname).tcp); tcpconnection = rmfield(tcpconnection,ipname); end else error([num2str(port),' is not a valid port number try a value between 1 and 65535.']); end end function bool = isnum(input) %ISNUM Tests if the input is numeric scalar bool = isnumeric(input)&&isscalar(input); end function num = forceNum(input) %FORCENUM Throws an error if the input is not numeric. if ~Equipment.isnum(input) error('Input should be a (single) number.'); end num = input; end function iptest(ipAddress) %IPTEST Checks if the given IPv4-address is valid. validip = regexp(ipAddress,'^(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]))$', 'once'); if isempty(validip) error('Invalid IP-address'); end end function [structname,cleanip] = ip2structname(ipAddress,port) %IP2STRUCTNAME Returns a structname and ip w/out leading zeros. %structname to store stuff in struct (especially for GETTCP). %cleanip is a shortened ip without leading zeros. cleanip = regexprep(ipAddress,'(?:(?<=\.)|^)(?:0+)(?=\d)',''); Equipment.iptest(cleanip); structname = matlab.lang.makeValidName(['ip',cleanip,'_',num2str(port)]); end end end