// TITLE     : Scilab driver for ZABER devices
// Author (c): Samuel GOUGEON - Université du Maine / ENSIM - France
// Date      : 2010 - 2012 -
// Release   : 0.4
// License   : CeCILL-C
//   http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
// Software  : for Scilab >= 5.4
//
// DISCLAIMER: THIS DRIVER IS RELEASED AS IS.
// NEITHER THE AUTHOR NOR HIS EMPLOYER CAN BE CONSIDERED AS RESPONSIBLE OF ANY 
// DAMAGES THAT WOULD OCCUR TO YOUR DEVICES OR HARDWARES WHILE USING THIS CODE
// AND RELATED MANUFACTURING DATA (the source of data is provided in the help.
// YOU MUST CHECK PARAMETERS OF YOUR DEVICES BEFORE USING THE DRIVER). 
// TEST AND USE IT TO YOUR OWN RISKS.
//

// HISTORY:
// 2012-11-01 : Release 0.4 => first publication
// 2012-11-01 : Release 0.3 (unpublished)
// 2012-10-31 : Many improvements of head_comments help
//              'set','storedPos': first implementation
//              'get','storedPos': first implementation
//              'mvs': (move to stored) first implementation
//              gettext() corrections: 
//                tmp= ".."; foo(gettext(tmp)+"\n") => tmp= _("..\n"); foo(tmp)
//              Improvement of the code style
// 2012-10-16 : property "locking?" renamed "locked?". 
//              Help written for 'home', and for properties homeOffset, 
//              autoHome?, homeSet?, locked?, position, range.
// 2012-10-09 : read_csv => csvRead() to comply with Scilab 5.4.0
//              Release 0.2 (unpublished)
// 2012-04-17 : Release 0.1 (unpublished)
// 2010-07-31 : Release 0.0 (unpublished)

// TO DO:
// + 'clean' command for aborted zaber sessions
// * Removal (or clearing usage) of ToR parameter
// * Check "status", devName => (18 mvs) output
// * add command #35: read or write zaber memory (128 non-volatile bytes 
//    available for user data) 
// * tests of reindexing when inserting or removing a device on a line
// * extensive tests of commands when operating multiple devices in a once
//

mode(0)
// ===========================================================================

