/// Copyright 2010 MBARI.                                                    
/// MBARI Proprietary Information. All rights reserved.                      
/// This file contains most of the functions called by a user and matchup
/// with an entry in commands.c's command lists.
#include <stdio.h>  /// sprintf()
#include <string.h> /// strtsr()
#include <stdlib.h> /// atoi() atol()
#include <ctype.h>  /// isdigit()
#include <stdint.h>
#include "actions.h"
#include "commands.h"       /// command lists
#include "ui.h" /// parse user commands
#include "ioserial.h"         /// serial functions
#include "iodigital.h"        /// IO bits
#include "cwgconf.h"    
#include "timers.h"   /// timers and delays
#include "rtc.h"        /// RealTimeClock
#include "a2d.h"        /// analog ports
#include "log.h"
#include "script.h"
#include "iofile.h"
#include "licor8xx.h"
#include "config.h"


/// cat 
// cat <file> [<ofs>] [<mode H|T>]
// defaults ofs=0, mode=H
return_code_t actCatCmd(char* arg)
{   
    return_code_t retval = ERR_FAILURE;

    // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle

    char *acpy = (char *)ptstrdup(arg,true,true);
    
    char *tokens[3]={0};
    int ntoks=tokenize(acpy,tokens," ",3);
    
    char *path = tokens[0];
    uint32_t ofs = 0ul;
    byte mode = FP_MODE_HEAD;
    if(ntoks>1)
        sscanf(tokens[1],"%lu",&ofs);
    if(ntoks>2)
        mode=tokens[2][0];
  
    retval = fcPrint(path,mode,(size_t)ofs);
    pfree(acpy);
    return retval;
 
}

/// count lines
/// lc <file>
return_code_t actLineCountCmd(char* arg)
{
    uint32_t t=0;
    uint32_t lc = sys_count_lines(arg, &t);
    serPrintf(SER_CONS,"\r\n %lu (%.3f s)\r\n",lc,t/1000.0);

    return ERR_SUCCESS;
}

/// __SUB LIST COMMANDS__***********************************
/// COMMAND:<b>  GET [cmd] </b>@n
/// Send rest of command to "get" list.
return_code_t actGetCmd(char* arg) {
    return uiParse(arg, cmdsGet);
}


/// COMMAND:<b>  SET [cmd] </b>@n
/// Send rest of command to "set" list.
return_code_t actSetCmd(char* arg) {
    return uiParse(arg, cmdsSet);
}


/// COMMAND:<b>  CLEAR|CLR [cmd]    </b>@n
/// Send rest of command to "clr" list.
return_code_t actClrCmd(char* arg) {
    return uiParse(arg, cmdsClr);
}

/// COMMAND:<b>  SCR [cmd]    </b>@n
/// Send rest of command to "scr".
return_code_t actScriptCmd(char* arg) {
    return script_dispatch(arg, cmdsScr, -1);
}

/// COMMAND:<b>  PULSE [cmd]    </b>@n
/// Send rest of command to "pulse".
return_code_t actPulseCmd(char* arg) {
    return uiParse(arg, cmdsPulse);
}

/// COMMAND:<b>  LI [cmd]   </b>@n
/// Send rest of command to "li" list.
return_code_t actLiCmd(char* arg) {
    return uiParse(arg, cmdsLi);
}

/// __ROOT COMMANDS__****************************
/// print one list of commands
int cmdListDump(const CmdStruct* cmds) {
    int i = 0;
    while (cmds[i].name[0] != '\0') {
        serPrintf(SER_CONS,"%*c%s\r\n",CMD_LIST_INDENT_DFL,' ',cmds[i].name);
//        serPutString(SER_CONS, cmds[i].name);
//        serPutString(SER_CONS, "\r\n");
        i++;
    }
    return (i);
}

/// COMMAND:<b>  HELP|?     </b>@n
/// Print a list of all commands.
return_code_t actHelpCmd(char* arg) {
    
    bool dfl=true;

    if(NULL!=arg && (arg[0]=='v' || arg[0]=='V')){
        if(fcPrint(SYS_HELP_DFL,FP_MODE_HEAD,(size_t)0)==ERR_SUCCESS){
            dfl=false;
        }
    }// if verbose
    if(dfl){
    serPutString(SER_CONS, "Root Commands\r\n");
    cmdListDump(cmdsRoot);

    serPutString(SER_CONS, "Get Commands\r\n");
    cmdListDump(cmdsGet);

    serPutString(SER_CONS, "Set Commands\r\n");
    cmdListDump(cmdsSet);

    serPutString(SER_CONS, "Clear Commands\r\n");
    cmdListDump(cmdsClr);

//    serPutString(SER_CONS, "File Commands\r\n");
//    cmdListDump(cmdsFl);

    serPutString(SER_CONS, "Licor Commands\r\n");
    cmdListDump(cmdsLi);
    serPutString(SER_CONS, "Script Commands\r\n");
    cmdListDump((CmdStruct *)cmdsScr);
#ifdef WITH_TELEM
    serPutString(SER_CONS, " Orbcomm Commands\r\n");
    cmdListDump(cmdsOrb);
#endif
        }

    return ERR_SUCCESS;
}


/// COMMAND:<b> UPTIME  </b>@n
/// Display uptime in seconds read from main time counter,
/// rolls over after about 500 days.
#ifdef WITH_RTC
return_code_t actUptimeCmd(char* arg) {
    serPrintf(SER_CONS, " %.2lu:%.2lu:%.2lu\r\n", tkGetUptime()/3600,
            (tkGetUptime()%3600)/60, tkGetUptime()%60);
    return ERR_SUCCESS;
}
#else
return_code_t actUptimeCmd(char* arg) {
    return ERR_NO_IMPL;
}
#endif

/// COMMAND:<b> RESET   </b>@n
/// Reboot the system
return_code_t actResetCmd(char* arg) {
    asm("reset");
    return ERR_SUCCESS;
}

/// COMMAND:<b> CFGEX [file]</b>@n
/// Save the values of the config variables
return_code_t actCfgExportCmd(char* arg) {
//    return (cfgSave());
    if(NULL!=arg && strlen(arg)>12){
        // truncate file name if > 8.3
        // TODO: check exist, rename (e.g. _1)
        arg[12]='\0';
    }
    // save to file (arg (!NULL) or SYS_CONFIG_DFL)
    return config_export( ((strlen(arg)>0) ? arg : NULL) );
}

/// COMMMAND:<b> CON [port name]    </b>@n
/// send console Rx to portTx, send port Rx to console Tx.
/// Turns on trans and power bits for that port and when done
/// turns them off if they started that way
return_code_t actConCmd(char* arg) {
    BYTE in, out;
    int port, pow, tran;
    BYTE isOn = 0;

    if (strstr(arg, ARG_LICOR)) {
        port = SER_LICOR;
        pow = LICOR_DC;
        tran = XCVR_LICOR;
    } else if (strstr(arg, ARG_HOST)) {
        port = SER_HOST;
        tran = XCVR_HOST;
        pow = 99; //no power for host, this will fail nicely
    } else {
        return (ERR_FAILURE);
    }

    //remember if it was on already
    if (doGetBit(pow)) isOn |= 0x2;
    if (doGetBit(tran)) isOn |= 0x4;
    //turn them on
    doSetBit(pow, 1);
    doSetBit(tran, 1);

    while (out != 4) { //end with ^D
        if (serGetByte(port, &in)) {
            serPutChar(SER_CONS, in);
        }
        if (serGetByte(SER_CONS, &out)) {
            serPutChar(port, out);
        }
    }

    //turn them off if they were off
    if (0 == (isOn & 0x2)) doSetBit(pow, 0);
    if (0 == (isOn & 0x4)) doSetBit(tran, 0);

    return (ERR_SUCCESS);
}


