// TITLE     : Scilab driver for the 38XR Multimeter (Wavetek Meterman 38XR 
//              a.k.a. Amprobe 38XR-A)
// AUTHOR (C): Samuel GOUGEON - Le Mans Université / ENSIM (France)
// SOFTWARE  : for Scilab >= 5.2 : http://www.scilab.org/
// DEPENDENCY: http://atoms.scilab.org/toolboxes/serial must be loaded
// LICENSE   : CeCILL-C
//     http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
// DISCLAIMER : No warranty of any kind. Available as is. Be aware and use it 
//              to your own risks & responsability
// RELEASE   : 1.1  (2020-01-13)
// HISTORY   :
//   04/2012: First public release
//   01/2020: port to Scilab 6.0 & 6.1. 1 bug fixed.
// ----------------------------------------------------------------------------

function rep = getMesurande(s,arg)
    // arg   : [ctime, Tshort, Tlong]  Integration times
    // rep= [ Ylab ; evalS ; strtod(stime) ];
    //
    // Ylab  : Y-label for plot
    // evalS : expression for data conversion according to selected calibres 
    //         and mesuring options
    //
    evalS = ' ';
    Dt = arg(2);

    code = strcat(s(1:2));

    select code,
    case '10',	// Voltmeter ~
        if strtod(s(13)) <4,	// mode [V]
            Ylab = 'Voltage ~ [V]';
            if s(12)=='8',Ylab = 'Delta(Voltage) ~ [V]'; end
            evalS = 'data*1e-4*10^strtod(s(9))'    // TO BE CONFIRMED
        else                                     // mode [dBm]
            Ylab  = 'Voltage ~ [dBm]'
            evalS = 'data*0.01'
            // ABS/REL has no effect
        end

    case '0C', 	// Voltmeter  --
        Ylab = 'Voltage';
        if s(12)=='8', Ylab = 'Delta(Voltage)';  end
        if strtod(s(8))>0, Ylab= Ylab+' AC+'; Dt=arg(3) ; end  // mode AC+DC
        Ylab = Ylab+" DC [V]";
        evalS = 'data*1e-4*10^strtod(s(9))';	// [V]

    case '07',	// µA
        Ylab = 'Current [A]';
        if s(12)=='8', Ylab = 'Delta(Current) [A]'; end
        select strtod(s(10))
        case 0,
            Ylab = Ylab+' DC';
        case 1,
            Ylab = Ylab+' AC';
        case 2,
            Ylab = Ylab+' AC+DC';
            Dt = arg(3);
        end
        evalS = 'data*1e-8*10^strtod(s(9))';	// [A]

    case '0E',	// mA
        Ylab = 'Current';
        if s(12)=='8', Ylab = 'Delta(Current)'; end
        select strtod(s(10))
        case 0,
            Ylab = Ylab+' DC';
        case 1,
            Ylab = Ylab+' AC';
        case 2,
            Ylab = Ylab+' AC+DC';
            Dt = arg(3);
        end
        Ylab = Ylab + " [A]";
        evalS = 'data*1e-6*10^strtod(s(9))'; // [A]

    case '0A', 	// 10 A
        Ylab = 'Current'
        select strtod(s(10))
        case 0,
            Ylab = Ylab+' DC'
        case 1,
            Ylab = Ylab+' AC'
        case 2,
            Ylab = Ylab+' AC+DC'
            Dt = arg(3);
        end   
        Ylab = Ylab + " [A]"
        evalS = 'data/1000.'              // [A]

    case '06',	// Temperature [°C]
        Ylab = 'Temperature [°C]'
        evalS = 'data'
        // ABS/REL has no effect

    case '0F',	// Frequencemeter
        Dt = 0.8
        if s(12)=='B'
            Ylab  = 'Cyclic Rate [%]'      // Rapport cyclique 
            evalS = 'data*0.01'            // [%]
        else
            Ylab = 'Frequency [Hz]'
            // if s(12)=='8', Ylab='\Delta(Frequency) [Hz]'; end
            // s(12) passe aussi à 8 en absolu lors des changements de calibre...
            evalS = 'data*0.01*10^strtod(s(9))'    // [Hz]
        end

    case '08',	// Ohm-meter
        Dt = 0.5 
        Ylab  = 'Resistance [Ohm]'
        evalS = 'data*1.0*10^(4-strtod(s(9)))'    // [Ohm]

    case '0B',	// Capacity
        Dt = 0.8
        Ylab = 'Capacity [µF]';
        // if s(12)=='8', Ylab='\Delta(Capacity) [µF]'; end
        // s(12) passe aussi à 8 en absolu lors des changements de calibre...
        evalS = 'data*1e-5*10^strtod(s(9));'    // [µF]

    case '03',	// mA 4-20 --			// [mA]
        Ylab ='Current 4-20 [mA] --';
        evalS = 'data';
        // ABS/REL has no effect

    case '02',	// Temperature [°F]
        Ylab  = 'Temperature [°F]';
        evalS = 'data';
        // ABS/REL has no effect

    case '04',	// Test diode
        Ylab  = 'Test diode [V]';
        evalS = 'data/1000.';
        // ABS/REL has no effect

    end	// select
    if french then    // french is defined in M38XR() calling this.
        Ylab = strsubst(Ylab,"Current","Courant")
        Ylab = strsubst(Ylab,"Voltage","Tension")
        Ylab = strsubst(Ylab,"Capacity","Capacité")
        Ylab = strsubst(Ylab,"Frequency","Fréquence")
        Ylab = strsubst(Ylab,"Cyclic Rate","Rapport cyclique")
        Ylab = strsubst(Ylab,"Resistance","Résistance")
        Ylab = strsubst(Ylab,"Temperature","Température")
    end
    rep = [Ylab ; evalS ; string(Dt) ];

