// TITLE     : Scilab driver for the oscilloscopes Metrix OX8040, OX8050, 
//              OX8100, OX8042 and OX8062
// AUTHOR (c): Samuel GOUGEON - Université du Maine / ENSIM (France)
// SOFTWARE  : for Scilab >= 5.3 : http://www.scilab.org/
// RELEASE   : 1.1
// LICENSE   : CeCILL-C
//   http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
// DEPENDENCY: http://atoms.scilab.org/toolboxes/serial must be loaded
// DISCLAIMER : No warranty of any kind is provided. This driver is available 
//  "as is". Be aware and use it to your own risks & responsability
// RELEASE HISTORY
//   2012-04-13 : Initial public release (1.0)
//   2012-04-16 : Bugs fixed (see release notes) (1.1)
// ----------------------------------------------------------------------------

function r = osciOX80(C,varargin)
// PURPOSE: 
//  This script is a Scilab driver for the Metrix OX8040, OX8050, OX8100,
//  OX8042 and OX8062 oscilloscopes.
//
// REQUIREMENTS: 
//  * 1 serial RS-232 port must be available on the computer. A USB port
//     may be converted into RS-232 ones with a USB-Serial converter.
//  * Connexion parameters are given herebelow ('open' command)
//  * Serial TCL toolbox (E. Segre) + Scilab > 5.2 :
//     http://atoms.scilab.org/toolboxes/serial must be installed
//
// GENERAL equivalent SYNTAXES:
//  osciOX80(<command>[, <parameter#1>[, <parameter#2>]])
//  osciOX80 <command> <parameter#1> <parameter#2>
//
// <command> is a case-unsensitive string specifying the action to perform.
//
// COMMANDS
// ========
// The main existing features are implemented. However, Cursors management,
// Trigger management, Glitch capture mode, Probe settings, Harcopy settings,
// and other minor features or features specific to a given oscilloscope model
// are not implemented. 
// In the other hand, some features unavailable on the device are added, such 
// as saving or restoring a set of device parameters, disabling buttons & knobs,
// or recording time evolution of some signal parameters measured in automatic
// mode.
//
// SERIAL OPERATIONS:
// -----------------
// open <port_number> : 
//        Opens the specified serial port numbered from 1 to 254.
//        Default is '1'. The communication parameters are set to 
//        19200 bauds, no parity, 8 databits, 1 stop bit. Parameters
//        set on the device must be these ones (Press Utility => RS232..) 
//        Example: osciOX80('open',1)   // Connexion on COM1 port
//
// close : Unlocks the front panel, 
//          Closes the serial port communication, and
//          Destroys the related TCL session.
// clean : Destroys the TCL session (whether for some reasons the osciOX80 
//          session has been avorted)
//
// GENERAL SETTINGS (for both analog and digital modes)
// ------------------------------------------------
// lock   : Disables all commands on the device (buttons & knobs of front panel)
// unlock : Enables all commands on the device (buttons & knobs of front panel)
// lock?  : returns %T if buttons & knobs are disabled on the device, %F otherwise.
//
// autoset: Triggers the autoset
//
// save, filename: Saves the current device's settings in a binary file
//                 The binary settings block is returned as a string
// restore, filename: Resets the devices according to parameters saved in the 
//                  specified binary file.
//
// model? : Returns either 'OX8040', 'OX8050', 'OX8100', or 'OX8042,OX8062'
// vers?  : Returns the version of the firmware (string 'YYYY.n' where YYYY is
//          the year, and n is the version number in this year)
//
// MEASUREMENT MODES
// -----------------
// analog  : Turns the device into analog mode
// digital?: returns %T if the oscillo runs in digital mode, %F in analog mode
// digital : Turns the device into digital mode (in refresh mode: For each
//           channel, when the memory is filled by a sweep, the next sweep
//           starts recording the forthcoming points at the BEGINNING of the
//           previous trace and goes on overwriting it from left to right.
// envelope: Turns the device into digital envelope mode:
//           For each channel and for each horizontal position, the minimum 
//           and the maximum values reached over time are displayed.
// rolling : Turns the device into digital rolling mode: For each channel,
//           when the memory is filled by the sweep and a new point is measured,
//           the trace is shifted to the left by one point (dropping the first 
//           point queued) and the new point is appended to the right at the
//           freed position.
//
// MEASUREMENT SETTINGS (in analog or digital modes)
// --------------------
// dispSig, 'CH1' | 'CH2' | '-CH2' | 'DUAL' | 'XY' | 'ADD' | 'DIFF'
//            Chooses the signal(s) to be displayed
// dispSig? : Returns 'CH1' | 'CH2' | '-CH2' | 'DUAL' | 'XY' | 'ADD' | 'DIFF'
//             'DUAL' is 'time, CH1' and 'time,CH2' 
//             'ADD'  is 'time, CH1+CH2'
//             'DIFF' is 'time, CH1-CH2'
//             'XY'   is 'CH1, CH2'
//
// timeScale? : Returns the sweeping time over 1 division (real number)
//              Possible values are: 10ns,20ns,50ns,100ns,200ns,500ns,1µs,2µs,
//              5µs,10µs,20µs,50µs,100µs,200µs,500µs,1ms,2ms,5ms,10ms,20ms,
//              50ms,100ms,200ms,500ms,1s,2s,5s,10s,20s,50s,100s,200s
//              For OX8042 & OX8062 models, 5ns is also available.
//
// timeScale, dt : Sets to dt seconds the sweeping time over 1 division
//              Possible values are listed above. If the given value is not
//              listed, the closest one in the list is set.
//
// Vscale?, #channel : Returns the Voltage over 1 vertical division (in V)
//            for the given #channel (1 | 2). output is a real number
//            All possible Vscale are: 1mV,2mV,5mV,10mV,20mV,50mV,100mV,
//                           200mV,500mV,1V,2V,5V,10V,20V,50V,100V,200V
//            OX8050 range          : 1mV  -  20V
//            OX8100 range          : 2mV  -   5V
//            OX8040 range          : 5mV  -  20V
//            OX8042 & OX8062 range : 10mV - 200V
//
// Vscale, #channel, value : Sets the Vscale to value for the given #channel
//            #channel = 1 | 2
//            value must be a real (in V) in the list given above.
//            Otherwise, the listed value closest to the given one is used.
//            Lower & upper bounds are checked: Overflow goes to the bound
//            Examples: 
//                 osciOX80  Vscale  1 0.01    // sets CH1 to 10mV/div
//                 osciOX80('Vscale',2,5e-3)   // sets CH2 to 5mV/div
//
// Coupling?, #channel     : Returns the coupling mode for the specified signal
//                           #channel may be 1 (CH1) or 2 (CH2)
//                           Answers are: 'AC' | 'DC' | 'GND'
// Coupling, #channel, mode : Sets the coupling mode for the given #channel
//                           Available modes: 'AC'|'DC'|'GND' (case-unsensitive)
//                           Example: osciOX80 coupling 1 DC // or equivalently
//                                    osciOX80('coupling',1,'DC')
// CAPTURING SIGNALS
// -----------------
// This feature is possible only in digital mode. However, if the device is
// currently in analog mode, it is switched to digital, the signal is caught,
// and finally the initial analog mode is restored.
//
// getSig, channel [,NbPoints] : Returns the querried signal (2 columns of 
//                   reals (time[s],V)).
//                 channel may be: 1 | '1' | 'CH1', or 2 | '2' | 'CH2'
//                   In enveloppe mode, additional channels are available:
//                    'CH1L': Low   enveloppe of CH1
//                    'CH1H': Hight enveloppe of CH1
//                    'CH2L': Low   enveloppe of CH2
//                    'CH2H': Hight enveloppe of CH2
//                 NbPoints : Number of points to be sampled. Possible 
//                   values (real) are 1000, 8000, or 16000. Default value
//                   = 1000, corresponding to signals displayed within the
//                   screen's width.
//                 Example: dat = osciOX80('getsig',1)
//                           clf, plot(dat(:,1),dat(:,2))
//
// AUTOMATIC MEASUREMENTS of SIGNAL PARAMETERS (for analog and digital modes)
// -------------------------------------------
// The oscilloscope can determine in a dynamical way some parameters of the
// current periodic signal (on each channel), such as the frequency, the 
// amplitudes, etc. 
// The following commands allow to choose the parameter among available ones, 
// and to query its running value
//
// 'getParam', channel, type, number, timeStep, hAxes
//        channel:  1 | 2     XY display mode is forbidden
//        number : Number of consecutive measurements to be done (default = 1)
//       timeStep: Delay (approximate) [s] between 2 consecutive measurements
//                 default value = 1 s. minimum value = 0.3 s
//        type   :  VOLTAGES (in [V]):
//                 'Vpp'  = Peak-to-Peak Voltage amplitude
//                 'Vrms' = Effective Voltage amplitude (Root Mean Square)
//                 'Vavg' = Average Voltage (over the displayed time interval)
//                 'Vmin' = Minimum Voltage
//                 'Vmax' = Maximum Voltage
//                 'Vhigh'= (not clear... see the device's doc)
//                 'Vlow' = (not clear... see the device's doc)
//                 'Vamp' = Voltage amplitude (= Vhigh - Vlow)
//                 TIME PARAMETERS:
//                 'Freq' = Frequency [Hz]
//                 'T'    = Periode [µs]
//                 'phase'= Phase offset between CH1 and CH2, in [-180, 180]°
//                          DUAL display mode is required
//                 'tR'   = Rising time [µs]
//                 'tF'   = Falling time [µs]
//                 'W+'   = Time width of positive impulse [µs]
//                 'W-'   = Time width of negative impulse [µs]
//                 'DC+'  = Duty cycle of positive impulse, in [0,100]%
//                 'DC-'  = Duty cycle of negative impulse, in [0,100]%
//
//       hAxes  : Handle of the Figure or Axes in which dynamic plotting is
//                required. The axes is not cleared before starting.
//                hAxes may also be %T. Then the figure and axes are created.
//                hAxes is used only is several measurements are expected.
//
//      OUTPUT : For 1 single measurement, its real numerical value is returned
//               For a serie of measurements, a (number,2) matrix of reals
//               is returned, with out(:,1)=times [s]  and out(:,2) = values
//
// DIGITAL MODE
// ------------
// digital : Turns the device into digital normal mode (= refresh. See above)
// envelope: Turns the device into digital envelope mode (see above)
// rolling : Turns the device into digital rolling mode (see above)
//
// run        : Runs acquisition and dynamical display (no effect in analog)
// stop       : Stops acquisition and dynamical display (idem)
// abort      : Stops immediately a running acquisition (idem)
// clearTrace : Clear the trace(s). This command can be particularly useful
//              when using the envelope mode.
//
// sample?         : Returns the (maximal) number of sampled points. 
//                   Possible values (real) are 1000, 8000, or 160000
//
// sample, nbPoints: Sets the (maximal) sample's length
//                   Possible values (real) are given above. Returns the
//                   actual number set (the closest in the list)(real)
//
// rollLength, length: in Rolling acquisition mode, sets the number of points
//               to be sampled (max = nbPoints)
//             length is an integer in [0,100] = % of nbPoints. 
//             Possible values are: 0  25  50  75  100
//             For OX8042, OX8062 and OX8100 models, in addition,
//               . length=1000 is a possible value if nbPoints = 8000
//               . length=1000|2000 are possible values if nbPoints = 16000
//             After <length> points have been sampled, the acquisition stops.
//             length=-1 enables a continuous rolling acquisition.
//            The command returns the actual number of points set as length 
//             (real number)(-1 for 'OFF')
//
// rollLength? : Querries and returns the number of points in rolling mode
//