/// log annotation
return_code_t actLogCmd(char* arg)
{
    return_code_t retval=ERR_FAILURE;
    
    if(log_syslog("%s",arg)>0){
        retval=ERR_SUCCESS;
    }
    
    return retval;
}

/// COMMAND: VER
/// show version info
return_code_t actVersionCmd(char* arg)
{
    return_code_t retval=ERR_SUCCESS;
    
    uint16_t serial_n=0;
    config_get(CFG_SERIAL_N,&serial_n);
    
    SYS_BANNER(serial_n);
    return retval;
}

/// COMMAND:<b>  HELLO  </b>@n
/// a test function for what ever I need it to do
return_code_t actHelloCmd(char* arg) {
    MPRINT(MOD_ACT,"hello[%s]\r\n",arg);
//    Time t1;
//    int lag;
//    lag = atoi(s);
//    if(lag ==0){
//        rtcDidAlarm();
//        return ERR_SUCCESS;
//    rtcRead(&t1);
//    t1.tm_sec += lag;
//    if(t1.tm_sec > 60){
//        t1.tm_sec -= 60;
//        t1.tm_min += 1;
//    rtcSetAlarm(&t1);
    serPutString(SER_HOST, "hello\r\n");
    serPrintf(SER_CONS, "hello\r\n");
    serTxWait(SER_HOST);
    return ERR_SUCCESS;
}

/// COMMAND:<b>  ECHO msg  </b>@n
/// a test function for what ever I need it to do
return_code_t actEchoCmd(char* arg) {
    MPRINT(MOD_ACT,"ECHO[%s]\r\n",arg);
    
//    serPutString(SER_HOST, arg);
//    serPutString(SER_HOST, "\r\n");
//    serPrintf(SER_CONS, "%s\r\n",arg);
//    serTxWait(SER_HOST);
//    return ERR_SUCCESS;
    
    char *cur=arg;
    char *cp[2]={0};
    char var_name[CFG_MEM_STRING_BYTES]={0};
    char var_val[CFG_LINE_BYTES]={0};
    char *pv=var_val;
//    uint32_t counter=0;
//    bool flag=false;
    while(*cur!='\0'){
        if(*cur=='$'){
            ZERO_MEM(var_name,CFG_MEM_STRING_BYTES);
            ZERO_MEM(var_val,CFG_LINE_BYTES);
            cp[0]=cur+1;
            cp[1]=var_name;
            while(!isspace(*cp[0]) && *cp[0]!='\0' && (cp[0]-cur)<CFG_MEM_STRING_BYTES){
                *cp[1]=*cp[0];
                cp[0]++;
                cp[1]++;
                *cp[1]='\0';
            }
            if(config_contains(var_name) && config_str_val(var_name,&pv)==ERR_SUCCESS){             
                serPutString(SER_HOST,var_val);                    
                serPutString(SER_CONS,var_val);                
                cur=cp[0]-1;                
            }else{
                // not a variable
                // just continue from cur
                serPutChar(SER_HOST, *cur);
                serPutChar(SER_CONS, *cur);
            }
        }else{
            serPutChar(SER_HOST, *cur);
            serPutChar(SER_CONS, *cur);
       }
        cur++;
     }
    serPutString(SER_HOST, "\r\n");
    serPutString(SER_CONS, "\r\n");
    return ERR_SUCCESS;
}


/// __GET COMMANDS__**********************************
/// COMMAND:<b>  GET TIME (u)   </b>@n
/// Display time from RTC, 'u' option prints time_t too.
return_code_t actGetTime(char* arg) {
    Time now={0};
    rtcRead(&now);
    if (*arg == 'u' || *arg == 'U') {
        serPrintf(SER_CONS, "%lu: ", (UINT32) mktime(&now));
    }
    memset(g_msg_buffer,0,SER_PRINTF_BUFFER_BYTES);
    rtcPrint(g_msg_buffer, &now);
    serPrintf(SER_CONS,"%s\r\n",g_msg_buffer);
//    serPutString(SER_CONS, g_msg_buffer);
//    serPutChar(SER_CONS, '\r');
//    serPutChar(SER_CONS, '\n');
 
    return ERR_SUCCESS;
}


/// COMMAND:<b>  GET NEXT   </b>@n
/// Display time until next task.
return_code_t actGetNextRun(char* arg) 
{
//    DPRINT("%s - not implemented\r\n",__FUNCTION__);
//    return ERR_NO_IMPL;
    DPRINT("\r\n  %.2f s\r\n\r\n",(script_next_event()/1000.0));
    return ERR_SUCCESS;
}

/// COMMMAND:<b>  GET BIT   </b>@n
/// Display state of IO-bits and Inst.Ports.
return_code_t actGetBitCmd(char* arg) {
    int i = 0;
        serPrintf(SER_CONS, " bit     SVn        (A/B)\r\n");
    for (i = 0; i < SV_COUNT; i++) {
        serPrintf(SER_CONS, " [%2d/%2d] SV%d        %d/%d\r\n",
                2*i,2*i+1, i , doGetBit(2*i),doGetBit(2*i+1));
    }
    serPrintf(SER_CONS, " [%5d] Pump0 DC   %d\r\n",PUMP0_DC, doGetBit(PUMP0_DC));
    serPrintf(SER_CONS, " [%5d] Pump1 DC   %d\r\n",PUMP1_DC, doGetBit(PUMP1_DC));
    serPrintf(SER_CONS, " [%5d] Licor DC   %d\r\n",LICOR_DC, doGetBit(LICOR_DC));
    serPrintf(SER_CONS, " [%5d] ADC0       %d\r\n",ADC0, doGetBit(ADC0));
    serPrintf(SER_CONS, " [%5d] ADC1       %d\r\n",ADC1, doGetBit(ADC1));
    serPrintf(SER_CONS, " [%5d] HOST  XCVR %d\r\n",XCVR_HOST, doGetBit(XCVR_HOST));
    serPrintf(SER_CONS, " [%5d] LICOR XCVR %d\r\n",XCVR_LICOR, doGetBit(XCVR_LICOR));
    serPrintf(SER_CONS, " [%5d] CONS  XCVR %d\r\n",XCVR_CONS, doGetBit(XCVR_CONS));
    return ERR_SUCCESS;
}


/// COMMAND:<b> GET ANALOG [port#]  </b>@n
/// Power on an analog port and read voltage
return_code_t actGetAnalogCmd(char* arg) {
    int port;
    int pbit=-1;   //power bit
    double volts;

    port = atoi(arg);
    
    switch(port){
        case 0:
            pbit=ADC0;
            break;
        case 1:
            pbit=ADC1;
            break;
        case 2:
            pbit=-1;
            break;
    }
    
//    if (1 == port) {
//        pbit = ADC0;
//    } else if (2 == port) {
//        pbit = ADC1;
//    }  else if (3 == port) {
//        pbit = -1;
//    } else {
//        return (ERR_FAILURE); //bad port num
//    }
    
    if(pbit>0)doSetBit(pbit, HI);

    tkDelayMs(500);

    volts = adVolts(port, 10);

    serPrintf(SER_CONS, "port:%d = %.4f volts\r\n", port, volts);
    
    if(pbit>0)doSetBit(pbit, LO);

    return (ERR_SUCCESS);
}