endfunction	// getMesurande

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

function repout = M38XR(command, arg, arg2)
// out = M38XR(command, arg, arg2)
//
// Scilab driver for the Meterman 38XR or Amprobe 38XR-A multimeter
//
// The device should be switched on. It must be connected to the computer 
//  through it serial optical output and cable. The serial output must be
//  activated (RS232 button). Beware about the auto power off.
//
//  Commands are case-unsensitive
//
// COMMANDS   ARG    EFFECT
//  M38XR            Displays this help (no argument)
// 'open'    port    Open M38XR session on COM port
//                      (9600 bits/s, no parity, 8 data bits, 1 stop bit)
//                     out = id of the opened RS-232 communication (scalar string)
//                    Example: M38XR open 1     or  id = M38XR('open',1)
//
// 'close'             Close M38XR serial connexion & clean session parameters
//                     out = 0 if no error has occured (real)
//                     Example: M38XR close     or err = M38XR('close')
//
// 'isOn'    1|-, dt  Checks output activity on the RS-232 connexion
//                      arg  = 1 : Verbose mode ; else: Quiet mode
//                      arg2 =  testing duration [s] (default=Long)
//                      out = Number of bytes delivered during dt
//
// 'monitor'  dt, Dt  * Open a graphic window
//                    * Do 1 measurement each dt [s] (integration time ~ periode)
//                    * Dynamically plot any forthcoming measurement
//                    * if Dt is provided (in [s]), data are ranged and plotted 
//                      over the last Dt seconds (slipping window). Otherwise,
//                      all data from the beginning are kept and plotted.
//                    out: No value is returned
//                    Examples: 
//                      M38XR monitor 1        or   M38XR('Monitor',1)
//                      M38XR monitor 1 60     or   M38XR('Monitor',1,60)
//          Remarks:
//          * The refresh rate of the device is about 2/s => dt must be >0.5
//          * If the data flow is suspended, the plot is suspended and a 
//            message is displayed. When the data flow is retrieved, the plot 
//            is resumed. The origin of time is unchanged.
//          * If "Data flow suspended" appears time to time in a transient way,
//             you should increase the integration time
//          * If the measurement mode is changed (e.g. V => A, etc) while
//             monitoring is running, the current plot is erased and 
//             automatically adapted.
//          * Default legends are displayed in english. They are in french
//             if the Scilab session is in french.
//
// 'getdata'       After monitoring, returns displayed data, in (N,2) columns
//                 of reals. out(:,1): time  out(:,2): values
//                 Example: out = M38XR('getdata')
//
// 'mesure'   dt   Returns 1 measurement done over dt [s] (integration time)
//                 dt = integration time
//                 out: 3x1 column of STRINGS
//                 out(1): Name of the output mesurande and related unit
//                  Possible values are: 
//                  'Voltage ~ [V]',    'Delta(Voltage) ~ [V]', 'Voltage ~ [dBm]',
//                  'Voltage DC [V]',   'Delta(Voltage) DC [V]', 
//                  'Current DC [A]',   'Delta(Current) DC [A]',
//                  'Current AC [A]',   'Delta(Current) AC [A]', 
//                  'Current AC+DC [A]','Delta(Current) AC+DC [A]',
//                  'Current 4-20 [mA] --', 'Capacity [µF]',
//                  'Resistance [Ohm]', 'Test diode [V]',  
//                  'Temperature [°C]', 'Temperature [°F]',
//                  'Frequency [Hz]', 'Cyclic Rate [%]'
//                  These labels are returned in french whether the Scilab
//                   session is in french.
//                 out(2): Measured value. 
//                         evstr(out(2)) gives it as a float number
//                 out(3): Value of integration time dt. 
//                         evstr(out(3)) gives it as a float number