// ---------------------------------------------------------------------------

    fn = "osciOX80"        // Function Name (for error messages)

    // STARTING TESTS
    // ==============
    if argn(2)==0, head_comments(fn)
        r = ''
        return
    end

    if ~isdef("C","local") | typeof(C)~="string"
        tmp = _("%s: Any command must be specified in arg#1 as a string\n")
        error(msprintf(tmp, fn))
    else
        c = convstr(C,'l')
    end
    if ~exists('openserial')
        tmp = _("%s: TCL serial toolbox unavailable. Please load it before adressing the scope\n")
        error(msprintf(tmp, fn))
    end
    if isdef("varargin","local") & length(varargin)>0
        d = varargin
        d1 = d(1)
        if typeof(d1)=='string'
            d1 = convstr(d1,'u')
        end
    else d = [], d1 = ''
    end

    // INITIALIZATIONS
    // ===============
    NL  = ascii(10)            // New Line
    tempo  = 20                // Temporisation [ms]
    r   = []                   // Default answer
    
    if c=="clean"
        if TCL_ExistInterp(fn), TCL_DeleteInterp(fn), end
        r = 1
        return
    end

    // Error messages
    errorMessages = struct(..
          "-100","Command error",..
          "-101","Invalid character",..
          "-102","Syntax error",..
          "-103","Invalid separator",..
          "-104","Data type error",..
          "-105","GET not allowed",..
          "-108","Parameter not allowed",..
          "-109","Missing parameter",..
          "-111","Header separator error",..
          "-112","Program mnemonic too long",..
          "-113","Undefined header",..
          "-114","Header suffix out of range",..
          "-121","Invalid caracter in number",..
          "-123","Numeric overflow",..
          "-124","Too many digits",..
          "-128","Numeric data not allowed",..
          "-130","Suffix error",..
          "-131","Invalid suffix",..
          "-138","Suffix not allowed",..
          "-140","Character data error",..
          "-141","Invalid character data",..
          "-144","Character data too long",..
          "-148","Characetr data not allowed",..
          "-150","String data error",..
          "-151","Invalid string data",..
          "-158","String data not allowed",..
          "-160","Block data error",..
          "-161","Invalid block data",..
          "-168","Block data not allowed",..
          "-170","Expression error",..
          "-171","Invalid expression",..
          "-178","Expression data not allowed",..
          "-200","Execution error",..
          "-211","Trigger ignored",..
          "-221","Settings conflict",..
          "-222","Data out of range",..
          "-223","Too much data",..
          "-300","Device specific error",..
          "-310","System error",..
          "-350","Too many errors",..
          "-400","Query error",..
          "-410","Query INTERRUPTED",..
          "-420","Query UNTERMINATED",..
          "-430","Query DEADLOCKED",..
          "-440","Query UNTERMINATED after indefinite response"..
          )
     Lerr = fieldnames(errorMessages)    // List of error codes

    // Simple commands 
    commands = struct("lock",      "SYST:KLOCK ON%s",..
                      "unlock",    "SYST:KLOCK OFF%s",..
                      "lock?",     "SYST:KLOCK?%s",..
                      "vers?",     "SYST:VERS?%s",..
                      "restore",   "SYST:SET ",..
                      "analog",    "MODE AN%s",..
                      "digital?",  "MODE?%s",..
                      "digital",   "ACQ:MODE REFR;PEAK OFF%s",..
                      "envelope",  "ACQ:MODE REFR;PEAK ON%s",..
                      "rolling",   "ACQ:MODE ROLL%s",..
                      "run",       "INIT:CONT ON%s",..
                      "stop",      "INIT:CONT OFF%s",..
                      "abort",     "ABOR%s",..
                      "cleartrace","DISP:TRAC:CLEAR%s",..
                      "autoset",   "AUT%s",..
                      "dispsig?",  "DISP:MODE?%s",..
                      "dispsig",   "DISP:MODE %s",..
                      "vscale?",   "INPUT%s:RANGE?",..
                      "vscale",    "INPUT%s:RANGE %s",..
                      "timescale?","TIM:RANG?%s",..
                      "timescale", "TIM:RANG %s",..
                      "coupling?", "INPUT%s:COUP?",..
                      "coupling",  "INPUT%s:COUP %s",..
                      "sample?",   "TIM:POIN?%s",..
                      "sample",    "TIM:POIN %s",..
                      "rolllength","ACQ:REF %s",..
                      "rolllength?","ACQ:REF?%s"..
                      )
     Lcommands = convstr(fieldnames(commands),'l')  // List of simple commands

    if TCL_ExistInterp(fn)
        port = TCL_GetVar("port",fn)    // Recovering the port ids (unique) 
        ph = port(2)                    // (1)= #COM  (2) = Handle
    else TCL_CreateSlave(fn)
    end