/// COMMAND:<b> GET VAR [V] name [name...]  </b>@n
/// Show current value of config variable, 
/// if name is not found print the whole list
return_code_t actGetVarCmd(char* arg) {

//     DPRINT("%s - GETVAR[%s] len[%d]\r\n",__FUNCTION__,arg,strlen(arg));
     // NOTE: arg string not copied before tokenization
    // since it's not used elsewhere
     char *tokens[16]={0};
    int ntoks=tokenize(arg,tokens," ",16);
    int i=0;
//     DPRINT("%s - GETVAR ntoks[%d]\r\n",__FUNCTION__,ntoks);

    bool verbose=false;
//    uint32_t counter=0;
//    bool flag=false;
    char val[CFG_MEM_STRING_BYTES]={0};
    char *pv=val;
    if(ntoks>0){
        for(;i<ntoks;i++){ 
            if(strcmp(tokens[i],"V")==0){
                verbose=true;
                if(ntoks==1){
                    config_show(NULL,verbose,5);
                    break;
                }
            }else if ( config_str_val(tokens[i],&pv)==ERR_SUCCESS) {
                if(verbose)
                serPrintf(SER_CONS, "%16s = %10s type[%s]\r\n", tokens[i], val,config_type_str(tokens[i]));
                else
                serPrintf(SER_CONS, "%16s = %10s\r\n", tokens[i], val);
            }
        }
    }else{
        config_show(NULL,verbose,5);
    }
//       config_show(NULL,true,5);
     return (ERR_SUCCESS);

}

#ifdef WITH_TELEM
/// COMMAND:<b>  GET DATA [serial port#] </b>@n
/// print a sample cycle's worth of data to HOST
return_code_t actGetDataCmd(char* arg) {
    tlmOutput(SER_HOST);
    tlmOutput(SER_CONS);
    return(ERR_SUCCESS);
}
#else
return_code_t actGetDataCmd(char* arg) 
{
    DPRINT("%s - not implemented\r\n",__FUNCTION__);
    return ERR_NO_IMPL;
}
#endif

/// COMMAND:<b>  GET SER [serial port#] </b>@n
/// Try to get text from a serial port,
/// ends when filled g_msg_buffer or nothing more to read
return_code_t actGetSerCmd(char* arg) {
    int port = atoi(arg);
    int i = 0;
    unsigned char b;

    // send out the serial prompt to the console 
    serPrintf(SER_CONS, "SER_%d[", port);

    // get anything in the serial buff 
    while (serGetByte(port, &b)) {
        if (i < SER_PRINTF_BUFFER_BYTES)
            g_msg_buffer[i++] = b;
    }

    // terminate the buffer 
    if (i < SER_PRINTF_BUFFER_BYTES)
        g_msg_buffer[i] = '\0';
    else
        g_msg_buffer[SER_PRINTF_BUFFER_BYTES - 1] = '\0';

    // write the string to the console 
    serPutString(SER_CONS, g_msg_buffer);
    serPutString(SER_CONS, "]\r\n");

    // clear everything out in case you had an overflow 
    serClrRxOverFlow(port);
    serRxFlush(port);

    return ERR_SUCCESS;
}


/// __SET COMMANDS__**************************************
/// COMMAND:<b>  SET TIME [new time]    </b>@n
/// Expects YY MM DD HH MM SS format fail if not all values are found
return_code_t actSetTime(char* arg) {
    Time now;
    char* b = arg;

    //turn non-digit to spaces
    for (; *b; b++)if (!isdigit(*b))*b = ' ';

    if (6 != sscanf(arg, " %d %d %d %d %d %d",
            &now.tm_year, &now.tm_mon, &now.tm_mday,
            &now.tm_hour, &now.tm_min, &now.tm_sec)) {
        serPutString(SER_CONS, "bad date/time, not setting clock\r\n");
        return (ERR_FAILURE);
    }
    //FIX THE MONTH
    now.tm_mon--;

    //FIX THE YEAR
    if (now.tm_year < 100) { //assume user entered 2 digit year
        now.tm_year += 100; //it's not the 1900s
    } else {
        now.tm_year -= 1900; //fix year offset
    }

    rtcSetTime(&now); //set the new time

    serPutString(SER_CONS, "new time is:");
    rtcRead(&now);
    memset(g_msg_buffer,0,SER_PRINTF_BUFFER_BYTES);
    rtcPrint(g_msg_buffer, &now);
    serPutString(SER_CONS, g_msg_buffer);
    serPutString(SER_CONS, "\r\n");

    return ERR_SUCCESS;
}

/// COMMAND:<b>  SET BITS [bits...]     </b>@n
/// Set one or more IO-bits HI.
/// bits may include:
/// bits: 0:18 valves, 19:20 pump, 21:22 analog 23:23 licor 24:26 RS232 drivers
/// #n : bit n (decimal)
/// xn : bit n (hex)
/// an : ADC n 0:1
/// a* : all ADC
/// vn : valve n A&B
/// vnA|B : valve n A,B
/// v* : all valves
/// pn : pump n 0:1
/// p* : all pumps
/// l  : licor DC power
/// uh : host xcvr
/// ul : licor xcvr
/// uc : console xcvr
return_code_t actSetBitsCmd(char* arg) {
    return_code_t retval=ERR_FAILURE;
    if(NULL!=arg){
        unsigned long int bits=0;
        if(dio_parse_bits(arg,&bits)==ERR_SUCCESS){
            doSetBits(bits,HI);
            retval=ERR_SUCCESS;
        }
    }
    return retval;
}

/// COMMAND:<b> SET VAR [var name] [val]    </b>@n
/// Set new value of config variable(will reset on reboot unless saved).
/// If name is not found print the whole list of variables
return_code_t actSetVarCmd(char* arg) 
{
    return_code_t retval=ERR_FAILURE;
    
    // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle
   char *acpy = (char *)ptstrdup(arg,true,true);
 
    char *tokens[2]={0};
    int ntoks=tokenize(acpy,tokens," ",2);
    
    if(NULL!=acpy && ntoks>=2){
        retval=config_parse(tokens[0],tokens[1]);
        pfree(acpy);
    }
    return retval;
}

/// COMMAND:<b>  SET SER [serial port #][string]    </b>@n
/// Send string and end line to serial port
return_code_t actSetSerCmd(char* arg) {
    int port;

    DESPACE(arg)
    switch(*arg){
        case '0':
            port = SER_HOST;
            break;
        case '1':
            port = SER_LICOR;
            break;
        case'2':
            port = SER_CONS;
            break;
        default:
            return(ERR_FAILURE);
    }
    arg++; //go past port#
    DESPACE(arg)


    serPutString(port, arg);
    serPutString(port,"\r\n");

    return ERR_SUCCESS;
}


/// COMMAND:<b>  SET BAUD [serial port #][new baud/100]     </b>@n
/// New speed must be 300*2^n for n=[0,7]
return_code_t actSetBaudCmd(char* arg) {
    int port;
    UINT16 newBaud;

    DESPACE(arg)
    switch(*arg){
        case '0':
            port = SER_HOST;
            break;
        case '1':
            port = SER_LICOR;
            break;
        case'2':
            port = SER_CONS;
            break;
        default:
            return(ERR_FAILURE);
    }
    arg++;
    DESPACE(arg)

    //only accept supported values
    newBaud = atoi(arg);
    switch (newBaud){
        case 300:
        case 600:
        case 1200:
        case 2400:
        case 4800:
        case 9600:
        case 19200:
        case 38400:
            break;
        default:
            return(ERR_FAILURE);
    }
    return (serSetBaud(port, newBaud));
}


/// __CLEAR COMMANDS__*****************************************

