% ========================================================================
%> @brief LABJACK Connects and manages a LabJack U3 / U6
%>
%> Connects and manages a LabJack U3 / U6. To run this in OS X and Linux,
%> you need to install libusb and the exodriver. The easiest way to do this
%> is to use homebrew on OS X, a light-weight package manager to install
%> universal builds of these:
%>     bash$ brew install libusb exodriver 
%> For linux, see instructions on the labjack site.
%> With the exodriver installed, you create a new labJack object and you
%> can then send digital I/O commands and strobed words. labJack uses a 12bit
%> word across EIO and CIO, using CIO0 as the strobe bit so one has a
%> resultant 11bits of values in the strobed word. For example:
%>
%> >> lj = labJack('verbose',true) %open labJack verbosely
%> >> lj.toggleFIO(1) %toggle FIO1 between low and high
%> >> lj.timedTTL(3,200) % send a 200ms timed TTL pulse on FIO3
%>
%> Copyright ©2014-2022 Ian Max Andolina — released: LGPL3, see LICENCE.md
% ========================================================================
classdef labJack < handle
	
	properties
		%> friendly object name, setting this to 'null' will force silentMode=1
		name='labJack'
		%> what LabJack device to use; 3 = U3, 6 = U6
		deviceID = 6
		%> if more than one labJack connected, which one to open?
		device = 1
		%> silentMode allows one to gracefully fail methods without a labJack connected
		silentMode = false
		%> header needed by loadlib
		header = '/usr/local/include/labjackusb.h'
		%> the library itself
		library = '/usr/local/lib/liblabjackusb'
		%> how long to wait to read or write to the LabJack in
		%> milliseconds, 0 = infinity
		timeOut = 0
		%> do we log everything to the command window?
		verbose = false
		%> allows the constructor to run the open method immediately (default)
		openNow = true
		%> whether we read the response (true) or not (false) to commands we send. 
		%> The LabJack usually requires that we read the response and do a
		%> soft reset if no response is read, but it does allow us to not
		%> have to wait till we get a response, thus can speed methods like
		%> timedTTL up.
		readResponse = true
		%> strobeTime is time of strobe in unit multiples of timeShort: 16
		%> units ~=1ms on a U6 where timeShort is 64e-6 .If you use a U3
		%> this needs to be 8 for a 1ms pulse...
		strobeTime = 16
	end
	
	properties (SetAccess = private, GetAccess = public)
		%> function list returned from loading the exodriver
		functionList
		%> library version returned on first open
		version
		%> how many devices are connected
		devCount
		%> handle to the opened device itself
		handle = []
		%> have we successfully opened the labjack?
		isOpen = false
		%> an input buffer
		inp = []
		%> output buffer, normally the number of bytes written
		outp = []
		%> FIO0 state
		fio0 = 0
		%> FIO1 state
		fio1 = 0
		%> FIO2 state
		fio2 = 0
		%> FIO2 state
		fio3 = 0
		%> FIO4 state
		fio4 = 0
		%> FIO5 state
		fio5 = 0
		%> FIO6 state
		fio6 = 0
		%> FIO7 state
		fio7 = 0
		%> LED state, which is controllable only on the U3
		led = 1
		%> The raw strobed word command generated with prepareStrobe, sent with strobeWord
		command = []
		%> universal ID
		uuid = 0
		%> clock() dateStamp set on construction
		dateStamp = []
	end
	
	properties (SetAccess = private, Dependent = true)
		%> The fullName is the object name combined with its uuid and class name
		fullName
	end
	
	properties (SetAccess = private, GetAccess = private)
		% WaitShort time quantum is 64microseconds for U6 and 128microseconds for U3
		timeShort = 64e-6
		% WaitLong time quantum is 16ms for U6 and 32ms for U3
		timeLong  = 16e-3
		%>raw commands ,you can use labjackPython to find/verify these. These
		%>are easy ways to control the I/O, but this object also includes
		%>SetDIO which calculates the command denovo and can control CIO EIO
		%>and FIO. The raw commands below may be faster, but I've never been
		%>able to tell a difference when benchmarking them.
		fio0High = hex2dec(['15'; 'f8'; '03'; '00'; '18'; '01'; '00'; '0d'; '80'; '0b'; '80'; '00'])'
		fio1High = hex2dec(['17'; 'f8'; '03'; '00'; '1a'; '01'; '00'; '0d'; '81'; '0b'; '81'; '00'])'
		fio2High = hex2dec(['19'; 'f8'; '03'; '00'; '1c'; '01'; '00'; '0d'; '82'; '0b'; '82'; '00'])'
		fio3High = hex2dec(['1b'; 'f8'; '03'; '00'; '1e'; '01'; '00'; '0d'; '83'; '0b'; '83'; '00'])'
		fio4High = hex2dec(['1d'; 'f8'; '03'; '00'; '20'; '01'; '00'; '0d'; '84'; '0b'; '84'; '00'])'
		fio5High = hex2dec(['1f'; 'f8'; '03'; '00'; '22'; '01'; '00'; '0d'; '85'; '0b'; '85'; '00'])'
		fio6High = hex2dec(['21'; 'f8'; '03'; '00'; '24'; '01'; '00'; '0d'; '86'; '0b'; '86'; '00'])'
		fio7High = hex2dec(['23'; 'f8'; '03'; '00'; '26'; '01'; '00'; '0d'; '87'; '0b'; '87'; '00'])'
		fio0Low  = hex2dec(['94'; 'f8'; '03'; '00'; '98'; '00'; '00'; '0d'; '80'; '0b'; '00'; '00'])'
		fio1Low  = hex2dec(['96'; 'f8'; '03'; '00'; '9a'; '00'; '00'; '0d'; '81'; '0b'; '01'; '00'])'
		fio2Low  = hex2dec(['98'; 'f8'; '03'; '00'; '9c'; '00'; '00'; '0d'; '82'; '0b'; '02'; '00'])'
		fio3Low  = hex2dec(['9a'; 'f8'; '03'; '00'; '9e'; '00'; '00'; '0d'; '83'; '0b'; '03'; '00'])'
		fio4Low  = hex2dec(['9c'; 'f8'; '03'; '00'; 'a0'; '00'; '00'; '0d'; '84'; '0b'; '04'; '00'])'
		fio5Low  = hex2dec(['9e'; 'f8'; '03'; '00'; 'a2'; '00'; '00'; '0d'; '85'; '0b'; '05'; '00'])'
		fio6Low  = hex2dec(['a0'; 'f8'; '03'; '00'; 'a4'; '00'; '00'; '0d'; '86'; '0b'; '06'; '00'])'
		fio7Low  = hex2dec(['a2'; 'f8'; '03'; '00'; 'a6'; '00'; '00'; '0d'; '87'; '0b'; '07'; '00'])'
		ledIsON  = hex2dec(['05'; 'f8'; '02'; '00'; '0a'; '00'; '00'; '09'; '01'; '00'])' %only works on U3
		ledIsOFF = hex2dec(['04'; 'f8'; '02'; '00'; '09'; '00'; '00'; '09'; '00'; '00'])' %only works on U3
		%> Is our handle a valid one, this is a cache so we save a bit of
		%> time on calling the method each time
		vHandle = 0
		%> what properties are allowed to be passed on construction
		allowedProperties='timeOut|device|readResponse|deviceID|name|silentMode|verbose|openNow|header|library|strobeTime'
		%>document what our strobed word is actually setting, shown to user if verbose = true
		strobeComment = ''
		%> class name
		className = ''
		%> timedTTL cache
		timedTTLCache = []
	end
	
	%=======================================================================
	methods %------------------PUBLIC METHODS
	%=======================================================================
		
		% ===================================================================
		%> @brief Class constructor
		%>
		%> We use parseArgs to parse allowed properties on construction and also
		%> switch into silent mode and/or auto open the labjack connection.
		%>
		%> @param varargin are passed as a structure of properties which is
		%> parsed.
		%> @return instance of labJack class.
		% ===================================================================
		function obj = labJack(varargin)
			obj.className = class(obj);
			obj.dateStamp = clock();
			obj.uuid = num2str(dec2hex(floor((now - floor(now))*1e10)));
			if nargin>0
				obj.parseArgs(varargin,obj.allowedProperties);
			end
			if strcmpi(obj.name, 'null') || ispc %we were deliberately passed null, means go into silent mode
				obj.silentMode = true;
				obj.verbose = true;
				obj.salutation('CONSTRUCTOR Method','labJack running in silent mode...')
				obj.verbose = false;
			elseif obj.openNow == true
				obj.open
			end
			
		end
		
		% ===================================================================
		%> @brief Open the LabJack device
		%>
		%> Open the LabJack device
		% ===================================================================
		function open(obj)
			if obj.silentMode == false || isempty(obj.handle)
				if ismac == true || isunix == true
					if ~libisloaded('liblabjackusb')
						try
							loadlibrary(obj.library,obj.header);
						catch ME
							obj.salutation('open method',['Loading the exodriver library failed: ' ME.message],true);
							warning(['Loading the exodriver library failed: ' ME.message]);
							obj.version = ['Library Load FAILED: ' ME.message];
							obj.silentMode = true;
							obj.verbose = true;
							return
						end
					end
					obj.functionList = libfunctions('liblabjackusb', '-full'); %store our raw lib functions
					obj.version =  calllib('liblabjackusb','LJUSB_GetLibraryVersion');
					obj.devCount = calllib('liblabjackusb','LJUSB_GetDevCount',obj.deviceID);
				else %incomplete PC support, basically need to add PC equivalents of rawRead and rawWrite
					obj.library = 'labjackud';
					obj.header = 'C:\progra~1\LabJack\drivers\labjackud.h';
					if (libisloaded('labjackud') || (libisloaded('labjackud_doublePtr')))
						% Libraries already loaded
					else
						loadlibrary(obj.library,obj.header);
						loadlibrary labjackud labjackud_doublePtr.h alias labjackud_doublePtr
						% If you wish to view a list of the available LabJack UD functions
						% and their associated Output Values and Input Arguments, uncomment out
						% the appropriate line of code below.
						%libfunctionsview labjackud % Use this in version 7.0 and newer
						%libfunctionsview labjackud_doublePtr % Use this in version 7.0+
						%libmethodsview labjackud % Use this in version 6.5
						%libmethodsview labjackud_doublePtr % Use this in version 6.5
					end
				end
				obj.inp=zeros(10,1);
				if obj.devCount == 0 %no device was found with input deviceID, lets scan for another device
					if obj.deviceID == 3 %we tried a U3, now lets try a U6
						obj.devCount = calllib('liblabjackusb','LJUSB_GetDevCount',6);
						if obj.devCount > 0 %yup it's a U6
							obj.deviceID = 6;
						end
					elseif obj.deviceID == 6 %we tried a U6, now lets try a U3
						obj.devCount = calllib('liblabjackusb','LJUSB_GetDevCount',3);
						if obj.devCount > 0
							obj.deviceID = 3;
						end
					end
					if obj.devCount == 0
						obj.salutation('open method','No LabJack U3 or U6 devices found, going into silent mode');
						obj.version = 'device discovery FAILED';
						obj.isOpen = false;
						obj.handle = [];
						obj.vHandle = false;
						obj.verbose = false;
						obj.silentMode = true; %we switch into silent mode just in case someone tries to use the object
						return
					end
				end
				obj.handle = calllib('liblabjackusb','LJUSB_OpenDevice',obj.device,0,obj.deviceID);
				if obj.validHandle()
					obj.version = calllib('liblabjackusb','LJUSB_GetDeviceDescriptorReleaseNumber',obj.handle);
					if obj.deviceID == 3 %respecify the time quantum for the U3
						obj.timeShort = 128e-6;
						obj.timeLong = 32e-3;
					end
					obj.isOpen = true;
					obj.salutation('open method','LabJack succesfully opened...');
					obj.salutation('open method',sprintf('Device U%g - Time Quantum short/long = %g secs / %g secs',obj.deviceID,obj.timeShort,obj.timeLong));
					none=[0,0,0];
					obj.setDIO(none); %Sink all our DIO to output LOW
				else
					obj.salutation('open method','LabJack failed to open, going into silent mode',true);
					obj.version = 'device opening FAILED';
					obj.isOpen = false;
					obj.handle = [];
					obj.verbose = false;
					obj.silentMode = true; %we switch into silent mode just in case someone tries to use the object
				end
			else %silentmode is ~false
				obj.close();
				obj.isOpen = false;
				obj.handle = [];
				obj.vHandle = false;
				obj.verbose = false;
				obj.silentMode = true; %double make sure it is set to true
			end
		end
		
		% ===================================================================
		%> @brief Close the LabJack device
		%>	void LJUSB_CloseDevice(HANDLE hDevice);
		%>	//Closes the handle of a LabJack USB device.
		% ===================================================================
		function close(obj)
			if ~isempty(obj.handle)
				if obj.validHandle() == true %double-check we still have valid handle
					calllib('liblabjackusb','LJUSB_CloseDevice',obj.handle);
				end
				obj.salutation('CLOSE method','LabJack Handle has been closed');
				obj.isOpen = false;
				obj.handle=[];
				obj.vHandle = false;
			else
				obj.salutation('CLOSE method','No handle to close...');
			end
		end
		
		% ===================================================================
		%> @brief Is Handle Valid?
		%>	bool LJUSB_IsHandleValid(HANDLE hDevice);
		%>	//Is handle valid.
		% ===================================================================
		function vHandle = validHandle(obj)
			obj.vHandle = false; %our cached value
			if obj.silentMode == false
				if ~isempty(obj.handle)
					obj.vHandle = calllib('liblabjackusb','LJUSB_IsHandleValid',obj.handle);
					if obj.vHandle
						obj.salutation('validHandle Method',sprintf('Handle to LabJack U%g is VALID',obj.deviceID));
					else
						obj.salutation('validHandle Method',sprintf('Handle to LabJack U%g is INVALID',obj.deviceID),true);
					end
				else
					obj.vHandle = false;
					obj.isOpen = false;
					obj.handle = [];
					obj.salutation('validHandle Method','Handle to LabJack is INVALID (empty handle)',true);
				end
			end
			vHandle = obj.vHandle;
		end
		
		% ===================================================================
		%> @brief Write formatted command string to LabJack
		%> 		unsigned long LJUSB_Write(HANDLE hDevice, BYTE *pBuff, unsigned long count);
		%> 		// Writes to a device. Returns the number of bytes written, or -1 on error.
		%> 		// hDevice = The handle for your device
		%> 		// pBuff = The buffer to be written to the device.
		%> 		// count = The number of bytes to write.
		%> 		// This function replaces the deprecated LJUSB_BulkWrite, which required the endpoint
		%>
		%> @param byte The raw hex encoded command packet to send
		% ===================================================================
		function out = rawWrite(obj,byte)
			out = calllib('liblabjackusb', 'LJUSB_WriteTO', obj.handle, byte, length(byte), obj.timeOut);
			if out == 0;	obj.salutation('rawWrite','ERROR WRITING!',true); end
		end
		
		% ===================================================================
		%> @brief Read response string back from LabJack
		%> 		unsigned long LJUSB_Read(HANDLE hDevice, BYTE *pBuff, unsigned long count);
		%> 		// Reads from a device. Returns the number of bytes read, or -1 on error.
		%> 		// hDevice = The handle for your device
		%> 		// pBuff = The buffer to filled in with bytes from the device.
		%> 		// count = The number of bytes expected to be read.
		%> 		// This function replaces the deprecated LJUSB_BulkRead, which required the endpoint
		%>
		%> @param bytein
		%> @param count
		% ===================================================================
		function in = rawRead(obj,bytein,count)
			if ~exist('bytein','var')
				bytein = zeros(10,1);
			end
			if ~exist('count','var') || count > length(bytein)
				count = length(bytein);
			end
			in =  calllib('liblabjackusb', 'LJUSB_ReadTO', obj.handle, bytein, count, obj.timeOut);
			if in == 0; obj.salutation('rawRead','ERROR READING!',true); end
		end
		
		% ===================================================================
		%> @brief WaitShort
		%>	LabJack Wait in multiples of 64/128microseconds
		%>	@param time time in ms; remember 64/128microseconds is the atomic minimum
		% ===================================================================
		function waitShort(obj,time)
			if obj.silentMode == false && obj.vHandle == 1
				time = time / 1000; %convert to seconds
				time=ceil(time/obj.timeLong);
				if time > 255
					time = 255; %truncate to maximum time delay allowed
				end
				
				cmd=zeros(10,1);
				cmd(2) = 248; %hex2dec('f8'); %feedback
				cmd(3) = 2; %number of bytes in packet
				cmd(8) = 5; %IOType for waitshort is 5
				cmd(9) = time;
				
				obj.command = obj.checksum(cmd,'extended');
				
				obj.outp = obj.rawWrite(obj.command);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
			end
		end
		
		% ===================================================================
		%> @brief WaitLong
		%>	LabJack Wait in multiples of 16/32ms
		%>	@param time time in ms, remember 16/32ms is the atomic minimum
		% ===================================================================
		function waitLong(obj,time)
			if obj.silentMode == false && obj.vHandle == 1
				time = time / 1000; %convert to seconds
				time=ceil(time/obj.timeLong);
				if time > 255
					time = 255; %truncate to maximum time delay allowed
				end
				
				cmd=zeros(10,1);
				cmd(2) = 248; %hex2dec('f8'); %feedback
				cmd(3) = 2; %number of bytes in packet
				cmd(8) = 6; %IOType for waitlong is 6
				cmd(9) = time;
				
				obj.command = obj.checksum(cmd,'extended');
				
				obj.outp = obj.rawWrite(obj.command);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
			end
		end
		
		% ===================================================================
		%> @brief timedTTL Send a TTL with a defined time of pulse
		%>
		%> Note that there is a maximum time to the TTL pulse which is ~8.16secs
		%> for the U3 and ~4.08secs for the U6; we can extend that time by
		%> adding more feedback commands in the command packet but this
		%> shouldn't be needed anyway. Any time longer than this will be
		%> truncated to the maximum allowable time.
		%>
		%> @param line 0-7=FIO, 8-15=EIO, or 16-19=CIO
		%>	@param time time in ms
		%> @param sync optional logical flag whether to use blocking (true) command
		% ===================================================================
		function timedTTL(obj,line,time)
			if (~exist('line','var') || ~exist('time','var'))
				if ~isempty(obj.timedTTLCache)
					obj.outp = obj.rawWrite(obj.timedTTLCache);
					if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
					obj.salutation('timedTTL method','Cached Command used')
				else
					fprintf('\ntimedTTL Input options: \n\tline (single value 0-7=FIO, 8-15=EIO, or 16-19=CIO), time (in ms)\n\n');
				end
				return
			end
			if obj.silentMode == false && obj.vHandle == 1
				time = time / 1000; %convert to seconds
				time1 = 0;
				time2 = 0;
				if time >= obj.timeLong %we need to use greater resolution for fine timing
					
					time1 = floor(time/obj.timeLong);
					if time1 > 255 %truncate to maximum time delay allowed
						time1 = 255; %truncate to maximum time delay allowed
					elseif time1 < 1 %truncate to minimum
						time1 = 1;
					end
					
					time2 = time - (time1 * obj.timeLong);
					time2 = round(time2/obj.timeShort);
					if time2 > 255 %truncate to maximum time delay allowed
						time2 = 255; %truncate to maximum time delay allowed
					elseif time2 < 0 %truncate to minimum
						time2 = 0;
					end
					
					otime = (time1 * obj.timeLong) + (time2 * obj.timeShort);
					
					cmd=zeros(16,1);
					cmd(2) = 248; %command byte for feedback command (f8 in hex)
					cmd(3) = (length(cmd)-6)/2;
					
					cmd(8) = 11; %BitStateWrite: IOType=11
					cmd(9) = line+128; %add 128 as bit 7 sets value high
					
					cmd(10) = 6; %IOType for waitshort is 5, waitlong is 6
					cmd(11) = time1; %time to wait in unit multiples
					
					cmd(12) = 5; %IOType for waitshort is 5, waitlong is 6
					cmd(13) = time2; %time to wait in unit multiples
					
					cmd(14) = 11; %BitStateWrite: IOType=11
					cmd(15) = line; %bit to set low
					
				else
					
					time2=ceil(time/obj.timeShort);
					if time2 > 255 %truncate to maximum time delay allowed
						time2 = 255; %truncate to maximum time delay allowed
					end
					iotype = 5;
					
					otime = time2 * obj.timeShort;
					
					cmd=zeros(14,1);
					cmd(2) = 248; %command byte for feedback command (f8 in hex)
					cmd(3) = (length(cmd)-6)/2;
					
					cmd(8) = 11; %BitStateWrite: IOType=11
					cmd(9) = line+128; %add 128 as bit 7 sets value high
					
					cmd(10) = iotype; %IOType for waitshort is 5, waitlong is 6
					cmd(11) = time2; %time to wait in unit multiples, this is the time of the strobe
					
					cmd(12) = 11; %BitStateWrite: IOType=11
					cmd(13) = line;
					
				end
				
				obj.command = obj.checksum(cmd,'extended');
				obj.timedTTLCache = obj.command;
				obj.outp = obj.rawWrite(obj.command);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
				obj.salutation('timedTTL method',sprintf('Line:%g Tlong:%g Tshort:%g output time = %g ms', line, time1, time2, otime*1000))
			end
		end
		
		% ===================================================================
		%> @brief setDIO
		%>	setDIO sets the direction/value for FIO, EIO and CIO
		%>  If only value supplied, set all others to [255,255,255]
		%>  @param value is binary identifier for 0-7 bit range
		%>  @param mask is the mask to apply the command
		%>  @param valuedir binary identifier for input (0) or output (1) default=[255, 255, 255]
		%>  @param maskdir is the mask to apply the command. default=[255, 255,255]
		% ===================================================================
		function setDIO(obj,value,mask,valuedir,maskdir)
			if ~exist('value','var');fprintf('\nsetDIO Input options: \n\tvalue, [mask], [value direction], [mask direction]\n\n');return;end
			if ~exist('mask','var');mask=[255,255,255];end %all DIO by default
			if ~exist('valuedir','var');valuedir=[255,255,255];maskdir=valuedir;end %all DIO set to output
			if obj.silentMode == false && obj.vHandle == 1
				cmd=zeros(14,1);
				cmd(2) = 248; %command byte for feedback command (f8 in hex)
				cmd(3) = (length(cmd)-6)/2;
				cmd(8) = 29; %IOType for PortDirWrite = 29
				cmd(9:11) = maskdir;
				cmd(12:14) = valuedir;
				cmd(8) = 27; %IOType for PortStateWrite = 27
				cmd(9:11) = mask;
				cmd(12:14) = value;
				
				obj.command = obj.checksum(cmd,'extended');
				obj.outp = obj.rawWrite(obj.command);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
			end
		end
		
		% ===================================================================
		%> @brief setDIODirection
		%>	setDIODirection sets the direction for FIO, EIO and CIO as read or write
		%>	@param value is binary identifier for 0-7 bit range
		%> @param mask is the mask to apply the command
		% ===================================================================
		function setDIODirection(obj,value,mask)
			if ~exist('value','var');fprintf('\nsetDIODirection Input options: \n\t\tvalue, [mask]\n\n');return;end
			if ~exist('mask','var');mask=[255,255,255];end
			if obj.silentMode == false && obj.vHandle == 1
				cmd=zeros(14,1);
				cmd(2) = 248; %command byte for feedback command (f8 in hex)
				cmd(3) = (length(cmd)-6)/2;
				cmd(8) = 29; %IOType for PortDirWrite = 29
				cmd(9:11) = mask;
				cmd(12:14) = value;
				
				obj.command = obj.checksum(cmd,'extended');
				obj.outp = obj.rawWrite(obj.command);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
			end
		end
		
		% ===================================================================
		%> @brief setDIOValue
		%>	setDIOValue sets the value for FIO, EIO and CIO as HIGH or LOW
		%>	@param value is binary identifier for 0-7 bit range
		%>  @param mask is the mask to apply the command
		% ===================================================================
		function setDIOValue(obj,value,mask)
			if ~exist('value','var');fprintf('\nSetDIOValue Input options: \n\t\tvalue, [mask]\n\n');return;end
			if ~exist('mask','var');mask=[255,255,255];end
			if obj.silentMode == false && obj.vHandle == 1
				cmd=zeros(14,1);
				cmd(2) = 248; %command byte for feedback command (f8 in hex)
				cmd(3) = (length(cmd)-6)/2;
				cmd(8) = 27; %IOType for PortStateWrite = 27
				cmd(9:11) = mask;
				cmd(12:14) = value;
				
				obj.command = obj.checksum(cmd,'extended');
				obj.outp = obj.rawWrite(obj.command);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
			end
		end
		
		% ===================================================================
		%> @brief Prepare Strobe Word
		%>	Sets the strobe value for EIO (8bits) and CIO (4bits) which are
		%> accesible via the DB15 using a single cable. This avoids using FIO, which
		%> can therefore be used for addtional control TTLs (FIO0 and FIO1 are used
		%> for START/STOP and pause/unpause of the Plexon Omniplex in Opticka for
		%> example).
		%>
		%>	@param value The value to be strobed, range is 0-2047 for 11bits
		%>  In Opticka, 0 and 2047 are reserved. Value can be 3 byte markers for
		%>  FIO (which is ignored), EIO and CIO respectively. CIO0 is used as the
		%>  strobe line, which leaves EIO0-7 and CIO1-3 for value data.
		%> @param mask Which bits to mask
		%> @param sendNow if true then sends the value immediately
		% ===================================================================
		function prepareStrobe(obj,value,mask,sendNow)
			if obj.silentMode == false && obj.vHandle == 1
				if value>2047;value=2047;end %block anything bigger than 2^11
				if value<0; value = 0; end %block anything smaller than 0
				obj.strobeComment = ['Original Value = ' num2str(value) ' | '];
				[eio,cio]=obj.prepareWords(value,0); %construct our word split to eio and cio, set strobe low
				ovalue(1) = 0; %fio will be 0
				ovalue(2) = eio;
				ovalue(3) = cio;
				[eio2,cio2]=obj.prepareWords(value,1); %construct our word split to eio and cio, set strobe high
				ovalue2(1) = 0; %fio will be 0
				ovalue2(2) = eio2;
				ovalue2(3) = cio2;
				if isempty(mask); mask = [0, 255, 255]; end
				obj.strobeComment = [obj.strobeComment 'FIO EIO & CIO: ' num2str(0) ' ' num2str(eio2) ' ' num2str(cio2)];
				
				cmd=zeros(30,1);
				cmd(2) = 248; %command byte for feedback command (f8 in hex)
				cmd(3) = (length(cmd)-6)/2;
				
				cmd(8) = 27; %IOType for PortStateWrite (1b in hex)
				cmd(9:11) = mask;
				cmd(12:14) = ovalue; %This is our strobe number but with strobe line set low, th
				
				cmd(15) = 27; %IOType for PortStateWrite (1b in hex)
				cmd(16:18) = mask;
				cmd(19:21) = ovalue2; %The same value but now set strobe high, all our values should be readable
				
				cmd(22) = 5; %IOType for waitshort is 5, waitlong is 6
				cmd(23) = obj.strobeTime; %time to wait in unit multiples, this is the time of the strobe
				
				cmd(24) = 27; %IOType for PortStateWrite (1b in hex)
				cmd(25:27) = mask;
				cmd(28:30) = 0;
				
				obj.command = obj.checksum(cmd,'extended');
				if exist('sendNow','var')
					obj.strobeWord;
				end
			end
		end
		
		% ===================================================================
		%> @brief Send the Strobe command
		%>
		%>
		% ===================================================================
		function strobeWord(obj)
			if ~isempty(obj.command)
				obj.rawWrite(obj.command);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
				%obj.salutation('strobeWord', obj.strobeComment);