// -------------------------------

    // PARSING COMMANDS
    // ================
    if c=="open"						// 'open' [,portNumber=1]
        if isdef("port","local")
            tmp = _("%s: Port #%s is already opened for a osciOX80 device. Multiple osciOX80 mode is not yet supported. => Ignoring this command\n")
            warning(msprintf(tmp, fn, port(1)))
        else
            ok? = %f
            if ~isdef("d","local")
                d = 1
                ok? = %t
            elseif typeof(d1)=="string" & isnum(d1)
                d = strtod(d1)
                if d==fix(d) & d>0 & d<256, ok? = %t, end
            elseif typeof(d1)=="constant" & d1==fix(d1) & d1>0 & d1<256, 
                d = d1
                ok? = %t
            end
            if ~ok?
                tmp = _("%s.%s: Port number expected as integer in [1,255] in arg#%d\n")
                error(msprintf(tmp, fn, C, 2))
            end
//            ph = openserial(d,"19200,n,8,1",handshake="xonxoff")
            ph = openserial(d,"19200,n,8,1")
            sleep(100)
            TCL_EvalStr("fconfigure "+ph+" -buffersize 20000 -sysbuffer 20000")
            sleep(100)
            TCL_SetVar("port",[msprintf("%g",d) ph],fn) // port(1)=#COM port(2)=handle
            r = ph
        end

    // ----------------------------------------------------------------------

    elseif c=="close"
        osciOX80("unlock")
        try
            r = 1+closeserial(ph)
        catch
            r = 0
            disp("Error when closing the OX80 session")
        end
        try, TCL_DeleteInterp(fn), end

    // ----------------------------------------------------------------------

    elseif c=="model?"
        OX80_clearErr(ph)    // Clears the error queue
        io_OX80(ph, "INPUT1:MODE?", tempo)
        err = OX80_readErr(ph)
        if err=="-113"        // OX8040 | OX8050 | OX8100
            trigmode = io_OX80(ph,"TRIG:MODE?", tempo)    // Saving before test
            io_OX80(ph,"TRIG:MODE PTP", tempo)            // Test
            err = OX80_readErr(ph)
            if err=='-151'
                r = "OX8040"
            else
                io_OX80(ph,"TRIG:B?", tempo)
                err = OX80_readErr(ph)
                if err=='-113'
                    r = "OX8050"
                else
                    r = "OX8100"
                end
            end
            io_OX80(ph,"TRIG:MODE "+trigmode, tempo)    // Restoring
        else                  // "OX8042,OX8062"
            r = "OX8042,OX8062"
        end
    // ----------------------------------------------------------------------

    elseif c=='getparam'  // 'getParam', channel, type, number, timeStep, hAxes

        // ARGUMENTS CHECKING & INIT
        // -------------------------
        // Channel: format of specification
        CH = d1
        if typeof(CH)=='constant'
            CH = string(CH)
        elseif CH=='CH1'
            CH = '1'
        elseif CH=='CH2'
            CH = '2'
        end
        // Type of measurement required
        if length(d)<2 then
            tmp = _("%s.getParam: Type of measurement not specified\n")
            error(msprintf(tmp,fn))
        end
        Mtype = d(2)
        if typeof(Mtype)~='string' then
            tmp = _("%s.getParam: Type of measurement must be specified as a STRING\n")
            error(msprintf(tmp,fn))
        else
            Mtype = convstr(Mtype,'u')    // TYPE's name in UPPER CASE
        end
        Tcommands = struct('VRMS','AC',..
                          'VAVG', 'DC',..
                          'VAMP', 'AMPL',..
                          'VLOW', 'LOW',..
                          'VHIGH','HIGH',..
                          'VMIN', 'MIN',..
                          'VMAX', 'MAX',..
                          'VPP',  'PTP',..
                          'FREQ', 'FREQ',..
                          'T',    'PER',..
                          'PHASE','PHAS',..
                          'TR',   'RTIM',..
                          'TF',   'FTIM',..
                          'W-',   'NWID',..
                          'W+',   'PWID',..
                          'DC+',  'PDUT',..
                          'DC-',  'NDUT'..
                          )
        if  ~or(Mtype==fieldnames(Tcommands))
            tmp = _("%s.getParam: Type of measurement UNKNOWN\n")
            error(msprintf(tmp,fn))
        else
            Tcomm = "MEAS:SCAL:VOLT:"+Tcommands(Mtype)+"? (@"+CH+")"
        end
        
        // Number of measurements
        if length(d)<3 then
            nM = 1
        else
            if typeof(d(3))=='string' & isnum(d(3)) then
                nM = max(1,ceil(evstr(d(3))))
            elseif typeof(d(3))=='constant'
                nM = max(1,ceil(d(3)))
            else
                tmp = _("%s.getParam: The specified number of measurements IS NOT a number\n")
                error(msprintf(tmp,fn))
            end
        end
        // Time Step
        dt = 1    // [s]  Default value
        if nM>1 & length(d)>3
            if typeof(d(4))=='string' & isnum(d(4)) then
                dt = max(0.3,abs(evstr(d(4))))
            elseif typeof(d(4))=='constant'
                dt = max(0.3,abs(d(4)))
            else
                tmp = _("%s.getParam: Time Step MUST BE a positive number\n")
                error(msprintf(tmp,fn))
            end
        end
        // Plotting ?
        ax = []            // default = No dynamical plot
        if length(d)>4 then
            if typeof(d(5))=='boolean' then
                ax = gca()
            elseif typeof(d(5))=='handle'
                ax = d(5)
                if ax.type=='Figure' then
                    scf(ax)
                    ax = gca()
                else
                    sca(ax)
                end
            end
            // Deleting existing curve(s)
            if length(ax.children)>0, delete(ax.children), end
        end
        // Checking unsupported configurations
        tmp = osciOX80('dispSig?')
        if  Mtype=='PHASE' & tmp~='DUAL'
            tmp = _("%s.getParam: Phase measurement: DUAL display mode is required\n")
            error(msprintf(tmp,fn))
        end
        if tmp=='XY'
            tmp = _("%s.getParam: XY display mode cannot be used for parametric measurements\n")
            error(msprintf(tmp,fn))
        end
        if (tmp=='CH1' & CH=='2') |  (tmp=='CH2' & CH=='1')
            tmp = _("%s.getParam: CH%s to be measured must be displayed\n")
            error(msprintf(tmp, fn, CH))
        end
            // Other possible errors
        r = ''
        i = 0
        imax = 3
        while r=='' & i<imax, r = io_OX80(ph,Tcomm, tempo), end
        t0 = getdate()
        t = 0
        dates = t
        if r=='not applicable'
            r = []
            tmp = _("%s.getParam: CH%s is decalibrated. Please lock its VAR knob at the CAL position\n")
            error(msprintf(tmp, fn, CH))
        elseif r=='failed'
            r =[]
            tmp = _("%s.getParam: Measurement FAILED. At least 1.5 period must be displayed\n")
            error(msprintf(tmp, fn))
        elseif r==''
            tmp = _("%s.getParam: The scope does not answer (%d temptatives)\n")
            error(msprintf(tmp, fn, imax))
        else
            if ~isnum(r) then
                r = []
                tmp = _("%s.getParam: ERROR for measurement #%d: %s\n")
                error(msprintf(tmp, fn, m, r))
            else
                r = evstr(r)
                Tfactor = 1
                if strindex(Mtype,'V')~=[], unit = '[V]'
                elseif Mtype=="FREQ",       unit = "[Hz]"
                elseif Mtype=="PHASE",      unit = "[°]"
                elseif or(Mtype==["T" "W+" "W-" "TR" "TF"])
                    unit = "[µs]"
                    Tfactor = 1e6
                elseif or(Mtype==["DC+" "DC-" ]), unit = "% of T"
                end
                if nM>1 & ax~=[]
                    drawlater
                    plot(0, r*Tfactor, "-k.","markSize",3)
                    ax.mark_size_unit = 'point'
                    ax = ax.children(1).children(1)
                    xtitle('','Time [s]',d(2)+" "+unit)
                    drawnow
                end
            end
        end

        // Main Measurement Loop
        // ---------------------
        for m = 2:nM
            delay = t - (m-1)*dt
            if delay<0
                sleep(max(1,(-delay-0.15)*1000))
            end
            t = etime(getdate(),t0)       // [s.ms]
            dates = [dates t ] 
            v = ''
            i = 0
            while v=='' & i<imax
                v = io_OX80(ph, Tcomm, tempo)
                i = i+1
            end
            if ~isnum(v) then
                tmp = _("%s.getParam: ERROR for measurement #%d: ")
                if v==''
                     tmp = tmp + _('The scope does not answer (%d temptatives)')
                     error(msprintf(tmp+"\n", fn, m, imax))
                else 
                    tmp = tmp + v
                    error(msprintf(tmp+"\n", fn, m))
                end 
            end
            v = evstr(v)
            r = [ r v ]
            // Dynamical plotting
            if ax~=[]
                ax.data = [ax.data ; t Tfactor*v ]
                D = ax.data
                replot([min(D(:,1)) min(D(:,2)) max(D(:,1)) max(D(:,2)) ])
            end
        end
        // Returning data
        r = r * Tfactor    // => some data in µs instead of [s] measured
        if nM>1 then
            r = [dates' r']
        end

    // ----------------------------------------------------------------------

    elseif c=="getsig"            // pages 50 (get) & 35 (format)
        // Saving initial modes
        Save.modeNum  = io_OX80(ph,"MODE?", tempo)
        Save.formData = io_OX80(ph,"FORM:DATA?", tempo)
        Save.initCont = io_OX80(ph,"INIT:CONT?", tempo)
        // settings
        io_OX80(ph,"MODE NUM", tempo)
        io_OX80(ph,"FORM:DATA INT", tempo)// = unsigned byte: the most compact
        io_OX80(ph,"FORM:DINT ON", tempo) // => ADIF header (for Voffset & pointScales)

        // Transfer time for tempo
        nPoints = 1000    // default
        if argn(2)>2      // specified nPoints
            tmp = varargin(2)
            if typeof(tmp)=='string'
                tmp = evstr(tmp)
            end
            nPoints = osciOX80('sample',tmp)
        end
        write(%io(2),msprintf("nPoints = %d",nPoints))
        dt_Transfer = (evstr(nPoints)+150)/2400
        
        // Acquisition time
        dtP = osciOX80('timescale?')
        dtAcq = dtP*nPoints/100    // [s]

        // Acquiring data
        write(%io(2),"Running Acquisition...")
        io_OX80(ph,"INIT:CONT ON",tempo)     // START
        sleep((dtAcq+1)*1000)
        io_OX80(ph,"INIT:CONT OFF",tempo)    // FREEZE

        // Recording data
        if typeof(d1)=='constant' then
            d1 = string(d1)
        end
        if nPoints>1000 
            if strindex(d1,"1")~=[]
                ch = "1"
            else
                ch = "2"
            end
            write(%io(2),"Saving data on the oscilloscope...")
            io_OX80(ph,"DISP:SAVE"+ch+" ON",tempo)  // Triggers saving the trace
            sleep(dt_Transfer*1000*1.3)   // ... saving the trace
        end

        // Transfering data
        if (d1=="CH1" | d1=='1')
            if nPoints==1000
                d1 = "S1"
            else
                d1 = "CH1"
            end
        elseif (d1=="CH2" | d1=='2')
            if nPoints==1000
                d1 = "S2"
            else
                d1 = "CH2"
            end
        end
        // Cleaning the output buffer:
        TCL_EvalStr("set ttybuf [read "+ph+"]")
        sleep(1000)    // DO NOT MODIFY!
        write(%io(2),"Transfering data to the computer...")
        tmp = 1 + writeserial(ph,"TRAC:DATA? "+d1+ascii(10))
        sleep(dt_Transfer*1000*1.6)    // 1.6 ok needed. DO NOT MODIFY!
        if ~tmp
            tmp = _("%s: Command ""%s"": OX80 oscillo (id ""%s"") does not answer\n")
            error(msprintf(tmp, fname, command, ph))
        end
        TCL_EvalStr("set ttybuf [read "+ph+"]")    // Read the result
        sleep(dt_Transfer*1000*1.7)    // 1.7 needed. DO NOT MODIFY!
        TCL_EvalStr("set fid [open ox80.tmp w]")
        TCL_EvalStr("fconfigure $fid -translation binary")
        TCL_EvalStr("puts -nonewline $fid $ttybuf")
        TCL_EvalStr("close $fid")
        fid   = mopen("ox80.tmp",'rb')
        fullData = mgeti(20000,'uc',fid)        // uc = unsigned char
        mclose(fid)
        mdelete("ox80.tmp")
        write(%io(2),"DONE !")
            // disp(size(fullData))
        data = fullData($-nPoints-4:$-5)
        header = strcat(ascii(fullData(1:$-nPoints-5)))
            // disp(header)
        [d,f,t] = regexp(header,'/IMPL SCALE (.*?) SIZE/')
        spS = evstr(part(t,12:length(t)-4))            // [s/sample]
        [d,f,t] = regexp(header,'/EXPL SCALE .*? OFFSET/')
        VpL = evstr(strsubst(part(t,12:length(t)-6),">",""))    // [V/Level]
        [d,f,t] = regexp(header,'/OFFSET .*? SIZE/')
        offset = evstr(part(t,8:length(t)-4))          // Vertical offset

        // Retrieving data
        v = (double(data)-offset-2)*VpL
        t = (0:length(v)-1)*spS
        r = [t',v']

        // Restoring previous options:
        io_OX80(ph,"FORM:DATA "+Save.formData,tempo) // Restore data format
        if isdef("ch","local")    // Releasing the saved channel
            io_OX80(ph,"DISP:SAVE"+ch+" OFF",tempo)
        end
        io_OX80(ph,"INIT:CONT "+Save.initCont,tempo) // Restore display mode
        io_OX80(ph,"MODE "+Save.modeNum,tempo) // Restore digital|analog mode

    // ----------------------------------------------------------------------

    elseif c=="save"    // Saving current device parameters
        // Cleaning the buffer
        TCL_EvalStr("flush "+ph)                // Flushing any preceeding output
        TCL_EvalStr("set ttybuf [read "+ph+"]") // Cleaning
        sleep(50)
        // Querrying the settings
        tmp = 1 + writeserial(ph,"SYST:SET?"+ascii(10))
        sleep(50)
        TCL_EvalStr("set ttybuf [read "+ph+"]")    // Read the result
        TCL_EvalStr("set fid [open "+d1+" w]")
        TCL_EvalStr("fconfigure $fid -translation binary -blocking 0")
        TCL_EvalStr("puts -nonewline $fid $ttybuf")
        TCL_EvalStr("close $fid")
        r = TCL_GetVar("ttybuf")

    // ----------------------------------------------------------------------
    elseif or(c==Lcommands)
        comm = commands(c)

        // Pre-processing of arguments
        // ===========================
        select c
        case "restore"
            [fid, err] = mopen(d1,'rb')
            if err~=0 then
                tmp = _("%s.save: Error (%d) when opening file %s => SETTINGS NOT SAVED")
                warning(msprintf(tmp, fn, err, d1))
                r = %f
                return
            end
            d1 = mgeti(200,"uc",fid)
            mclose(fid)
            //disp(dec2hex([d1]))
            comm = comm+ascii(d1)

        case "dispsig"
            if typeof(d1)=='string'
                if or(d1==['DIFF' '-CH2']) tmp = 'ON', else tmp = 'OFF', end
                io_OX80(ph, "INPUT2:INV "+tmp, tempo)
                if d1 == 'DIFF'
                    d1 = 'ADD'
                elseif d1=='-CH2'
                    d1 = 'CH2'
                end
            end

        case "vscale"                    // page 39
            Vvalues = ["1E-3" "2E-3" "5E-3" "10E-3" "20E-3" "50E-3" "100E-3" ..
                       "200E-3" "500E-3" "1" "2" "5" "10" "20" "50" "100" "200"]
            if typeof(d(2))=="string" then
                d(2) = abs(evstr(d(2)))
            end
            if d(2)<7.6e-3 | d(2)>7.4 then
                execstr('tmp='+fn+"(''model?'')")
                if tmp=="OX8040"
                    d(2) = min(20,max(d(2),5e-3))
                elseif tmp=="OX8050"
                    d(2) = min(20,max(d(2),1e-3))
                elseif tmp=="OX8100"
                    d(2) = min(5,max(d(2),2e-3))
                elseif tmp=="OX8042,OX8062"
                    d(2) = min(200,max(d(2),1e-2))
                end
            end
            [tmp, i] = min(abs(evstr(Vvalues)-d(2)));
            comm = msprintf(comm,"%s",Vvalues(i(1)))

        case "timescale"
            Tvalues = [ "5E-9" "10E-9" "20E-9" "50E-9" "0.1E-6" "0.2E-6" "0.5E-6"..
            "1E-6" "2E-6" "5E-6" "10E-6" "20E-6" "50E-6" "0.1E-3" "0.2E-3"..
            "0.5E-3" "1E-3" "2E-3" "5E-3" "10E-3" "20E-3" "50E-3" "100E-3"..
            "200E-3" "500E-3" "1" "2" "5" "10" "20" "50" "100" "200" ]
            d1 = abs(evstr(d1))

            // Autorized bounds depend on the display & acquisition modes
            //  and on the model of the oscilloscope. Checking them:
            isAnalog = ~osciOX80("digital?")
            if ~isAnalog then
                refreshMode = io_OX80(ph, "ACQ:MODE?")=='REFR'
                if refreshMode then
                    modeName = "DIGITAL Single Shot or Refresh"
                else
                    modeName = "DIGITAL Rolling"
                end
            else
                modeName = "ANALOG"
            end
            model = osciOX80('model?')
            LowMessage = _("%s: Query smaller than lower bound in %s mode (%-.1g) => timeScale set to %-.1g s/div")
            HighMessage = _("%s: Query bigger than upper bound in %s mode (%-.1g) => timeScale set to %-.1g s/div")
            if isAnalog then
                if model=="OX8100" then
                    if d1<5e-8 then
                        d1 = 5e-8
                        warning(msprintf(LowMessage,fn,modeName,5e-8,5e-8))
                    elseif d1>0.1
                        d1 = 0.1
                        warning(msprintf(HighMessage,fn,modeName,0.1,0.1))
                    end
                elseif model=="OX8040" | model=="OX8050"
                    if d1<5e-8 then
                        d1 = 5e-8
                        warning(msprintf(LowMessage,fn,modeName,5e-8,5e-8))
                    elseif d1>0.2
                        d1 = 0.2
                        warning(msprintf(HighMessage,fn,modeName,0.2,0.2))
                    end
                else      // model=="OX8042,OX8062"
                    if d1<2e-7 then    // OX8042 is actually limited to 5e-7
                        d1 = 2e-7
                        warning(msprintf(LowMessage,fn,modeName,2e-7,2e-7))
                    elseif d1>0.2
                        d1 = 0.2
                        warning(msprintf(HighMessage,fn,modeName,0.2,0.2))
                    end
                end
            else    // Limits for DIGITAL MODEs
                if refreshMode then    // DIGITAL REFRESH or SINGLE SHOT modes
                    if model=="OX8040"
                        if d1<1e-8 then 
                            d1 = 1e-8
                            warning(msprintf(LowMessage,fn,modeName,1e-8,1e-8))
                        elseif d1>200
                            d1 = 200
                            warning(msprintf(HighMessage,fn,modeName,200,200))
                        end
                    else    // OX8050, OX8100, OX8042, OX8062
                        if d1<5e-9 then 
                            d1 = 5e-9
                            warning(msprintf(LowMessage,fn,modeName,5e-9,5e-9))
                        elseif d1>200
                            d1 = 200
                            warning(msprintf(HighMessage,fn,modeName,200,200))
                        end
                    end
                else    // DIGITAL ROLLING mode (all models)
                    if d1<0.1
                        d1 = 0.1
                        warning(msprintf(LowMessage,fn,modeName,0.1,0.1))
                    elseif d1>200
                        d1 = 200
                        warning(msprintf(HighMessage,fn,modeName,200,200))
                    end
                end
            end
            [tmp, i] = min(abs(evstr(Tvalues)-d1));
            d1 = Tvalues(i(1))

        case "coupling"
            comm = msprintf(comm,"%s",convstr(d(2),'u'))

        case "sample"
            if ~or(d1==["1000" "8000" "16000"])
                d1 = abs(evstr(d1))
                if d1<2828 then
                    d1 = "1000"
                elseif d1<11314
                    d1 = "8000"
                else
                    d1 = "16000"
                end
            end
            r = d1
        case "envelope"
            io_OX80(ph, "MODE NUM", tempo)
            io_OX80(ph, "ACQ:MODE REFR", tempo)

        case "digital"
            io_OX80(ph, "MODE NUM", tempo)

        case "rolling"
            io_OX80(ph, "MODE NUM", tempo)        // Only in digital mode
            io_OX80(ph, "INIT:CONT OFF", tempo)   // Acquisition must be stopped

        case "rolllength"
            if typeof(d1)=='string' then
                d1 = evstr(d1)
            end
            if d1<0 then
                d1 = 'OFF'    // Continuous rolling mode
            else
                nbPoints = osciOX80("sample?")
                if d1<=100 then
                    values = [0 25 50 75 100]
                    [v,i] = min(abs(values - d1)) // Actual % value
                    d1 = values(i(1))        // = the closest to an allowed one
                    d1 = string(round(d1*nbPoints/100))
                else    // 1000 & 2000 cases
                    model = osciOX80("model?")
                    if or(model==["OX8040" "OX8050"]) then
                        if nbPoints==1000 then
                            d1 = 1000
                        elseif nbPoints==8000
                            d1 = 2000
                        else    // nbPoints==16000
                            d1 = 4000
                        end
                        d1 = string(d1)
                    else    // OX8042, OX8062, OX8100
                        values = [0 1000 2000 4000 ]
                        [v,i] = min(abs(values - d1))
                        d1 = string(min(values(i(1)),nbPoints))
                    end
                end    // d1<=100
            end    // d1<0
        end

        // Calling the device for the main command (2 temptatives)
        // =======================================================
        if c~='restore', comm = msprintf(comm,string(d1)), end

        if strindex(comm,"?")~=[]     // Query
            r = io_OX80(ph, comm, tempo)
            if r=='' then
                errC = OX80_readErr(ph)
                if or(errC==Lerr)
                    error(errorMessages(errC))
                end
            end
        else                        // Setting or action
            io_OX80(ph, comm, tempo)
        end

        // Post-processing the answer
        // ==========================
        r = strsplit(r,ascii(10))
        r = r($)
        select c
        case "lock?"
            r = r=='ON'

        case "dispsig?"
            if r=="ADD" then
                tmp = io_OX80(ph,"INPUT2:INV?",tempo)
                if tmp=='ON' then
                    r = 'DIFF'
                end
            end
        case "digital?"
            r = r=="NUM"
        case "vscale?"
            r = evstr(r)
        case "timescale?"
            r = evstr(r)
        case "sample?"
            r = evstr(r)
        case "sample"
            r = evstr(r)
        case "rolllength"
            r = osciOX80("rolllength?")
            if r=='OFF' then
                r = -1
            else
                r = evstr(r)
            end
        end
    //-----------------------------------------
    else
        tmp = _("%s: Command ""%s"" unknown.\n")
        error(msprintf(tmp, fn, c))
    end