/// COMMAND:<b>  CLR BITS [bits...]     </b>@n
/// bits may include:
/// bits: 0:18 valves, 19:20 pump, 21:22 analog 23:23 licor 24:26 RS232 drivers
/// #n : bit n (decimal)
/// xn : bit n (hex)
/// an : ADC n 0:1
/// a* : all ADC
/// vn : valve n A&B
/// vnA|B : valve n A,B
/// v* : all valves
/// pn : pump n 0:1
/// p* : all pumps
/// l  : licor DC power
/// uh : host xcvr
/// ul : licor xcvr
/// uc : console xcvr

/// Clear one or more IO-bits (set LO).
return_code_t actClrBitsCmd(char* arg) {
    return_code_t retval=ERR_FAILURE;
    if(NULL!=arg){
        unsigned long int bits=0;
        if(dio_parse_bits(arg,&bits)==ERR_SUCCESS){
            doSetBits(bits,LO);
            retval=ERR_SUCCESS;
        }
    }
    return retval;
}

return_code_t actSweep(char* arg){
    int i=0;
    uint8_t delay=10;
    sscanf(arg,"%hhu",&delay);
    // turn everything on
    for(i=0;i<IO_BITS;i++){
        switch(i){
            case XCVR_HOST:
            case XCVR_LICOR:
            case XCVR_CONS:
                break;
            default:
        doSetBit(i,HI);
        tkDelayMs(delay);
        break;
        }
    }
   // turn everything on
    for(i=0;i<IO_BITS;i++){
       switch(i){
            case XCVR_HOST:
            case XCVR_LICOR:
            case XCVR_CONS:
                break;
            default:
        doSetBit(i,LO);
        tkDelayMs(delay);
        break;
        }
    }

   return(ERR_SUCCESS);
}

//return_code_t actPulseBitCmd(char* arg){
//    short unsigned int delay=0;
//    char state='?';
//    io_bit_t bit=IO_BITS;
//    char pulse[2]={0};
//    return_code_t retval=ERR_SUCCESS;
//    
//    if(sscanf(arg,"%hu %c %hu",(short unsigned *)&bit,&state,&delay)==3){
//        if(bit>=SV0A && bit<=ADC1){
//            if(delay>0){
//                switch(state){
//                    case 'h':
//                    case 'H':
//                    case '1':
//                        pulse[0]=1;
//                        pulse[1]=0;
//                        break;
//                    case 'l':
//                    case 'L':
//                    case '0':
//                        pulse[0]=0;
//                        pulse[1]=1;
//                        break;
//                    default:
//                        retval=ERR_FAILURE;
//                        break;
//                }
//            }
//        }
//    }
//    
//
//    if(retval==ERR_SUCCESS){
//        doSetBit(bit,pulse[0]);
//       tkDelayMs(delay);
//        doSetBit(bit,pulse[1]);
//    }
//    
//    return(ERR_SUCCESS);
//}
//
//return_code_t actPulseBitsCmd(char* arg){
//    return_code_t retval=ERR_SUCCESS;
//    short unsigned int delay=0;
//    char state='?';
//    long unsigned int bits=0x0;
//    char pulse[2]={0};
//    
//    if(sscanf(arg,"%lx %c %hu",&bits, &state, &delay)==3){
//        if(delay>0){
//            switch(state){
//                case 'h':
//                case 'H':
//                case '1':
//                    pulse[0]=1;
//                    pulse[1]=0;
//                    break;
//                case 'l':
//                case 'L':
//                case '0':
//                    pulse[0]=0;
//                    pulse[1]=1;
//                    break;
//                default:
//                    retval=ERR_FAILURE;
//                    break;
//            }
//        }
//    }
//    
//    if(retval==ERR_SUCCESS){
//        doSetBits(bits,pulse[0]);
//       tkDelayMs(delay);
//        doSetBits(bits,pulse[1]);
//    }
//    
//    return(ERR_SUCCESS);
//}


//return_code_t actPulseBitCmd(char* arg){
//    return ERR_NO_IMPL;
//}


// pulse bits state delay bits...
return_code_t actPulseBitsCmd(char* arg)
{
    return_code_t retval=ERR_FAILURE;
    
    unsigned long int bits=0;
    short unsigned int delay=0;
    char state='?';
    char pulse[2]={0};

    char *tokens[10]={0};
    int ntoks=tokenize(arg,tokens," ",10);
    if(ntoks>=3){
        if(sscanf(tokens[0],"%c",&state)==1 && sscanf(tokens[1],"%hu",&delay)==1 && delay>0){

            switch(state){
                case 'h':
                case 'H':
                case '1':
                    pulse[0]=1;
                    pulse[1]=0;
                    retval=ERR_SUCCESS;
                break;
                case 'l':
                case 'L':
                case '0':
                    pulse[0]=0;
                    pulse[1]=1;
                    retval=ERR_SUCCESS;
                    break;
                default:
                    break;
            }
           
            if(retval==ERR_SUCCESS){
                int i=0;
                for(i=2;i<ntoks;i++)
                    dio_parse_bits(tokens[i],&bits);
                 doSetBits(bits,pulse[0]);
                 tkDelayMs(delay);
                 doSetBits(bits,pulse[1]);
            }else{
                retval=ERR_FAILURE;
            }
        }
    }
    
    return(retval);
}


/// SCR LOAD [O] src [src...]
return_code_t actScrLoadCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;

    char *acpy = (char *)ptstrdup(arg,true,true);
    char *tokens[SCR_SCRIPT_COUNT]={0};
    int ntoks=tokenize(arg,tokens," ",SCR_SCRIPT_COUNT);
    bool ovr = ( ( ntoks>0 && strcmp(tokens[0],"O")==0 ) ? true : false);
    int i=(ovr ? 1 : 0);
    MPRINT(MOD_ACT,"SCR LOAD[%s] n[%d] ovr[%c]\r\n",arg,ntoks,(ovr?'Y':'N'));
    while(i<ntoks){
        if(script_load(tokens[i],ovr)<0){
            break;
        }
        i++;
    }
    if(i==ntoks){
        retval=ERR_SUCCESS;
    }
    pfree(acpy);
    return retval;
}
return_code_t actScrUnloadCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_NO_IMPL;
    if( !SCR_HRANGE_VALID(hnd) && sscanf(arg,"%d",&hnd)!=1)
        hnd = script_lookup(arg);
    MPRINT(MOD_ACT,"SCR UNLOAD[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    retval=script_unload(hnd);

    return retval;
}

return_code_t actScrDebugCmd(char* arg, script_hnd_t hnd)
{
     return_code_t retval=ERR_INVALID;
    char state=0;
    script_hnd_t h=hnd;

    if( (SCR_HRANGE_VALID(hnd) && sscanf(arg,"%c",&state)==1) ||
          ((sscanf(arg,"%c %d",&state, &h)==2) && SCR_HRANGE_VALID(h))  ){
        // if called from state machine
        if(state=='+'){
            MPRINT(MOD_ACT,"SCR DEBUG - set mode\r\n");
            script_set_mode(h,SM_DEBUG);
            retval=ERR_SUCCESS;
        }else if(state=='-'){
            MPRINT(MOD_ACT,"SCR DEBUG - clr mode\r\n");
            script_clr_mode(h,SM_DEBUG);
            retval=ERR_SUCCESS;
        }
    }
   MPRINT(MOD_ACT,"SCR DEBUG[%s] scr[%d/%s] st[%c] h[%d]\r\n",arg,hnd,script_name(hnd),state,h);
//    else if( ((sscanf(arg,"%c %d",&state, &h)==2) && SCR_HRANGE_VALID(h))){
//        // if called from cmd line, hnd is appended to arg
//       script_set_mode(hnd,SM_DEBUG);
//        retval=ERR_SUCCESS;
//    }

    return(retval);
}