// dataType : Data type = Y-label

fname = "M38XR"
stime = [0.4 0.4 1.5 0] // Integration times [s]: current  minimal  maximal  other
nl = ascii([10 13])

if argn(2)==0 then        // Display the help
    head_comments(fname)
    repout = %t
    return
end

// -----------
command = convstr(command,'l')
french = (part(getlanguage(),1:3)=="fr_")

if (command ~= 'open'),
    // Reading connexion parameters :
    try  
        h = TCL_GetVar("M38XR.Handle")
        nCOM     = TCL_GetVar("M38XR.numCOM")       // Serial port number
        dataType = TCL_GetVar("M38XR.Datatype")     // Data type readout
   catch
       disp(msprintf("%s: Error when recovering status data",fname))
    end
end

select command

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

case 'open'
    if ~exists('openserial')
        tmp = _("%s: openserial() from SERIAL toolbox is missing\n")
        error(msprintf(tmp,"M38XR"))
    else
        try
            h = openserial(arg,"9600,n,8,1")
            sleep(200)
            TCL_EvalStr("fconfigure "+h+" -buffersize 5000 -sysbuffer 5000")
            TCL_SetVar("M38XR.numCOM",string(arg))
            TCL_SetVar("M38XR.Handle",h)
            TCL_SetVar("M38XR.Datatype",'00')    // Data type readout
            repout = h
        catch
            warning(h)
            repout = 0
        end
    end
    // Programme lancé après la sortie RS-232 du multimètre = Risque de 
    //  ligne initiale incomplète --> Détermination (et enregistrement) de LL1
    //  Chaque ligne se termine par \cr\nl=\013\010

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

case 'close',
    if (~exists('closeserial'))
        tmp = _("%s: closeserial() from SERIAL toolbox by Enrico Segre is missing\n")
        if french then
        tmp = _("%s: closeserial() (module SERIAL de E. Segre) n''est pas disponible\n")
        end
        error(msprintf(tmp,"M38XR"))
    else
        try
            repout = 1+closeserial(h)
        catch
            repout = 0
        end
        try, TCL_UnsetVar("M38XR.numCOM")
        catch, disp("close: Port # undefined")
        end
        try, TCL_UnsetVar("M38XR.Handle")
        catch, disp("close: COM handle undefined")
        end
        try, TCL_UnsetVar("M38XR.Datatype")
        catch, disp("close: Datatype undefined")
        end
        try, TCL_UnsetVar("tmp"), end
    end

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

case 'ison',
    dt = stime(3)        // default dt
    if exists('arg2')
        dt = arg2
    end
    TCL_SetVar("ttyq",'')
    TCL_EvalStr("set ttyq [fconfigure "+h+" -queue]")    // reading the number of bytes in the input buffer
//    sleep(dt*1000)
    n = strtod(TCL_GetVar("ttyq"))
    sleep(dt*1000)
    TCL_EvalStr("set ttyq [fconfigure "+h+" -queue]")    // Has it changed ?
    repout = strtod(TCL_GetVar("ttyq"))-n
    if arg~=0            // verbose mode
        if ~repout
            if french then
                tmp = [ "ERREUR: Aucune donnée émise! SVP vérifier"
                        '  - Que le multimètre est allumé'
                        '  - L''activation de RS-232 sur celui-ci'
                        '  - La connexion RS-232 (port #'+nCOM+")" ]
            else
                tmp = [ "ERROR: No data output! Please check"
                        '  - RS-232 activation on the 38XR multimeter'
                        '  - RS-232 line (port #'+nCOM+")" ]
            end
            write(%io(2),tmp)
        else
            tmp = 'M38XR: Data output is running OK !'
            if french then
                tmp = 'M38XR : Le multimètre fonctionne correctement !'
            end
            disp(tmp)
        end
    end
    
    // =====================================================================