endfunction

// --------------------------
function out = io_OX80(port,command,tempo)
    tmp = 1 + writeserial(port,command+ascii(10))
    sleep(tempo)
    if ~tmp
        tmp = _("%s: Command ""%s"": OX80 oscillo (id ""%s"") does not answer\n")
        error(msprintf(tmp, fname, command, port))
    end
    out = readserial(port)
    if command~="SYST:SET?" then        // Removing the ending \n
        out = ascii(out)
        out = ascii(out(1:$-1))
    end
    if out=='' then    // 2nd temptative
        out = readserial(port)
        if command~="SYST:SET?" then
            out = ascii(out)
            out = ascii(out(1:$-1))
        end
    end
    if command~="SYST:SET?" & strindex(command,"?")~=[]
        out = strsplit(out,ascii(10))
        out = out($)
    end
endfunction

// --------------------------
function ec = OX80_readErr(port)
      // Reads the next error code in the errors queue, & returns it (string)
      writeserial(port,"SYST:ERR?"+ascii(10))
      sleep(tempo)
      ec = readserial(port)    // error code (string)
      ec = part(ec,1:length(ec)-1)
endfunction

// --------------------------

function OX80_clearErr(port)
    // Cleans the errors queue
     ec = ''
     while ec~='0', ec = OX80_readErr(port), end
endfunction