% 				if obj.inp(6) > 0
% 					obj.salutation('strobeWord',['Feedback error in IOType ' num2str(obj.inp(7))]);
% 				end
			end
		end
		
		% ===================================================================
		%> @brief Set FIO to a value
		%>
		%> Note this uses the pregenerated raw commands, so only works with
		%> the FIO bits seen in properties above. Us SetDIO and a mask for a
		%> robust way to control any digital I/O
		%>
		%>	@param val The value to be set
		%> line which FIO to set
		% ===================================================================
		function setFIO(obj,val,line)
			if ~exist('val','var');fprintf('\nInput options: \n\t\tvalue, [line]\n\n');return;end
			if obj.silentMode == false && obj.vHandle == 1
				if ~exist('line','var');line=0;end
				myname = ['fio' num2str(line)];
				cmdHigh = [myname 'High'];
				cmdLow = [myname 'Low'];
				if ~exist('val','var')
					val = abs(obj.(myname)-1);
				end
				if val == 1
					obj.outp = obj.rawWrite(obj.(cmdHigh));
					if obj.readResponse; obj.inp  = obj.rawRead(zeros(1,10),10); end
					obj.(myname) = 1;
					obj.salutation('SETFIO',[myname ' is HIGH'])
				else
					obj.outp = obj.rawWrite(obj.(cmdLow));
					if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
					obj.(myname) = 0;
					obj.salutation('SETFIO',[myname ' is LOW'])
				end
			end
		end
		
		% ===================================================================
		%> @brief Toggle FIO value
		%>
		%> Note this uses the pregenerated raw commands, so only works with
		%> the FIO bits seen in properties above. Us SetDIO and a mask for a
		%> robust way to control any digital I/O
		%>
		% ===================================================================
		function toggleFIO(obj,line)
			if obj.silentMode == false && obj.vHandle == 1
				if ~exist('line','var');line=0;end
				myname = ['fio' num2str(line)];
				obj.(myname)=abs(obj.(myname)-1);
				obj.setFIO(obj.(myname),line);
			end
		end
		
		% ===================================================================
		%> @brief Turn LED ON
		%>
		%> I think this only works on the U3
		% ===================================================================
		function ledON(obj)
			if obj.silentMode == false && obj.vHandle == 1
				obj.outp = obj.rawWrite(obj.ledIsON);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
			end
		end
		
		% ===================================================================
		%> @brief Turn LED OFF
		%>
		%> I think this only works on the U3
		% ===================================================================
		function ledOFF(obj)
			if obj.silentMode == false && obj.vHandle == 1
				obj.outp = obj.rawWrite(obj.ledIsOFF);
				if obj.readResponse; obj.inp = obj.rawRead(zeros(1,10),10); end
			end
		end
		
		
		% ===================================================================
		%> @brief Reset the LabJack
		%>
		%> @param resetType whether to use a soft (1) or hard (2) reset
		%> type
		% ===================================================================
		function reset(obj,resetType)
			if ~exist('resetType','var')
				resetType = 0;
			end
			cmd=zeros(4,1);
			cmd(2) = hex2dec('99'); %command code
			if resetType == 0 %soft reset
				cmd(3) = bin2dec('01');
			else
				cmd(3) = bin2dec('10');
			end
			
			obj.command = obj.checksum(cmd,'normal');
			
			obj.outp = obj.rawWrite(cmd);
			if obj.readResponse; obj.inp  = obj.rawRead(zeros(4,1)); end
		end
		
		% ===================================================================
		%> @brief checksum
		%>	Calculate checksum for data packet. Note see the labjack
		%> documentation; there are 2 types of checksums, normal and extended.
		%> This method uses 2 static methods checksum8 and checksum16 for each
		%> type respectively.
		%>
		%> @param command The command that needs checksumming
		%> @param type normal | extended
		% ===================================================================
		function command = checksum(obj,command,type)
			switch type
				case 'normal'
					command(1) = obj.checksum8(command(2:end));
				case 'extended'
					[command(5),command(6)] = obj.checksum16(command(7:end));
					command(1) = obj.checksum8(command(2:6));
			end
		end
		
		% ===================================================================
		%> @brief concatenate the name with a uuid at get.
		%> @param
		%> @return name the concatenated name
		% ===================================================================
		function name = get.fullName(obj)
			if isempty(obj.name)
				name = [obj.className '#' obj.uuid];
			else
				name = [obj.name ' <' obj.className '#' obj.uuid '>'];
			end
		end
		
	end
	
	%============================================================================
	methods (Hidden = true) %--HIDDEN METHODS 
	%============================================================================
	
		function strobeServer(obj, value)

			obj.prepareStrobe(value,[],true);

		end
	
	end
	
	%=======================================================================
	methods ( Static ) % STATIC METHODS
	%=======================================================================
		
		% ===================================================================
		%> @brief checksum8
		%>	Calculate checksum for data packet
		%>
		% ===================================================================
		function chk = checksum8(in)
			in = sum(uint16(in));
			quo = floor(in/2^8);
			remd = rem(in,2^8);
			in = quo+remd;
			quo = floor(in/2^8);
			remd = rem(in,2^8);
			chk = quo + remd;
		end
		
		% ===================================================================
		%> @brief checksum16
		%>	Calculate checksum (lsb and msb) for extended data packet
		%>
		% ===================================================================
		function [lsb,msb] = checksum16(in)
			in = sum(uint16(in));
			lsb = bitand(in,255);
			msb = bitshift(in,-8);
		end
		
		% ===================================================================
		%> @brief Prepare Strobe Word split into EIO (8bit) and CIO (3bit). 0-2047
		%>  %is the max # of variables with 2^11bits.
		%>
		%>	 @param value The value to be split into EIO and CIO
		%>  @return eio is an 8bit word value represented the LSB
		%>  @return cio is a 4bit value where the 1st bit is 1 for strobe line 22
		%>  and the rest is the 3bit remainder to combine with eio to make an
		%>  11bit strobed word.
		% ===================================================================
		function [eio,cio] = prepareWords(value,strobeState)
			if ~exist('strobeState','var')
				strobeState = 1;
			end
			eio = bitand(value,255); %get eio easily ANDing with 255
			msb = bitshift(value,-8); %our msb is bitshifted 8 bits
			msb = bitshift(msb,1); %shift it across as cio0 is reserved;
			cio = bitor(msb,strobeState); %OR with 1 as cio0 is the strobe trigger and needs to be 1
		end
		
	end % END STATIC METHODS
	
	
	%=======================================================================
	methods ( Access = private ) % PRIVATE METHODS
	%=======================================================================
		
		% ===================================================================
		%> @brief delete is the object Destructor
		%>	Destructor automatically called when object is cleared
		%>
		% ===================================================================
		function delete(obj)
			obj.salutation('DELETE Method','labJack object Cleaning up...')
			obj.close;
		end
		
		% ===================================================================
		%> @brief salutation - log message to command window
		%>	log message to command window, dependent on verbosity
		%>
		% ===================================================================
		function salutation(obj,in,message,verbose)
			if ~exist('verbose','var')
				verbose = obj.verbose;
			end
			if verbose ~= false
				if ~exist('in','var')
					in = 'General Message';
				end
				if exist('message','var')
					fprintf(['---> labJack: ' message ' | ' in '\n']);
				else
					fprintf(['---> labJack: ' in '\n']);
				end
			end
		end
		
		% ===================================================================
		%> @brief Sets properties from a structure or varargin cell, ignores invalid properties
		%>
		%> @param args input structure/cell - will automagically handle
		%> either type
		%> @param allowedProperties a regex of allowed properties to set at
		%> runtime
		% ===================================================================
		function parseArgs(obj, args, allowedProperties)
			allowedProperties = ['^(' allowedProperties ')$'];
			while iscell(args) && length(args) == 1
				args = args{1};
			end
			if iscell(args)
				if mod(length(args),2) == 1 % odd
					args = args(1:end-1); %remove last arg
				end
				odd = logical(mod(1:length(args),2));
				even = logical(abs(odd-1));
				args = cell2struct(args(even),args(odd),2);
			end
			fnames = fieldnames(args); %find our argument names
			for i=1:length(fnames);
				if regexp(fnames{i},allowedProperties) %only set if allowed property
					obj.salutation(fnames{i},'Configuring setting in constructor');
					obj.(fnames{i})=args.(fnames{i}); %we set up the properies from the arguments as a structure
				end
			end
		end
	end
end