return_code_t actScrDelayCmd(char* arg, script_hnd_t hnd)
{
    MPRINT(MOD_ACT,"SCR DELAY[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    
    uint32_t delay_ms;
    
    if(SCR_HRANGE_VALID(hnd) && sscanf(arg,"%lu",&delay_ms)==1){
        script_delay(hnd,delay_ms);
    }

    return(ERR_SUCCESS);
}

return_code_t actScrNOPCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;
    MPRINT(MOD_ACT,"SCR NOP[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    return retval;
}
return_code_t actScrEndCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;
    if( !SCR_HRANGE_VALID(hnd) && sscanf(arg,"%d",&hnd)!=1)
        hnd = script_lookup(arg);
     MPRINT(MOD_ACT,"SCR END[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
     script_set_event(hnd,SE_END);
     return retval;
}

return_code_t actScrRunCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;
    if(!SCR_HRANGE_VALID(hnd) && sscanf(arg,"%d",&hnd)!=1)
        hnd=script_lookup(arg);
    MPRINT(MOD_ACT,"SCR RUN[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    script_clr_mode(hnd,SM_MAN);
    script_run(hnd);
    return retval;
}

return_code_t actScrResetCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;
    if( !SCR_HRANGE_VALID(hnd) && sscanf(arg,"%d",&hnd)!=1)
        hnd = script_lookup(arg);
    MPRINT(MOD_ACT,"SCR RESET[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    script_reset(hnd);
    return retval;
}

return_code_t actScrJogCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;
    if( !SCR_HRANGE_VALID(hnd) && sscanf(arg,"%d",&hnd)!=1)
        hnd = script_lookup(arg);
     MPRINT(MOD_ACT,"SCR JOG[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
     script_set_mode(hnd,SM_MAN);
     script_run(hnd);

    return retval;
}
return_code_t actScrShowCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;
 
    if(strlen(arg)==0 || arg[0]=='*'){
        MPRINT(MOD_ACT,"SCR SHOW[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
        int i=0;
        for(i=0;i<SCR_SCRIPT_COUNT;i++){
           serPrintf(SER_CONS,"  SCR[%d]\r\n",i);
            script_show(i,true,5);
            serPrintf(SER_CONS,"\r\n");
        }
    }else{       
        if( !SCR_HRANGE_VALID(hnd) && sscanf(arg,"%d",&hnd)!=1){
            hnd = script_lookup(arg);
        }
        MPRINT(MOD_ACT,"SCR SHOW[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
        script_show(hnd,true,5);
    }
    
    return retval;
}

return_code_t actUploadCmd(char* arg){
    
    return_code_t retval=ERR_FAILURE;
   MPRINT(MOD_ACT,"UPLOAD[%s] arg[%s]\r\n",arg);
    if(arg!=NULL){
        int test=0;
        FIL ofile;
        byte brk=0;
        unsigned wb=1;
        bool quit=false;
        Timer tmout;

        if( (test=f_open(&ofile,arg,FA_WRITE|FA_CREATE_ALWAYS)) == FR_OK){
            // stop for EOF or ETX
            DPRINT("Waiting for file - ");
            DPRINT("Interrupt/end using CTRL-D\r\n");
            tkTmrSet(&tmout,ACT_UPLOAD_TO_MSEC);
            tkTmrStart(&tmout);
            while(!quit){
                tkTmrUpdate(&tmout);
                if(tmout.ticksLeft<=0){
                    DPRINT("\r\n%s - timed out\r\n",__FUNCTION__);
                     quit=true;
                 }else if( serGetByte(SER_CONS, &brk)==TRUE){
                    tkTmrSet(&tmout,ACT_UPLOAD_TO_MSEC);

                    switch(brk){
                        // break on CTRL-D or CTRL-W (EOT) CTRL-X (CAN))
                        case 0x04:
                        case 0x17:
                        case 0x18:
                            DPRINT("\r\n%s - interrupted [%02X]\r\n",__FUNCTION__,brk);
                            quit=true;
                            break;
                        default:
                            DPRINT("%c",brk);
                            wb=0;
                            if(brk>=0x20 || ISCRLF(brk)){
                            f_write(&ofile,&brk,1u, &wb);
                            }
                            if(wb!=1){
                                EPRINT("\r\n%s - write failed[%d]\r\n",__FUNCTION__,wb);
                            }
                            break;
                    }  
                 }
            }
            retval=ERR_SUCCESS;
            f_close(&ofile);
        }else{
            EPRINT("file open failed[%d]\r\n",test);
        }
    }
    return retval;
}

/////////////////////

return_code_t actScrMacroCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
    
    MPRINT(MOD_ACT,"SCR MACRO[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    
    // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle
   char *acpy = (char *)ptstrdup(arg,true,true);
 
    char *tokens[8]={0};
    int ntoks=tokenize(acpy,tokens," ",8);

    if( !( (SCR_HRANGE_VALID(hnd)) || 
            ((ntoks>2 && sscanf(tokens[ntoks-1],"%d",&hnd)==1)  && SCR_HRANGE_VALID(hnd) )) ){
        // hnd valid (called from script context)
        // OR
        // hnd must be parsed (called from CLI context)
        // else invalid
        hnd=SCR_HND_INVALID;
    }

    if(ntoks>=2 && NULL!=tokens[0]){
        char *io_bit_str=NULL;
        char *delay_str=tokens[1];    
        // if token name not NULL, should be macro name
        if(strcmp(tokens[0],MACRO_AIR_STR )==0){
            if(ntoks>=2 ){
                // set IO preset bits
                io_bit_str=MACRO_AIR_IO;
            }
        }else if(strcmp(tokens[0],MACRO_ZERO_STR)==0){
            if(ntoks>=2 ){
                // set IO preset bits
                io_bit_str=MACRO_ZERO_IO;
            }
       }else if(strcmp(tokens[0],MACRO_PURGE_STR)==0){
            if(ntoks>=2 ){
                // set IO preset bits
                io_bit_str=MACRO_PURGE_IO;
            }
       }else if(strcmp(tokens[0],MACRO_SPAN_STR)==0){
            if(ntoks>=2 ){
                // set IO preset bits
                io_bit_str=MACRO_SPAN_IO;
             }
        }else if(strcmp(tokens[0],MACRO_EQU_STR)==0){
            if(ntoks>=2 ){
                // set IO preset bits
                io_bit_str=MACRO_EQU_IO;
             }
        }else if(strcmp(tokens[0],MACRO_VENT_STR)==0){
           if(ntoks>=2 ){
                // set IO preset bits
                io_bit_str=MACRO_VENT_IO;
             }
        }else if(strcmp(tokens[0],MACRO_FILL_STR)==0){
           if(ntoks>=2 ){
                // set IO preset bits
                io_bit_str=MACRO_FILL_IO;
             }
        }else if(strcmp(tokens[0],MACRO_VENT_STR)==0){
            if(ntoks>=2 ){
                // set IO preset bits
                io_bit_str=MACRO_VENT_IO;
             }
        }else if(strcmp(tokens[0],MACRO_ZIO_STR)==0){
            if(ntoks>=2 ){
                doSetBits(0xFFFFF,LO);
                // set IO preset bits 
                // [to NULL, since we're handling here]
                io_bit_str=NULL;
             }
        }
        
        if(NULL!=io_bit_str){
            
            if(NULL!=io_bit_str){
                actSetBitsCmd(io_bit_str);
            }
            if(SCR_HRANGE_VALID(hnd)){
                // do delay if called from script context
                actScrDelayCmd(delay_str,hnd);
            }
       }
       retval= ERR_SUCCESS;
    }
   
    pfree(acpy);
    
    return retval;
}

/// take Licor measurement
/// args
/// type : char
/// dest : string, one or more of 'H','C','L'
///    n : unsigned >0 number of samples
/// delay_ms : delay between samples
return_code_t actLicorTMCmd(char* arg)
{
    return_code_t retval = ERR_NO_IMPL;
    MPRINT(MOD_ACT,"LI.TM[%s]\r\n",arg);
    // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle
  char *acpy = (char *)ptstrdup(arg,true,true);
 
    char *tokens[8]={0};
    int ntoks=tokenize(acpy,tokens," ",8);
  
    char type = RT_USER;
     licor_pub_flags_t flags=LPF_CONSOLE;
    unsigned n=1;
    unsigned delay=0;

    if(ntoks>0){
        type = tokens[0][0];
       
        if(ntoks>1){
             flags=licor_parse_pub_flags(&flags,tokens[1]);

             if( !(ntoks>2 && sscanf(tokens[2],"%u",&n)==1 && n>0) ){
                n=1;
            }// number of samples specified

            if( !(ntoks>3 && sscanf(tokens[3],"%u",&delay)==1 && delay>0) ){
                delay=0;
            }// delay specified
        }// output specified
    }// rec type specified

    int i=0;
    
    char *rstr=(char *)palloc(LI_RAW_RECORD_STR_BYTES);
    for(i=0;i<n;i++){
        licor_vrecord_t rec={0};
        if(licor_poll_parsed(&rec,type)==ERR_SUCCESS){

            if(licor_vrecord_str(rstr,&rec)>0){
                log_queue_push(QUEUE_DATA, (byte *)&rec,RT_VALUE,sizeof(licor_vrecord_t));
                licor_publish(rstr,flags);
            }
        }
        if(delay>0){
            tkDelayMs(delay);
        }
        ZERO_MEM(rstr,LI_RAW_RECORD_STR_BYTES);
    }
    pfree(rstr);
    pfree(acpy);
    retval=ERR_SUCCESS;
        
    return retval;
}
/// set/get Licor output rate
/// args
/// period : seconds (min 0.5)
return_code_t actLicorORCmd(char* arg)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"LI.OR[%s]\r\n",arg);
    float period_sec=0;
    if(sscanf(arg,"%f",&period_sec)==1){
        licor_set_outrate(period_sec);
        retval=ERR_SUCCESS;
    }else{
        float or = licor_get_outrate();
        serPrintf(SER_CONS," %.2f\r\n",or);
    }
    return retval;
}
/// set/get Licor NMEAS
/// args
/// handler        : measurement handler ID
/// sample_n       : number of samples to process
/// period         : output period (sec, >= 0.5)
/// optional args (any order)
/// O<dest>        : destination(s) (optional, H|L|C|A)
/// R<record_type> : record type
/// T<tmout_ms>    : timeout (msec, optional, default 1.1*sample_n*period)