case 'mesure',
    dt = stime(1)
    if exists('arg','l')
        dt = arg
    end
    TCL_EvalStr("set tmp [read "+h+"]")                // Cleaning the buffer
    tic()
    tmp = '-1'
    // One waits for the end of the first line (possibly uncomplete):
    while tmp=='-1' & toc()<dt
        tmp = TCL_EvalStr("gets "+h+" tmp")   // returns -1 while EOL is not met
    end
    //write(%io(2),string(dt)+"/"+tmp+"/");
    if tmp=='-1'
        repout = ''
        return
    end

    // Reading the first line (lost, since possibly uncomplete)
    tmp = TCL_GetVar("tmp");

    tic()        // True start for integration
    Go  = %T
    nLL = 0      // Number of lines read
    Mesures = []
    while Go & toc()<dt
        tmp = '-1'
        while tmp=='-1'& toc()<3*dt, tmp = TCL_EvalStr("gets "+h+" tmp"); end
        if tmp=='-1'
            repout = ''
            return
        end
        tmp = TCL_GetVar("tmp")
        //		write(%io(2),"2: "+tmp+"/");
        s = strsplit(tmp,1:length(tmp)-1)
        code = strcat(s(1:2))     // Code mesurande
        if nLL 
            Go = (code==codeM)    // Mesurande changed on the flight?
        else 
            mesurande = getMesurande(s,[0 0.3 1.1])
            dt = max(dt, strtod(mesurande(3)))
        end
        if Go
            codeM = code
            nLL = nLL+1
            data = strtod(strcat(s(3:6)))
            if length(data)~=0,
                if modulo(strtod(s(13)),2)==1
                    data = -data
                end
                Mesures = [Mesures  evstr(mesurande(2))]
            end
        end	
    end
    //write(%io(2),nLL);
    if length(Mesures)==0
         repout = ''
    else repout = [ mesurande(1) ; string(mean(Mesures)) ; string(dt) ]
    end


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

case 'monitor'
    s = M38XR('isOn',1,arg)
    dataType = '00'
    cf = gcf()
    show_window(cf)

    dt = arg
    if isdef('arg2')        // Dt range 
        Dt = abs(arg2)
    else
        Dt = 0
    end
    jm = 0
    s = ''
    if french
        disp("Pour ARRETER : CTRL + C  puis saisir -->abort")
    else
        disp("To STOP: CTRL + C  then enter -->abort")
    end
    while 1
        s = M38XR('mesure',dt)  // Tunable Integration time
        if (s=='' & jm~=0 )     //  If the serial flux is suspended...
            ca = gca()
            tmp = ["Data readout" ; "suspended !"]
            if french, tmp = ["Flux des données"; "suspendu"] , end
            r = stringbox(tmp,0,0,0,6,5)
            H = r(2,2)-r(2,1)    // height
            L = r(1,3)-r(1,2)    // width
            b = ca.data_bounds
            x = (b(1,1)+b(2,1))/2 - L/2
            y = (b(1,2)+b(2,2))/2 - H/2
            xstring(x,y,tmp)
            ct = ca.children(1)
            ct.font_size = 5
            ct.font_foreground = color('red')
            ct.alignment = 'center'
            while s==''
                s = M38XR('mesure',dt)
            end
            delete(ct)
        end

        if s~=''
            dtype = stripblanks(s(1))
            data = strtod(s(2))
            dt = strtod(s(3))
            if dtype ~= dataType,    // unit has changed => New graph
                dataType = dtype
                j0 = getdate()
                jm = 0
                clf
                plot(0,data,"-b.","markSize",2)
                if french 
                    xtitle('Multimètre 38XR','Temps [s]',dataType)
                else
                    xtitle('Multimeter 38XR','Time [s]',dataType)
                end
                ca = gca()
                ca.title.font_size = 3
                ca.x_label.font_size = 3
                ca.y_label.font_size = 3
                ca.mark_size_unit = 'point'
            else        // unchanged unit => updating the current plot
                ax = gca()
                c = ax.children(1).children(1)
                jm = etime(getdate(), j0)    //# of elapsed seconds
                c.data = [c.data ; jm data ]
                if Dt~=0
                    k = find(c.data(:,1)>= c.data($,1)-Dt)
                    c.data = c.data(k,:)
                end
                d = c.data
                replot([min(d(:,1)) min(d(:,2)) max(d(:,1)) max(d(:,2)) ])
            end
        end
    end  // while
    // =====================================================================

case 'getdata'
    ax = gca()
    repout = ax.children(1).children(1).data

end	// select

endfunction	// M38XR