function result = convZaberData(data)
    // One position or value per column. Multiple columns are possible
	r = []
	if size(data,1)==1,    // Only 1 row => slicing true pos into 4 bytes
		for d = data
			if d < 0, d = 256^4 + d; end
			byte4 = fix(d / 256^3) ; d = d - 256^3 * byte4
			byte3 = fix(d / 256^2) ; d = d - 256^2 * byte3
			byte2 = fix(d / 256)
			byte1 = round(d - 256 * byte2)
			r = [ r  [byte1 byte2 byte3 byte4]' ]
		end
		result = r
	elseif size(data,1)==4,        // 4 rows: 4 bytes => true position
		for d = data
			tmp = 256^3 * d(4) + 256^2 * d(3) + 256 * d(2) + d(1);
			if d(4) > 127, tmp = tmp - 256^4; end
			r = [r tmp]
		end
		result = r
	else
		disp(data)
		result = %nan				// unexpected data format
	end
    //disp(data,result)
endfunction

// ===========================================================================

function devN = zaberDevIds(devName,command)
	// Returns the respective indices of the Zaber devices nicknamed devName(*), 
	// in the devices(*,:) array, or [] if devName is a single undeclared device
	session = "zaber"
	if typeof(devName)~="string"
		tmp = _("%s: Name(s) of device(s) must be specified as a (vector of) string\n")
		error(msprintf(tmp, session))
	end
	devN = []
	if TCL_ExistInterp(session)
		devices = TCL_GetVar("devices",session)
		for d = devName
			n= find(devices(:,1)==d)
			if n==[]
				if size(devName,"*")>1
					tmp = _("%s.%s : ""%s"" is not a declared device\n")
					error(msprintf(tmp, session,command,d)+ascii(10));
				end
			else devN = [ devN n ]
			end
		end
	end
endfunction

// ==========================================================================

function b = zaber_end_pos(a,Hport,step)	// Read final position after a move
	if a
		c = ascii(readserial(Hport))'
		//sleep(tempoRead)
		b = convZaberData(c($-3:$))*step
	else b = %f
	end
endfunction

// ===========================================================================
function out = zaber(command,target,varargin) 
// Scilab Driver for Zaber devices
//
// Input arguments: 
// #1: Command name
// #2: Device name or serial port number
// #3: Data (position, etc)
//
// GENERAL CONDITIONS
// ------------------
// This function handles zaber devices set on one or several lines connected 
//  to the serial ports of the computer (one line per port).
// Each port (and related line) can be made of several zaber devices serially 
//  plugged to each others. 
// Translation and rotation devices are supported and can be mixed along a line.
// Hereafter, (NV) stands for Non-Volatile data. These data are stored in a
//  non-volatile memory into each device. Then, they are automatically
//  recovered after powering off/on the device.
//
//  REQUIREMENTS
//  ------------
//  * global zaber_devs_params_file : 
//    This global variable must be set in the calling environnement. 
//    It must contain the path and filemane of a CSV file with tab-separated 
//    fields, listing devices parameters as published by Zaber manufacturer:
//    Source: http://www.zaber.com/support/?tab=Device%20ids#tabs
//    1 line = 1 device
//    column #1: device's id
//    column #2: device commercial name (may vary with time...)
//    column #3: step size [in µm or µrad]
//    column #4: Type of device (= 2 char codes): TR = TRanslation RA = Rotation 
//               VE = VErin (french word for Jack :-) TI = TIlt
//    column #5: Mode (cmd 40)
//    column #6: Range = number of steps
//    column #7: Speed
//    column #8: Currents: Run / Hold / Limit
//  * Scilab > 5.2.0 is required
//  * Serial TCL toolbox 0.2 (E. Segre) must be loaded
//    http://atoms.scilab.org/toolboxes/serial
//
// ================
// LIST OF COMMANDS    [ ]== vector   {...}== optionnal
// ================
//
// PORTS HANDLING
// --------------
// 'open', ports : 
//     Open the RS-232 communication for the given RS-232 ports.
//     ports may be a scalar or a vector of serial port numbers.
//     If ports is a vector of strings, it must follow the format "COM#:"
//     or "/dev/ttyS#" with # stating from 1 in the first case (Windows)
//     and from 0 in the second case (Linux). Else, integer numbers 
//     starting from 0 or 1 must be specified.
//     Connexion parameters are set by the script. They are not tunable:
//     9600 bauds, 8 data bits, no parity, one stop bit, no hand shaking.
// 
// 'close', ports : 
//     Close the RS-232 communication for the given ports.
//     ports may be a scalar or a vector of port numbers.
//     ports==0 means "All opened Zaber serial ports must be closed".
//     The closure of any port used for a chain of Zaber device(s)
//     automatically removes all related Zaber devices from the setup.
//
// BUILDING a SETUP
// ----------------
// 'listdevices', port: 
//     List all declared zaber devices connected to the given serial port.
//     port 0 or unspecified lists devices for all ports. [data] are ignored.
//
// 'adddev', port, devName {, rankOnLine {,aliasNum {, ToR }}} :
//     Adds a device to a setup & returns its current position [mm|°]
//      port: serial port number to which the device is connected.
//            If port is not yet declared, it is then opened prior to device
//            declaration.
//   devName: nickName of the device (string). Must be unique over all ports.
//            Nicknames are case sensitive.
// rankOnLine: Rank at which the device is inserted (integer>0). The device 
//            closest to the computer gets rank #1. The rank of each device 
//            previously ranked >= rankOnLine is then incremented.
//            By default, one assumes that the additionnal device is queued on
//            its line (rankOnLine = max(rank)+1).
//  aliasNum: Alias number ascribed to the device (0 < number < 254).
//       ToR: "R" for a rotation, "T" for a translation. This information is
//            also read from he device after it has been plugged, declared, and
//            powered. It may be given in argument for checking purpose.
//
//    Device internal format: [nickname portNumber rankOnLine idOnLine ToR ]
//
//  'rmdev',devNames : 
//            Remove device(s) from a setup. The devices may be on the
//             the same line, or not. For each related port, remaining devices
//             are renumbered afterwards if necessary.
//            Note: implementation not yet extensively tested.
//
// SETTINGS
// --------
// 'rename', devName, newDevName : 
//    Renames a device or a set of devices
//		devName: Vector of nicknames of devices to be renamed
//	 newDevName: Vector of new nicknames of the respective devices
//		devName and newDevName are 2 vectors with same number of strings
//		Names of 2 devices may be commuted:
//		  devName=[ "dev1" "dev2" ]  newDevName=[ "dev2" "dev1"]
//    Note: implementation not yet extensively tested.
//
// 'renum', devName [, data] :  Renumbers device(s) on a serial line.
//	a) devName is an integer or a vector of integers. Then it represents
//		the number of the serial port(s) on which all connected devices
//		must be renumbered: The device closest to the computer gets 
//		number 1, and so forth.
//	b) devName is a string and data > 0:   NOT YET IMPLEMENTED
//		Then devName is the name of the device to be renumbered.
//		Then, data = new number to be set for devName
//
// 'reset',devNames : Resets the devices given in the vector of devices names,
//                    as if it was powered off, and then powered on again.
//
// 'set', devNames,'default': Resets devices to their default factory settings
// 'set', devName, properties,values :
// 'set', devName,'property1=value,property2=value2,...'
//    The list of available properties is given herebelow for the 'get' command
// 
// INFOS
// -----
// <no parameter>    : "zaber" alone displays this embedded help
// 'pos',devNames    : Returns the current position [in mm or °] for the vector
//                      of named devices
// 'status',devNames : Returns the current status for the devices specified by
//                  their name in the vector devNames. For each device,
//                  the answer is a 2-element list L with L(1) an integer code,
//                  and L(2) the related word, among the following ones:
//                  0  idle: The device is at rest
//                  1  home: The device is going to its home position
//                  10 knob: The device is moving by hand through its knob
//                  18 mvs : A move to Stored position command is being executed
//                  20 mva : A move absolute instruction is being executed
//                  21 mvr : A move relative instruction is being executed
//                  22 mvc : The device is continuously moving at constant speed
//                  23 stop: The device is decelerating to stop
//                     The numerical form of the anwser is returned in ans(1)
//                     The litteral form is returned in ans(2)
//
// 'get', devName [,properties][,%t]: 
//
//  Reads & returns values of properties, for the specified device.
//
//  * If no property is specified, all properties are read and returned.
//    The full list with 28 ordered components is given below.
//  * If %t is set in last parameter, a string array A is returned, with 
//     A(:,1): properties names in the query order. A property may be query
//             several times.
//     A(:,2): respective values of properties (in strings)
//    Else, a vector N of numbers is returned, with N(i) = value of properties(i)
//    Default mode is %F.
//  * Boolean properties have a name ending with "?". 
//     Returned values are then 0 (meaning %F) or 1 (meaning %T)
//
//  * AVAILABLE PROPERTIES: (are case insensitive)
//    --------------------
//  (1)         id: integer>0 identifying the model of device
//
//  (2)    aliasId: integer 0|>0 = n° of pseudo-device (for grouping)
//                  Devices connected to the same serial port and having the
//                  same aliasId can be addressed together as a group.
//
//  (3)    version: Release number of the firmware of the device
//
//  (4)    locked?: [0] | 1. 1 => non-volatile settings can't be changed
//                   (like speed, range, etc)
//
//  (5)   position: in mm | ° : (volatile)
//                  default after power ON = mechanical position - homeOffset
//                  It can be set at will (then the "range" parameter is NOT
//                  changed accordingly)
//
//  (6)  storedPos: 'set', devName, 'storedPos', #mem
//                    stores the current position of the device in its
//                    non-volatile memory number #mem (integer 0 - 15)
//                    Note: Use 'mvs', devName, #mem  to move the device to
//                     the position recorded in its memory #mem
//                  'get', devName, 'storedPos', #mem :
//                    returns the position recorded in memory #mm in mm|° 
//                    The default position stored in all memories is 0.
//
//  (7) homeOffset: in mm | ° : When a "home" is performed, this mechanical
//                  position is set as the logical 0 position. 
//                  When "homeOffset" is changed, the "range" parameter is 
//                  changed accordingly.
//
//  (8)   homeSet?: [0] | 1 : Flag set to 1 by a 
//                  "home" (setting mechanical homeOffset as logical 0) or a 
//                  "set <dev> position #"  or a
//                  "set <dev> homeSet? 0" command.
//                     Flag set to 0 by a power OFF/ON or a
//                  "reset" command or a
//                  "set <dev> homeSet? 0" command
//
//  (9)  autoHome?: [non-volatile] 0 | 1 :
//                  1 => any movement reaching the mechanical 0 stops and
//                       triggers a "home" command (see this command).
//                  0 => prevents the auto-home behavior.
//
// (10) resolution: 1 2 4 8 16 32 _64_ 128 are possible values of numbers
//                  of microsteps/step.
//
// (11)      range: Maximal position [mm|°]. Beware that the range can be
//                  set beyond the physical limit of the device!
//                 When the logical home is changed (homeOffset parameter),
//                  the logical range is changed accordingly. This is not
//                  the case when the logical position is reset to a given
//                  value ("position" parameter).
//
// (12)     maxMvr: (to be implemented)
//
// (13)   acceleration: Acceleration  [°/s^2 | mm/s^2 ]
//
// (14)          speed: Nominal speed [°/s | mm/s ]
//
// (15) circularPhase?: 1 enables the smooth phase mode for microstepping
//                      => constant (but lower: 70%) torque, smoother operation, 
//                      better µstep accuracy                               [0]
//
// (16)  antiBacklash?: 1 enables anti-backlash, in order to compensate any
//                      external mechanical hysteresis when the device is
//                      moved back.                                         [0]
//
// (17) antiStriction?: 1 enables anti-striction, in order to insure that even
//                      very small movements will be actual (compensation of
//                      internal hysteris)                                  [0]
//
// (18)        voltage: Voltage [V] powering the device
//
// (19)    idleCurrent: current at rest = fraction of maximal current       [0]
//
// (20)  activeCurrent: current at move = fraction of maximal current       [1]
//
// (21)      powerLED?: The green  LED is ON if the line is powered         [1]
//
// (22)       dataLED?: The orange LED is ON when data are sent on the line [1]
//
// (23)          knob?: knob available and enable (for hand control)        [1]
//
// (24)   knobReverse?: knob direction is inverted with respect to movement [0]
//
// (25)  moveTracking?: 1 enables tracking positions when the device moves  [0]
//
// manualMoveTracking?: 0 cancels tracking positions while the device is moved 
//                      with the knob...............                        [1]
//
// (27)     autoReply?: 0 cancels sending the final position into the buffer [1]
//
// (28)  messageByte6?: Byte#4 of data can be used to code any info         [0]
//   
//
// MOVEMENTS
// ---------
// Each command can be applied to a set of devices, with respective amplitudes
//  when applicable. If %t is passed in final parameter, a vector of the final 
//  positions is read and returned after the last device has stopped. Else,
//  1000 is immediately returned while devices are still running.
//
// IMPORTANT REMARK : Negative positions | angles are not supported by Zaber.
//  => Any relative or absolute movement covering some negative logical 
//     positions is ignored. This prohibition includes
//   - the case of a shifted home: Example: If the logical home (=0) has been
//     set to the mechanical position m=17, any downward movement targetting 
//     a mechanical position below m=17 will be cancelled.
//   - the case of a rotation for which the auto-home has been disabled in order
//     to allow free multiple tours. Then, the position a=-10° will have to be
//     reached upwards as 350°.
//  Thus, the logical home must be considered as the low-limit of the device.
//  Negative _relative_ movements are allowed, provided that they do not reach
//  negative logical positions.
//
// 'home' : Move to the mechanical home position, then 
//          move to the homeOffset, and finally
//          set this position as 0.
//          Any previous position setting is lost.
//          BEWARE: Be sure that the setup is able to sweep the full 
//          [0 homeOffset] mechanical interval before performing a 'home'.
//
// 'mva', devname, pos : 
//          MoVe Absolute: Go to the position given as pos [mm|°]
//
// 'mvr', devName, shift: 
//          MoVe Relative: Shifts the current position by the given shift
//
// 'mvc', devName, speed: 
//          MoVe Continuously at the given constant speed [mm/s|°/s]
//
// 'mvs', devName, #mem:
//          MoVe the device to its position Stored in non-volatil memory 
//          number #mem, with 0 <= #mem <=15. 
//          The default positions stored in memories is 0 [mm|°].
//          devName and #mem may be vectors, to move several devices in a 
//          once (untested)
//
// 'stop' : Decelerates the device from its current position, until it stops
//
// 'wait' : Waits for all given devices to stop. Then returns their position
//
//  EXAMPLE: 
//  zaber("mva","rot",45.5) // "rot" is run to reach 45.5. 1000 is immediately 
//  zaber mva rot 45.5      // returned without waiting for "rot" to stop.
//                          // Both given syntaxes are equivalent.
//  zaber("mvr","x",13, %t)  // "x" is moved by 13. %t => waits for "x" to stop.
//                           // Then the position reached by "x" is returned 
//  zaber("home",["x" "y"])  // "x" and "y" are run to reach their respective 
//                           //  home positions. 1000 is immediately returned
//  zaber("mvr",["x" "y"],[13 -4.5], %t)  
//                           // "x" is moved by 13, while 'y' is moved by -4.5
//                           // %t => Both devices are waited to stop; then the
//                           // [xpos ypos] vector of final absolute positions
//                           // is returned.
// INTERNALS : 
// ---------
// A TCL zaber session contains all useful data:
//  ports  : Array of RS-232 opened ports (number) connected to zaber devices
//           1 line / port. 1 line = [ portNumber idTCL ]
//  devices: Array of declared Zaber devices 
//			 1 device/line. 
//			 1 line = [ nickname  portNumber ToR  rankOnLine  idNum  aliasNum ]
//						idNum and aliasNum are NOT YET SUPPORTED (=> empty)

// ============================================================================
// "zaber" command without input parameters => Displays the help:
if argn(2)==0
    head_comments("zaber")
    out = 0
    return
end

// INITIALISATIONS
// ===============
	tab = ascii(9)
	stepR = 2.34375e-4		    // °/µstep
	stepT = 0.09921875e-3	     // mm/µstep
	tempoRead = 20				// Temporisation [ms] after any read operation
	// ------
	if isdef("target","local")
        target  = string(target)
    else 
        target = ""
    end
	command = convstr(command,'l')

	if isdef('varargin'), data = varargin, end
	Ndat = argn(2) - 2            // Number of optional arguments (data)
	// ------
	// Checking for the TCL serial toolbox availability:
	if ~exists('writeserial')
		tmp = _("%s: The SERIAL TCL toolbox is missing. Please instal it before processing.\n")
		error(msprintf(tmp,"zaber"))
	end
	
	// For "get" and "set". Properties with code<0 are readonly (only GET):
	parameters=['id' '-50' 'aliasId' '48' 'version' '-51' ..
				'locked?' '49'  ..
				'position' '45' 'storedPos' '-17' .. // set storedPos => 16 = special case)
				'homeOffset' '47' 'homeSet?' '107' 'autoHome?' '108' ..
				'resolution' '37' 'range' '44' 'maxMvr' '46' ..
				'acceleration' '43' 'speed' '42' ..
				'circularPhase?' '111' 'antiBacklash?' '101' 'antiStriction?' '102'..
				'voltage' '-52' 'idleCurrent' '39' 'activeCurrent' '38' ..
				'powerLED?' '114' 'dataLED?' '115' ..
				'knob?' '103' 'knobReverse?' '109'..
				'moveTracking?' '104' 'manualMoveTracking?' '105'..
				'autoReply?' '100' 'messageByte6?' '106'
				]
		// Any code<0 refers to the command #|code| to GET the info
		// Any 0 <code<100 refers to the command #code to SET the property value,
		//  and to the command #53,code to get the info (except for #36)
		// Any code>99 refers to bit #(code-100) answered to the command #53,40

	// Setting the Zaber TCL session
	// -----------------------------
	if TCL_ExistInterp("zaber"),  session = "zaber"; else session = ""; end
	if session=="",   // Creation of a Zaber TCL session
		session = "zaber"
		TCL_CreateSlave(session);
		// Array of used RS-232 ports. 1 line = [ portNumber idTCL] :
		ports = ["" ""];
		// Array of declared zaber devices. 1 device per line :
		devices = emptystr(1,7)         // 7 fields per device
		TCL_SetVar("ports", ports, session)
		TCL_SetVar("devices", devices, session)
	else	// The zaber session already exists => Recovering it 
		ports   = TCL_GetVar("ports", session)
		devices = TCL_GetVar("devices", session)
	end
	out = [] // default output value
	
	// Selected devices and their parameters
	// -------------------------------------
	c = ["rmdev" "reset" "home" "mva" "mvr" "mvc" "mvs" "stop" "wait" ..
	     "pos" "status" "get" "set" "store"]
	if or(command==c)
		idev = zaberDevIds(target,command)
		if idev<>[]
			dev = devices(idev,:)      // setting the list of invoked dev
			step = eval(dev(:,6))
			Hport = [], nDev = []
			for d = dev'
				n = find(ports(:,1)==d(2))
				Hport = [Hport ports(n,2) ]   // Handles to the related serial port
				nDev  = [nDev eval(d(3)) ]    // # of the devices along respect. lines
			end
		else
			tmp = _("%s.%s : Argument #2 must be the name of a declared device\n")
			error(msprintf(tmp, session, command));
		end
	end	

// ===========================================================================
// MAIN COMMAND LOOP
// ===========================================================================

    select command

// =========================================================================
// OK            // SEE ALSO: TCL_EvalStr("file channels") : Lists opened files
    case 'open'                           // ports:  [ portNumber idTCL ]

    for port = target
        if or(ports(:,1)==port)
            tmp = _("%s.%s : port #%s is already opened\n")
            warning(msprintf(tmp, session, command, port))
        else
            if length(port)<4

                t = eval(port)
            else t = port
            end
            out = openserial(t,"9600,n,8,1","binary")
            //TCL_EvalStr("fconfigure "+h+" -buffersize 30000 -sysbuffer 30000")
            ports = [ ports ; [ port  out ] ]
            TCL_SetVar("ports", ports, session)
            zaber("renum",port,0)  // Any connected Zaber line is renumbered
        end
    end

  // =========================================================================
  // OK
	case 'close'
	if (~exists('closeserial'))
		tmp = _("%s: %s from SERIAL toolbox is missing\n")
		error(msprintf(tmp, session, "closeserial()"))
	end
	if target=="0"  		// All registered ports must be closed
		t = ports(:,1)'
		target = t(2:$) 
	end
	for port = target		// Vector of ports to be closed is supported
		n = find(ports(:,1)==port)
		if isempty(n)
			tmp = _("%s.%s : port #%s is already closed\n")
			warning(msprintf(tmp, session, command, port));
		else
			out = [ out string(1+closeserial(ports(n,2))) ]	// Closing the port
			ports(n,:) = [];                                // Unindexing it 
			// Unindexing related devices : 
			N = find(devices(:,2)==port)
			if ~isempty(N), devices(N,:)=[], end
		end
	end

	// Delete TCL session when no remaining port (except empty line #1):
	if size(ports,1)<2
        TCL_DeleteInterp(session)
	else     // Updating TCL data:
		TCL_SetVar("devices",devices,session);
		TCL_SetVar("ports",ports,session);
	end

  // ==========================================================================

  case 'listdevices'	// Returns Zaber devices of all | a given serial port
		// Device format: [ nickname  portNumber ToR  rankOnLine  idNum  aliasNum ]
		Devices = devices;
		if target~=[] & target~="0" & target~="", 
			Devices  = devices(find(devices(:,2)==target),:)
		else Devices = devices(2:$,:), 
		end
		[tmp,i] = gsort(Devices(:,1),"r","i")
		out = Devices(i,:)
		
  // ==========================================================================

	case 'listports'
		out = ports(2:$,:)
		
  // ==========================================================================

	case 'adddev'			// Declaring a new device into the setup
	
	// 'adddev', port, devName {, rankOnLine {,aliasNum {, ToR }}}
	//		port: serial port number to which the device is connected.
	//			If port is not yet declared, it is then opened before declaring
	//			the new device.
	//	 devName: nickName of the device (character string). 
	//			Must be unique over all serial ports driving the whole setup.
	//		 ToR: "R" for a rotation, "T" for a translation (default)
	//rankOnLine: Rank at which the device is inserted (integer>0). The device 
	//			closest to the computer gets rank 1. The rank of each device 
	//			previously ranked >= rankOnLine is then incremented.
	//			By default, one assumes that the additionnal device is queued
	//			on its line (rankOnLine = max(rank)+1)
	//	   idNum: Not yet supported
	//  aliasNum: Not yet supported: Alias number ascribed to the device (int>0)
	//
	//	Device internal format: 
	//  OLD: [ nickname  portNumber ToR  rankOnLine  idNum  aliasNum ]
	//  NEW: [ nickname  portNumber  rank  aliasN  ToR  step  id ]

		// Checking for the used port
		// --------------------------
		port = target(1)
		n = find(ports(:,1)==port)
		if isempty(n)  // Port not opened => open it
			out = zaber('open',port)
		end
		// Checking for nickname syntaxe:
		// -----------------------------
		if length(data)<1
			tmp = _("%s.%s : A nickname must be given in argument #%i.\n")
			error(msprintf(tmp, session, command,3))
		end
		if typeof(data(1))~="string"
			tmp = _("%s.%s : argument #%i: String expected\n")
			error(msprintf(tmp, session, command, 3))
		end
		nickname = data(1)				// is case sensitive

		// Checking for nickname collision:
		// --------------------------------
		if zaberDevIds(nickname,command)~=[]
			tmp = _("%s.%s : Device ""%s"" already exists. Please choose another name for the new device.\n")
			error(msprintf(tmp, session, command, nickname))
		end
		// OK: no nickname collision. Adding the device
		
		// SETTING the RankOnLine
		// ----------------------
		// Getting all existing devices on this port
		devOnPort = find(devices(:,2)==port)
		portDev   = devices(devOnPort,:)	// List devices on this port
		if portDev==[], rankOnLineMax = 0
		else rankOnLineMax = max(eval(portDev(:,3)))
		end
		if Ndat<2,  // true rank is not explicitly given
			device = [ nickname port string(rankOnLineMax+1) "" "" "0.1" ""]
		else		// rankOnLine is explicitely given
			gtr = data(2);	// Given true rank
			// Checking its syntaxe:
			if typeof(gtr)=="string"
				if ~isnum(gtr)
					tmp = _("%s.%s : Argument #%i: Number expected\n")
					error(msprintf(tmp, session, command, 3))
				else gtr = eval(gtr)
				end
			end
			if typeof(gtr)~="constant" | gtr<1 | gtr~=fix(gtr) | gtr>254
				tmp = _("%s.%s : Argument #%i: Integer in [1, 254]  expected.\n")
				error(msprintf(tmp, session, command, 3))
			end

			// Preventing rankOnLine hole
			if gtr>rankOnLineMax+1
				tmp = "%s.%s : Last device number on port #%s is #%d. "+..
					"Setting a new device on position %d would create a hole "+..
					"in the devices series. The device has been queued as #%d instead\n"
				warning(msprintf(tmp, session, command, target,..
										rankOnLineMax,gtr,rankOnLineMax+1))

			// Inserting the device => Higher declared ranks must be incremented
			elseif gtr<=rankOnLineMax,
				for i = devOnPort
					t = eval(devices(i,3))
					if devices(i,2)==port & t>=gtr
                        devices(i,3) = string(t+1)
                    end
				end
			end
			device = [nickname port string(gtr) "" "" "0.1" ""]
		end

        // Reading specifications of the device
        // ------------------------------------
        zaber("renum",port,0);  				// Renumbers all devices on this port
        devices = [ devices ; device ]			// Transient record for first access
        TCL_SetVar("devices", devices, session);
        // Getting and copying infos from the device
        t = zaber("get", nickname, "id")	// Read id number from the device
        if t==[] then
            tmp = _("%s.%s : No ""id"" returned from the device\n")
            error(msprintf(tmp, session, "get id"))
        end
        t = msprintf("%g",t)
        
        // Setting the step size according to the type and model of Zaber device
        // File of Zaber references:
        // Columns are: id name step_[µm|µrad] Type ... 
        //Type:VE=VErin TR=TRanslation RS=Rotation_Stage RA=Rotation_Axis TI=Tilt
        global zaber_devs_params_file
        zrefs = csvRead(zaber_devs_params_file, ascii(9), ".", "string")
        i = find(zrefs(:,1)==t)
        if i==[]
            tmp = _("%s.%s : Device of ref. #%s not in the Zaber reference file\n")
            error(msprintf(tmp, session, command,t))
        end
        zname = zrefs(i,2)
        ztype = zrefs(i,4)
        tmp = _("%s.%s : Device %s is referenced of type %s\n")
        disp(msprintf(tmp, session, command, zname, ztype))
        tmp = zrefs(i,3)
        if isnum(tmp)
            step = eval(tmp)
        else
            // A FAIRE : PRENDRE LE PAS DONNé EN PARAMETRE
            tmp = _("%s.%s : Undefined step for device of ref #%d = %s. Please complete de reference file\n")
            error(msprintf(tmp, session, command, t, zname))
        end
        if or(ztype==["RS" "RO" "TI"])
             step = step*5.729578e-5   // µrad => °
        else step = step/1000          // µm => mm
        end
        device(5:7)  = [ztype msprintf("%e",step) t ]
        devices($,:) = device
        TCL_SetVar("devices",devices,session)

        out = zaber("pos",device(1))

    // ==================================================================

    case 'rmdev'		// Removes a Zaber device from the set-up

        for i = idev
            // Updating the array of remaining devices on implied ports
            j = find(devices(:,2)==devices(i,2) & strtod(devices(:,3))>strtod(devices(i,3)))
            for u = j
                devices(u,3) = msprintf("%g",eval(devices(u,3))-1)
            end
            zaber("renum", devices(i,2), 0);  // Renumbers all remaining devices on port
        end
        devices(idev,:) = []	// Removes the devices from the devices() array
        TCL_SetVar("devices", devices, session);
        out=1

    // ==================================================================

    case 'rename'      // Renaming a Zaber device (changing its nickname)
    
      if Ndat==0
        tmp = _("%s.%s : 3rd argument missing: Vector of new nicknames.\n")
        error(msprintf(tmp, session, command))
      end
      names = target
      newNames = data(1)
      if typeof(newNames)~="string"
        tmp = _("%s.%s : Argument #3 must be a vector of strings\n")
        error(msprintf(tmp, session, command))
      end
      if size(names,"*")~=size(newNames,"*")
        tmp = _("%s.%s : Arguments #2 and #3 must have the same number of components\n")
        error(msprintf(tmp, session, command))
      end

      // Renaming related devices
      Devices = devices;  // Copy to be modified
      for i = 1:size(names,"*")
          j = find(devices(:,1)==names(i))
          if j==[]
              tmp = _("%s.%s : Device ""%s"" does not exist => cannot be renamed\n")
              warning(msprintf(tmp, session, command, names(i)))
          else
              Devices(j,1) = newNames(i)
          end
      end

      // Checks whether some devices would then have the same name:
      if size(unique(Devices(:,1)),"*")~=size(Devices,1)
        tmp = _("%s.%s : Some devices would have the same name\n")
        error(msprintf(tmp, session, command))
      end
      devices=Devices;

    // ==================================================================
    // 'renum', ports: Renumbers device(s) on a serial line.
    //  ports is an integer or a vector of integers. It represents the id
    //  number of the serial port(s) on which all connected devices must be
    //  renumbered: The device connected to the socket gets number 1, and 
    //  so forth.

    case 'renum'
        if and(isnum(target))
            for port = target
                n = find(ports(:,1)==port)
                if isempty(n)
                    tmp = _("%s.%s : Port #%s NOT OPENED => Renumbering ignored\n")
                    warning(msprintf(tmp, session, command, port))
                else 
                    out = string(1+writeserial(ports(n,2), ascii([0 2 0 0 0 0])))
                    sleep(1000)
                end
            end
        else 
            tmp = _("%s.%s : Port(s) number mis-specified\n")
            warning(msprintf(tmp, session, command))
        end

    // ==================================================================
    case 'reset'		// Command #0   => Reply data: None
        out = 1
        for i = 1:size(nDev,"*")
            tmp = 1+writeserial(Hport(i),ascii([nDev(i) 0 0 0 0 0]))
            sleep(tempoRead)
            if ~tmp,
                tmp = _("%s.%s : The device ""%s"" does not answer\n")
                warning(msprintf(tmp, session, command, dev(i,1)))
                out = 0
            end
        end
        zaber("wait",dev(:,1))

    // ==================================================================
    case 'status'        // Command #54                            // OK
        readserial(Hport)    // cleaning the serial buffer
        sleep(tempoRead)

        for i = 1:size(nDev,"*")
            tmp = 1+writeserial(Hport(i),ascii([nDev(i) 54 0 0 0 0]))
            sleep(tempoRead)
            if ~tmp,
                tmp = _("%s.%s : The device ""%s"" does not answer\n")
                error(msprintf(tmp, session, command, dev(i,1)))
            else 
                tmp = ascii(readserial(Hport(i)))
                sleep(tempoRead)
                L = length(tmp)
                nL = round(L/6)
                nC = round(L/nL)
                tmp = matrix(tmp,nL,nC)
                out = [ out tmp(max(find(tmp(:,2)==54)),3) ]
            end
        end
		s = emptystr(out)
		s = ["idle" "home"]
        s(11) = "knob", 
		s([20 21 22 23]+1) = ["mva" "mvr" "mvc" "stop"]
		out = list(out, s(out+1))

	// ========================================================================
	case 'pos'			// Command #60   =>  Reply data: Position  			// OK
		readserial(Hport), sleep(tempoRead)	// cleaning the serial buffer
		for i = 1:size(nDev,"*")
			tmp = 1+writeserial(Hport(i),ascii([nDev(i) 60 0 0 0 0]))
			sleep(tempoRead)
			out = [out zaber_end_pos(tmp,Hport(i),step(i)) ]
		end
		
	// ========================================================================
	case 'set'			// 
		out = 1
		noAns = _("%s.%s: Device ""%s"" does not answer")

		if convstr(data(1),'l')=="default"
			tmp = noAns+_(". Former settings kept\n")
			for i = 1:length(nDev)
				t = 1+writeserial(Hport(i),ascii([nDev(i) 36 0 0 0 0]))
				if ~t,
					warning(msprintf(tmp, session, command, dev(i,1)))
					out = 0
				end
				sleep(tempoRead)
			end
			break
		end
		
		ALLs = parameters

		// Parsing & checking input arguments:
		// -----------------------------------
		if size(data)==1		// syntaxe: property1=value1,.. or 'default'
			tmp = data(1)
			if typeof(tmp)~="string"
				tmp = _("%s.%s.%s : arg#%d must be a string or a vector of strings\n")
				error(msprintf(tmp, session, command, dev(1), 3))
			end
			if size(tmp,"*")~=1 & size(tmp,2)~=2
				tmp = _("%s.%s.%s : Wrong size for arg#%d : must be a scalar or a nx2 matrix\n")
				error(msprintf(tmp, session, command, dev(1), 3))
			end
			sep = "/"
			if size(tmp,"*")~=1, tmp=strcat(strcat(tmp,"=","c"),sep), end
			if convstr(tmp,'l')=='default', tmp='default=0', end  // special case
			tmp = tokens(strsubst(tmp,sep,"="+sep+"="),"=")
			tmp(tmp==sep) = []
			tmp = matrix(tmp,[2,size(tmp,1)/2])'
			p = convstr(tmp(:,1),'l')    // p: properties
            v = eval(tmp(:,2))           // v: values
		else
			if typeof(data(1))~="string"
				tmp = _("%s.%s.%s : arg#%d must be of type string\n")
				error(msprintf(tmp, session, command, dev(1), 3))
			end
			if size(data)<2
				tmp = _("%s.%s.%s : arg#%d missing. Vector of reals expected\n")
				error(msprintf(tmp, session, command, dev(1), 4))
			elseif size(data(1))~=size(data(2))
				tmp = _("%s.%s.%s : arg#%d and arg#%d must have the same length\n")
				error(msprintf(tmp, session, command, dev(1), 3, 4))
			else
				t = typeof(data(2))
				if t=="string"
					[v,e] = evstr(data(2))
                    data(2) = v
					if e~=0, 
						tmp = _("%s.%s.%s : arg#%d must be real number(s)\n")
						error(msprintf(tmp, session, command, dev(1), 4))
					end
				elseif t ~= "constant"
					tmp = _("%s.%s.%s : arg#%d must be of type real\n")
					error(msprintf(tmp, session, command, dev(1), 4))
				end
			end
			p = convstr(data(1),'l')
            v = data(2)
		end
		
		// Extracting codes to be written 
		[iA,iD] = grep("."+convstr(ALLs(1:2:size(ALLs,2)),'l')+".","."+p'+".")
		[t,i] = gsort(iD,"g","i")
		propNum = eval(ALLs(2:2:size(ALLs,2)))
		propNumData = abs(propNum(iA(i)))	// For further sorting of results
		codes = propNum(iA(i))		// Codes to be read
		// Properties with code<0 are only readable (not processed in this "set" section)
		// Any 0 <code<100 refers to the command #code to set the parameter
		// Any code>99 refers to bit #(code-100) to be set with the command #53,40
		
		// Clearing the serial buffer to avoid overflow
		t = readserial(Hport), sleep(tempoRead)

		// Read the Settings of the "device mode":
		if or(codes>99)					// Asking for the current device mode:
			t = 1+writeserial(Hport, ascii([nDev 53 40 0 0 0]))
			if ~t
                error(msprintf(noAns+"\n", session, command, dev(1)))
            end
			sleep(tempoRead)
			t = ascii(readserial(Hport))	// Reading the answer
			setting = t(3)+256*t(4)
		end
		// MAIN LOOP
		
		for i = 1:length(codes) 
			c = codes(i)
			if c<100
                if c==-17             // store current position in memory #mem
                    c = 16  //actual command code
                    // v(i)== #mem must be an integer in [0 15]
                    d = [ v(i) 0 0 0 ]
				elseif or(c==[44 45 46 47])  // range positions maxMvr homeOffset
					d = convZaberData(v(i)/step)'
				elseif c==42							// Speed
					d = convZaberData(v(i)/9.375/step)'
				elseif c==43							// Acceleration
					d = convZaberData(v(i)/11250/step)'
				elseif c==48							// Alias number
					if v(i)<0 | v(i)>54
						tmp = _("%s.%s.%s : %s must be in [0,254]. %d ignored\n")
						warning(msprintf(tmp, session,command,dev(1),p(i),v(i)))
						d=0, out=0
					else d=[v(i) 0 0 0 ], end
				elseif c==49							// Locked state
					if v(i)~=0, V=1, else V=0, end
					d = [V 0 0 0]
				elseif c==38 | c==39					// activeCurrent, idleCurrent
					if v(i)<0 | v(i)>1
						tmp = _("%s.%s.%s : %s must be in [0,1]. %d ignored\n")
						warning(msprintf(tmp, session,command,dev(1),p(i),v(i)))
						d = 0
                        out = 0
					else
						if v(i)==0
                            d = [0 0 0 0]
						else 
                            d = [ min(round(10/v(i)),127) 0 0 0 ]
                        end
					end
				elseif c==37							// Resolution
					if v(i)<0.708 | v(i)>181
						tmp = _("%s.%s.%s : %s must be in {1 2 4 8 16 32 64 128}. %d ignored\n")
						warning(msprintf(tmp, session, command, dev(1), p(i), v(i)))
						d = 0
                        out = 0
					else
						d = [ 2^round(log2(v(i))) 0 0 0]
					end
				end

				if length(d)==4
					t = 1+writeserial(Hport,ascii([nDev c d]))
					if ~t
                        error(msprintf(noAns+"\n", session, command, dev(1)))
                    end
					sleep(tempoRead)
				end
				
			elseif c>99	  // settings in mode
				b = c-100	// bit #
				if or(b==[0 3 5 8 14 15])
					if v(i)==0, v(i)=1, else v(i)=0, end
				end
				if v(i)~=0
                    setting = bitset(setting, b+1)
				else 
                    setting = bitand(setting, 65535-round(2^b))
                end
			end
		end

		if isdef("setting","local")
			t = 1 + writeserial(Hport,ascii([nDev 40 convZaberData(setting)']))
			if ~t
                error(msprintf(noAns+"\n", session, command, dev(1)))
            end
			sleep(tempoRead)
		end

	// ========================================================================
	case 'get'			// 
		ALLs = parameters

		// Parsing args
		outString = %f	// By default, answer is a numerical vector
		if size(data)==0
            da = ALLs(1:2:size(ALLs,2))
		elseif size(data)>1
			da = data(1)
			outString = (data(2)==%t | data(2)=="%t" | data(2)=="%T")
		else // size(data)==1:
			outString = and(data(1)==%t | data(1)=="%t" | data(1)=="%T")
			if outString
                da = ALLs(1:2:size(ALLs,2))
            else 
                da = data(1)
            end
		end

		// Extracting codes to be read + preparing sorting of final result
		[iA,iD] = grep("."+convstr(ALLs(1:2:size(ALLs,2)),'l')+".","."+convstr(da,'l')+".")
		[t,i]   = gsort(iD,"g","i")
		propNum = eval(ALLs(2:2:size(ALLs,2)))
		propNumData = abs(propNum(iA(i)))	// For further sorting of results
		codes = propNum(iA(i))		// Codes to be read
		// Any code<0 refers to the command #|code| to get the info
		// Any 0 <code<100 refers to the command #53,code to get the info 
		// Any code>99 refers to bit #(code-100) answered to the command #53,40

		readserial(Hport), sleep(tempoRead)	// Clearing the serial buffer

		// Settings laying in the "device mode":
		r1 = []
        if or(codes>99)
            // Reading
            tmp = 1 + writeserial(Hport,ascii([nDev 53 40 0 0 0]))
            sleep(tempoRead)
            if ~tmp
                tmp = _("%s.%s: Device ""%s"" does not answer\n")
                error(msprintf(tmp, session, command, dev(1)))
            end
            tmp = ascii(readserial(Hport))
            sleep(tempoRead)
            infos = tmp(3) + 256*tmp(4)

            // Preparing the table of results
            bits = codes(codes>99)
            r = zeros(length(bits),6)

            // Parsing the Zaber infos output from "mode"
            b = bits-100
            n = length(b)
            t = bitand(ones(1,n)*infos,2^b)'
            t(t~=0) = 1
            r(:,6) = t
            i = find(b==0|b==3|b==5|b==8|b==14|b==15)
            r(i,6) = 1-r(i,6)
            r1 = r
            r1(:,2) = bits.'
        end
        //
        for c = codes
            if c<0
                tmp = 0
                if c==-17 // storedPos
                    tmp = data(2)   // #mem where the stored pos must be read
                    if typeof(tmp)=="string" then
                        tmp = eval(tmp)
                    end
                end
                tmp = 1 + writeserial(Hport,ascii([nDev -c tmp 0 0 0]))
            elseif c<100
                tmp = 1 + writeserial(Hport,ascii([nDev 53 c 0 0 0]))
            end
            sleep(tempoRead)
            if ~tmp
                tmp = _("%s.%s: Device ""%s"" does not answer\n")
                error(msprintf(tmp, session, command, dev(1)))
            end
        end
        sleep(tempoRead)
        r = readserial(Hport), sleep(tempoRead)	// cleaning the serial buffer
        r = ascii(r)

		r = matrix(r,[6, length(r)/6])'
		r(:,5) = r(:,3)+256*(r(:,4)+256*r(:,5))
		r(:,6) = r(:,5)

		// Processing outputs:
        i = find(r(:,2)==51), r(i,6) = r(i,5)/100			// version
        i = find(r(:,2)==52), r(i,6) = r(i,5)/10			// voltage [V]
        i = find(r(:,2)==17 | (r(:,2)>=44 & r(:,2)<=47)) // stored position | range 
           r(i,6) = r(i,5)*step 	// |current position |maxMvr |homeOffset [° | mm] 
        //i=find(r(:,2)==37), r(i,6)=r(i,5)                // resolution
        i = find((r(:,2)==38 | r(:,2)==39) & r(:,5)~=0)    // activeCurrent 
			 r(i,6) = 10 ./ r(i,5)                        //   & idleCurrent
        i = find(r(:,2)==42), r(i,6)=r(i,5)*9.375*step	// nominalSpeed [°/s|mm/s]
		i = find(r(:,2)==43), r(i,6)=r(i,5)*11250*step	// acceleration [°/s2|mm/s2]

		// Sorting the rows of the result according to the order of the 
		//  querried properties
		r = [r1 ; r]		// All results
		[t,ir]  = gsort(r(:,2),"g","i")
		[t,ip]  = gsort(propNumData,"g","i")
		[t,ip2] = gsort(ip,"g","i")
		r = r(ir(ip2),:)

		//if outString, out=[da' emptystr(da')+":" string(r(:,6)) string(r(:,2))], end
		if outString, out = [da' string(r(:,6)) ]
		else out = r(:,6)
		end

	// =======================================================================
    case 'wait'
        moving = %t
        while moving
            tmp = zaber("status",dev(:,1))
            moving = or(tmp(1)~=0)
        end
        out = zaber("pos",dev(:,1))

	// =======================================================================
	else 	// to allow grouped cases
                            // [ nickname portNumber ToR rankOnLine ]
        m = ['mva' 'mvr' 'mvc' ]
        if or(command==m)    // OK
            if typeof(data(1))=="string"
                dx = eval(data(1))
            else
                dx = data(1)
            end
            ns = dx ./ step                          // mm | ° => number of steps
			if command=='mvc'
                ns = ns / 9.375 // speed mm/s | °/s
            end  
			if size(ns,1)>1, ns = ns', end
			z = convZaberData(ns)
			c = 19 + find(m==command)              // mva= 20  mvr= 21  mvc= 22
			if size(nDev,2)==1, nDev = nDev', end
			tmp = [nDev ; ones(nDev)*c ; z]

			// All devices are run
			for i = 1:size(z,2)
				out = 1 + writeserial(Hport(i),ascii(tmp(:,i)'))
				if ~out	// => Warning instead of a stopping error
					tmp = _("%s.%s: Addressing error for device ""%s""\n");
					warning(msprintf(tmp, session, command, dev(i,1)))
				end
			end

        elseif command=='mvs'      // mvs = Move to Stored
            if typeof(data(1))=="string"
                dx = eval(data(1))
            else
                dx = data(1)
            end
			if size(nDev,2)==1, nDev = nDev', end
			tmp = [nDev ; ones(nDev)*18 ; dx(:)'; zeros(3,length(dx))]

			// All devices are run
			for i = 1:length(dx)
				out = 1 + writeserial(Hport(i),ascii(tmp(:,i)'))
				if ~out	// => Warning instead of a stopping error
					tmp2 = _("%s.%s: Addressing error for device ""%s""\n");
					warning(msprintf(tmp2, session, command, dev(i,1)))
				end
			end
        
		elseif command=='home' | command=='stop'
			if command=='home', c = 1,
			elseif command=='stop', c = 23
			end
			for i = 1:length(nDev)
				tmp = 1 + writeserial(Hport(i),ascii([nDev(i) c 0 0 0 0]))
				if ~tmp,
					tmp = _("%s.%s: The device ""%s"" does not answer\n");
					error(msprintf(tmp, session, command, dev(i,1)))
				end
			end
			// %t can be given in data(1), but later expected in data(2)
			if size(data)>0, data(2) = data(1), end
		end

	// =======================================================================
	end
	if ~isdef("out","local"), out = "", end
endfunction