return_code_t actLicorNMEASCmd(char* arg)
{
    return_code_t retval = ERR_INVALID;
 
        // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle
    char *acpy = (char *)ptstrdup(arg,true,true);
    char *tokens[6]={0};
    int ntoks=tokenize(acpy,tokens," ",6);
    MPRINT(MOD_ACT,"LI.NMEAS[%s] ntoks[%d]\r\n",arg,ntoks);

    li_handler_fn handler = NULL;
    double period_sec=0.5;
    uint16_t sample_n=1;
    char *rtstr=NULL;
    licor_record_type_t rtype = RT_ANY;
     licor_pub_flags_t flags=LPF_CONSOLE;
 
    // timeout = period_sec+10% 
    uint32_t to_msec=0;
    
    bool err_flag=false;
    // parse arguments
    if(ntoks>=3){
        switch(toupper(tokens[0][0])){
            case HID_BLINE:
                handler = licor_bline_handler;
                break;
            case HID_AVG:
                handler = licor_avg_handler;
                break;
            default:
               err_flag=true;
                break;
        }

        // sample count
        if(sscanf(tokens[1],"%u",&sample_n)!=1){
           err_flag=true;
        }
 
        // period
        if(sscanf(tokens[2],"%lf",&period_sec)!=1){
            err_flag=true;
        }            
 
        if(ntoks>3){
            int i=3;
            for(i=3;i<ntoks;i++){
                if(tokens[i][0]=='T'){
                     // option: timeout
                    if(sscanf(tokens[5],"%lu",&to_msec)!=1){
                        err_flag=true;
                    }
                }
                if(tokens[i][0]=='O'){
                   // option: output destination
                   flags=0;
                    if(licor_parse_pub_flags(&flags,&tokens[i][1])!=0){
                        err_flag=true;
                    }
                }
                if(tokens[i][0]=='R'){
                  // option: record type variant
                  rtstr = &tokens[i][1];
                }
            }
        }
        
        // sanity check timeout, 
        // use default if unset
        if(to_msec<=0){
            to_msec=1100*period_sec*sample_n;
        }
        
        if(!err_flag){

          MPRINT(MOD_ACT,"%s - handler[%c] period[%.2lf] sample_n[%u] rtype[%s] to_msec[%lu] flags[%04x]\r\n",__FUNCTION__,toupper(tokens[0][0]),period_sec,sample_n,rtstr,to_msec,flags);
           if(handler==licor_bline_handler){
                bline_state_t bstate,*pb=&bstate;
                ZERO_MEM(&bstate, sizeof(bline_state_t) );

                retval=licor_stream_x(period_sec, sample_n, handler, pb, rtype, to_msec);
 
                char *rec=(char *)palloc(LI_BRECORD_STR_BYTES);
                if(NULL!=rec){
                    rtype = ( NULL==rtstr ? RT_BLINE : rtstr[0]);
                    pb->data.type = rtype;

                    log_queue_push(QUEUE_DATA, (byte *)&pb->data,RT_BLINE,sizeof(licor_brecord_t));
                    
                    licor_brecord_str(rec, (licor_brecord_t *)&pb->data);
//                    DPRINT("    BLINE record len[%d]:\r\n",strlen(rec));     
                    licor_publish(rec, flags);
                    pfree(rec);
                }

            }else if(handler==licor_avg_handler){
                avg_state_t astate, *pa=&astate;
                ZERO_MEM(pa, sizeof(avg_state_t) );
                
                 retval=licor_stream_x(period_sec, sample_n, handler, pa, rtype, to_msec);
                                                     
                 if(log_queue_push(QUEUE_DATA, (byte *)&pa->data,RT_SAMPLE,sizeof(licor_xrecord_t))!=ERR_SUCCESS){
                     DPRINT("log_push RT_SAMPLE failed\r\n");
                 }

                 char *rec=NULL;

                rec=(char *)palloc(LI_XRECORD_STR_BYTES);
                if(NULL!=rec){
                   rtype = ( NULL!=rtstr && strlen(rtstr)>=1 ?  rtstr[0] : RT_SAMPLE );
                   pa->data.type = rtype;
                    licor_xrecord_str(rec, &pa->data);
//                    DPRINT("    FEATURE record len[%d]:\r\n",strlen(rec));     
                    licor_publish(rec, flags);
                    pfree(rec);
                }
           }
        }       
    }
    pfree(acpy);
    
  
    return retval;
}

/// initialize Licor 
/// args : none
return_code_t actLicorInitCmd(char* arg)
{
    return_code_t retval = ERR_NO_IMPL;
    MPRINT(MOD_ACT,"LI.INIT[%s]\r\n",arg);
  // DPRINT("%s - not implemented\r\n",__FUNCTION__);
    retval=licor_initialize(NULL);
    return retval;
}

/// print last licor record [deprecated - use qpeek]]
/// to console and/or host
/// args : none
return_code_t actLicorLastCmd(char* arg)
{
    return_code_t retval = ERR_NO_IMPL;
    serPrintf(SER_CONS,"deprecated - use qpeek\r\n");
//    MPRINT(MOD_ACT,"LI.LAST[%s]\r\n",arg);
//    char rbuf[LI_RECORD_STR_BYTES]={0};
//    if( NULL!=licor_last_record(rbuf, LI_RECORD_STR_BYTES)){
//        //if(strstr(arg,"C")!=NULL)
//        serPrintf(SER_CONS,"%s\r\n",rbuf);
//        if(strstr(arg,"H")!=NULL)
//        serPrintf(SER_HOST,"%s\r\n",rbuf);
//        retval=ERR_SUCCESS;
//    }

    return retval;
}

/// li span <z|s|s2> [<span concentration>]
/// concentration required for s, s2 only
return_code_t actLicorCalCmd(char* arg)
{
    return_code_t retval = ERR_FAILURE;
    
    if( NULL!=arg ) {
        MPRINT(MOD_ACT,"LI.CAL[%s]\r\n",arg);
        
        char *acpy = (char *)ptstrdup(arg,true,true);
        char *tokens[2]={0};
        int ntoks=tokenize(acpy,tokens," ",2);

        double spanc=0.0;
        licor_cal_id type = LI_CAL_INVALID;
        
        if(ntoks>=2 && 
                (tokens[0][0]=='s' || tokens[0][0]=='S') && 
                sscanf(tokens[1],"%lf",&spanc)==1){
            
            // cal type span or span2
            if(tokens[0][1]=='2'){
                type=LI_CAL_S2;
            }else{
                type=LI_CAL_S;
            }
        }else if(ntoks>0  && 
                (tokens[0][0]=='z' || tokens[0][0]=='Z')) {
            // cal type zero
            type=LI_CAL_Z;
        }// else error
        
        retval=licor_cal(type,spanc);
    }
    return retval;
}

/// <et module debug flags
return_code_t actSetDebugCmd(char* arg)
{
    return_code_t retval = ERR_SUCCESS;
//    MPRINT(MOD_ACT,"DEBUG[%s]\r\n",arg);
    uint32_t mask=0;
    char state=0;
    char quiet=0;
    if(sscanf(arg,"%c %lx %c",&state,&mask,&quiet)>=2){
        if(state=='+'){
            SYS_SET_MDFLAGS(mask);
        }else if(state=='-'){
            SYS_CLR_MDFLAGS(mask);
        }
     }
    if(quiet!='Q')
     DPRINT("%s - mdebug flags[x%08lx]\r\n",__FUNCTION__,SYS_MDFLAGS);
   return retval;
}

/// schedule script
/// scr sched <period> <sync Y|N> <hnd>
return_code_t actScrScheduleCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"SCR SCH[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    
    // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle
 
    char *acpy = (char *)ptstrdup(arg,true,true);
 
    char *tokens[3]={0};
    int ntoks=tokenize(acpy,tokens," ",3);

    // if handle not valid, try to get from args
    if( !SCR_HRANGE_VALID(hnd) && ntoks>2){
        if( !( (sscanf(tokens[ntoks-1],"%d",&hnd)==1) && (SCR_HRANGE_VALID(hnd)) ) ){
            hnd = script_lookup(tokens[ntoks-1]);
        }
    }
    
    if( SCR_HRANGE_VALID(hnd) ){
        //hnd valid
        uint32_t period=0;
        byte sync='N';
        // period required
        if(ntoks>=1 && sscanf(tokens[0],"%lu",&period)==1){
            // sync optional (default:N)
            if(ntoks>=2 ){
                if(!(sscanf(tokens[1],"%c",&sync)==1 && (sync=='Y' || sync=='N'))){
                    MPRINT(MOD_ACT,"WARN - invalid sync [%c]\r\n",sync);                  
                }
            }// else invalid    
            
            retval = script_set_period(hnd,period,(sync=='Y'?true:false));
//            DPRINT("SCR SCH[%s] scr[%d/%s] period[%lu] sync[%c]\r\n",arg,hnd,script_name(hnd),period,sync);
            log_syslog("sch [%s]",arg);
         }else{// else invalid   
//    DPRINT("SCR SCH[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
         }
    }// else invalid hnd
     pfree(acpy);
  
    return retval;
}

/// restart script run timer
/// scr sync <delay> <hnd>
return_code_t actScrSyncCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"SCR SYNC[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    
    // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle
   char *acpy = (char *)ptstrdup(arg,true,true);
 
    char *tokens[2]={0};
    int ntoks=tokenize(acpy,tokens," ",2);

    // if handle not valid, try to get from args
    if( !SCR_HRANGE_VALID(hnd) && ntoks>1){
        if( !( (sscanf(tokens[ntoks-1],"%d",&hnd)==1) && (SCR_HRANGE_VALID(hnd)) ) ){
            hnd = script_lookup(tokens[ntoks-1]);
        }
    }
           
    uint32_t delay=0;

    if( SCR_HRANGE_VALID(hnd) ){
        //hnd valid
         if(ntoks>=1){
            
            sscanf(tokens[0],"%lu",&delay);
            
         }// else invalid   
 
        retval = script_sync(hnd,delay);
        log_syslog("sync [%s]",arg);
  }// else invalid hnd
      
    DPRINT("SCR SYNC[%s] scr[%d/%s] delay[%lu]\r\n",arg,hnd,script_name(hnd),delay);
   pfree(acpy);

    return retval;
}

/// goto script label
/// scr goto label <hnd>
return_code_t actScrGotoCmd(char* arg, script_hnd_t hnd)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"SCR GOTO[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    
    // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle
   char *acpy = (char *)ptstrdup(arg,true,true);
 
    char *tokens[2]={0};
    int ntoks=tokenize(acpy,tokens," ",2);

    // if handle not valid, try to get from args
    if( !SCR_HRANGE_VALID(hnd) && ntoks>1){
        if( !( (sscanf(tokens[ntoks-1],"%d",&hnd)==1) && (SCR_HRANGE_VALID(hnd)) ) ){
            hnd = script_lookup(tokens[ntoks-1]);
        }
    }

    if( SCR_HRANGE_VALID(hnd) ){
        //hnd valid
         if(ntoks>=1){
            // if arg is var, evaluate
            char *plabel=tokens[0];
            char *pvar=NULL;

             if(tokens[0][0]=='$'){
                 if(config_str_val((tokens[0]+1),&pvar)==ERR_SUCCESS){
                     plabel=pvar;
                 }else{
                     EMSG("invalid variable");
                     // set script error flag
                     script_set_event(hnd,SE_ERROR);
                 }
             }
            retval = script_goto(hnd,plabel);
         }// else invalid   
 
  }// else invalid hnd
      
   pfree(acpy);

    return retval;
}

/// conditional execution
/// scr if [ expr ] statement <hnd>
/// expr : ! var | var | var op val 
/// var  : $id 
/// id   : name | FX[0:31] | CX[0:SCR_COUNTERS]
/// val  : var | const
/// op   : == != > <  >= <= 

return_code_t actScrIfCmd(char* arg, script_hnd_t hnd)
{ 
    return_code_t retval=ERR_FAILURE;
    MPRINT(MOD_ACT,"SCR IF[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
//    DPRINT("SCR IF[%s] scr[%d/%s]\r\n",arg,hnd,script_name(hnd));
    
      
    // copy and tokenize the arg string
    // the first token should be the macro name
    // If called from the command line, the last token
    // must be a script handle
    char *acpy = (char *)ptstrdup(arg,true,true);
    
    if(NULL!=acpy){
        bool expr=false;
        char *action=NULL;
        if(script_parse_boolex(acpy,&expr)==ERR_SUCCESS && expr==true){
            if( (action=strstr(arg,"]"))!=NULL){
                action++;
                while(isspace(*action) && *action!='\0')
                    action++;
//                DPRINT("%s - dispatch(%s, %d)\r\n",__FUNCTION__,action,hnd);
                retval = script_parse_line(action,strlen(action),hnd);
            }else{
                EMSG("NULL action");
            }        
        }
//        else{
//            DPRINT("boolex false");
//        }
    }
    pfree(acpy);
    return retval;
}

/// show, manipulate counter (CX0:SCR_COUNTERS)
/// CTR CXn [OP [VAL]]
/// OP : =,--,++,+=.-=.*=,/=,%=
/// VAL: int32 (signed)
return_code_t actCtrCmd(char* arg)
{
    return_code_t retval=ERR_FAILURE;
    if(NULL!=arg){
       char *tokens[3]={0};
       int ntoks=tokenize(arg,tokens," ",3);
       int32_t chg=1;
       uint32_t val=0;
       
       if(ntoks>2){
           sscanf(tokens[2],"%ld",&chg);
       }
       
//       DPRINT("CTR[%s] ntoks[%d]\r\n",arg,ntoks);
       
//       if(script_get_counter(tokens[0],&val)==ERR_SUCCESS){
       if(config_get(tokens[0],&val)==ERR_SUCCESS){
           switch(ntoks){
              case 1:
                  // just show
                  serPrintf(SER_CONS, "%16s = %10lu\r\n", tokens[0], val);
                  retval=ERR_SUCCESS;
                   break;
               case 2:
               case 3:
                   retval=ERR_SUCCESS;
                   if(strcmp(tokens[1],"=")==0){
                       val=chg;
                   }else if(strcmp(tokens[1],"++")==0){
                       val++;
                   }else if(strcmp(tokens[1],"--")==0){
                       val--;
                   }else if(strcmp(tokens[1],"*=")==0){
                       val*=chg;
                   }else if(strcmp(tokens[1],"/=")==0){
                       if(chg!=0){
                        val/=chg;
                       }else{
                           EMSG("division by zero");
                           retval=ERR_FAILURE;
                       }
                   }else if(strcmp(tokens[1],"+=")==0){
                       val+=chg;
                   }else if(strcmp(tokens[1],"-=")==0){
                       val-=chg;
                   }else if(strcmp(tokens[1],"%=")==0){
                       val=val%chg;
                   }else{
                        retval=ERR_FAILURE;
                   }
                   break;
               default:
                   break;       
           }
           if(retval==ERR_SUCCESS && ntoks>1){
               char vstr[12]={0};
               sprintf(vstr,"%lu",val);
               retval=config_parse(tokens[0],vstr);
           }
       }
    
    }
 return retval;   
}

/// show, manipulate flag (FX0:31)
/// CTR FXn [OP [VAL]]
/// OP : =,!
/// VAL: 0|1 (signed)
return_code_t actFlagCmd(char* arg)
{
    return_code_t retval=ERR_FAILURE;
    if(NULL!=arg){
       char *tokens[3]={0};
       int ntoks=tokenize(arg,tokens," ",3);
       int chg=1;
       bool val=0;
       
       if(ntoks>2){
           sscanf(tokens[2],"%d",&chg);
       }
       
//       DPRINT("FLAG[%s] ntoks[%d]\r\n",arg,ntoks);
       
//       if(script_get_flag(tokens[0],&val)==ERR_SUCCESS){
       if(config_get(tokens[0],&val)==ERR_SUCCESS){
           switch(ntoks){
              case 1:
                  // just show
                  serPrintf(SER_CONS, "%16s = %c\r\n", tokens[0], (val ? '1':'0'));
                  retval=ERR_SUCCESS;
                   break;
               case 2:
               case 3:
                   retval=ERR_SUCCESS;
                   if(strcmp(tokens[1],"=")==0){
                       val=(chg==0?false:true);
                   }else if(strcmp(tokens[1],"!")==0){
                       val=(val?false:true);
                   }else{
                        retval=ERR_FAILURE;
                   }
                   break;
               default:
                   break;       
           }
           if(retval==ERR_SUCCESS && ntoks>1){
               char vstr[12]={0};
               sprintf(vstr,"%c",(val ? '1':'0'));
               retval=config_parse(tokens[0],vstr);
           }
       }
    
    }
 return retval;   
}


/// queue entry count
return_code_t actQCountCmd(char* arg)
{
    return_code_t retval= ERR_SUCCESS;
    queue_handle_t hnd=QUEUE_DATA;
    sscanf(arg,"%d",&hnd);
    int n = log_queue_count(QUEUE_DATA);
    serPrintf(SER_CONS,"%d\r\n",n);
    return retval;
}

return_code_t s_qextract(queue_entry_t *pqe, bool verbose)
{
    return_code_t retval= ERR_FAILURE;
    if(NULL!=pqe){ 
//        char *rec=(char *)palloc(LI_BRECORD_STR_BYTES);
       char *rec=(char *)palloc(LI_XRECORD_STR_BYTES);
        licor_xrecord_t *pa=NULL;
        licor_vrecord_t *pd=NULL;
        licor_brecord_t *pb=NULL;
        licor_pub_flags_t flags=LPF_CONSOLE;
        
        if(NULL!=rec){
            
            switch(pqe->id){
                case RT_VALUE:       
                case RT_USER:       
                    pd=(licor_vrecord_t *) pqe->data;
                    if(verbose)licor_vrecord_show(pd,true,5);
                    licor_vrecord_str(rec,pd);
                    licor_publish(rec, flags);
                    retval= ERR_SUCCESS;
                    break;
               case RT_SAMPLE: 
               case RT_REF: 
                    pa=(licor_xrecord_t *) pqe->data;
                    if(verbose)licor_xrecord_show(pa,true,5);
                    licor_xrecord_str(rec,pa);
                    licor_publish(rec, flags);
                    retval= ERR_SUCCESS;
                    break;
               case RT_BLINE:   
                    pb=(licor_brecord_t *) pqe->data;
                    if(verbose)licor_brecord_show(pb,true,5);
                    licor_brecord_str(rec,pb);
                    licor_publish(rec, flags);
                    retval= ERR_SUCCESS;
                    break;
                default:
                    break;   
            }
            pfree(rec);
        }
    }
     
    return retval;
}

/// read and erase queue entries
return_code_t actQPopCmd(char* arg)
{
    return_code_t retval= ERR_FAILURE;
    byte qe_data[LOG_QUEUE_DATA_BYTES]={0};
    queue_entry_t qe={0,0,qe_data}, *pqe=&qe;
    
    bool verbose = (strcmp(arg,"V")==0 ? true : false);

    if( log_queue_pop(QUEUE_DATA, pqe)==ERR_SUCCESS){        
        retval = s_qextract(pqe,verbose);
     }
    return retval;
}
/// read queue entries
return_code_t actQPeekCmd(char* arg)
{
    return_code_t retval= ERR_FAILURE;
   byte qe_data[LOG_QUEUE_DATA_BYTES]={0};
    queue_entry_t qe={0,0,qe_data}, *pqe=&qe;
     
    bool verbose = false;
    char *cp=arg;
    if((cp=strstr(arg,"V"))!=NULL){
        verbose=true;
    }else{
        cp=arg;
    }

    while(!isdigit(*cp) && *cp!='\0')cp++;
    // peek at next (default) or idx if specified
    int idx=-1;
    sscanf(cp,"%d",&idx);

    if( log_queue_peek(QUEUE_DATA, idx, pqe)==ERR_SUCCESS){        
        retval = s_qextract(pqe, verbose);
     }
   return retval;
}
