///
/// @file cli_impl.c
/// @authors k. Headley
/// @date 05 dec 2019

/// Command line interface implementation

/// @sa doxygen-examples.c for more examples of Doxygen markup


/////////////////////////
// Terms of use
/////////////////////////
/*
 Copyright Information

 Copyright 2002-2019 MBARI
 Monterey Bay Aquarium Research Institute, all rights reserved.

 Terms of Use

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version. You can access the GPLv3 license at
 http://www.gnu.org/licenses/gpl-3.0.html

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details
 (http://www.gnu.org/licenses/gpl-3.0.html)

 MBARI provides the documentation and software code "as is", with no warranty,
 express or implied, as to the software, title, non-infringement of third party
 rights, merchantability, or fitness for any particular purpose, the accuracy of
 the code, or the performance or results which you may obtain from its use. You
 assume the entire risk associated with use of the code, and you agree to be
 responsible for the entire cost of repair or servicing of the program with
 which you are using the code.

 In no event shall MBARI be liable for any damages, whether general, special,
 incidental or consequential damages, arising out of your use of the software,
 including, but not limited to, the loss or corruption of your data or damages
 of any kind resulting from use of the software, any prohibited use, or your
 inability to use the software. You agree to defend, indemnify and hold harmless
 MBARI and its officers, directors, and employees against any claim, loss,
 liability or expense, including attorneys' fees, resulting from loss of or
 damage to property or the injury to or death of any person arising out of the
 use of the software.

 The MBARI software is provided without obligation on the part of the
 Monterey Bay Aquarium Research Institute to assist in its use, correction,
 modification, or enhancement.

 MBARI assumes no responsibility or liability for any third party and/or
 commercial software required for the database or applications. Licensee agrees
 to obtain and maintain valid licenses for any additional third party software
 required.
 */

/////////////////////////
// Headers
/////////////////////////

/// sprintf()
#include <stdio.h>
/// strtsr()
#include <string.h>
/// atoi() atol()
#include <stdlib.h>
/// isdigit()
#include <ctype.h>
#include <stdint.h>

#include "cli_impl.h"
#include "cwgsys.h"
#include "cwg-shared.h"
#include "actions.h"
/// command lists
#include "commands.h"
/// parse user commands
#include "ui.h"
/// serial functions
#include "ioserial.h"
/// IO bits
#include "iodigital.h"
/// timers and delays
#include "timers.h"
/// RealTimeClock
#include "rtc.h"
/// analog ports
#include "a2d.h"
#include "log.h"
#include "script.h"
#include "fileutils.h"
#include "iowfile.h"
#include "licor8xx.h"
#include "licor8xx-shared.h"
#include "config.h"
//#include "logutils.h"
#include "lutil_api.h"
#include "micron.h"

/////////////////////////
// Macros
/////////////////////////

// These macros should only be defined for
// application main files rather than general C files
/*
 /// @def PRODUCT
 /// @brief header software product name
 #define PRODUCT "carbonwg"

 /// @def COPYRIGHT
 /// @brief header software copyright info
 #define COPYRIGHT "Copyright 2002-2019 MBARI Monterey Bay Aquarium Research Institute, all rights reserved."
 /// @def NOWARRANTY
 /// @brief header software terms of use
 #define NOWARRANTY  \
 "This program is distributed in the hope that it will be useful,\n"\
 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"\
 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"\
 "GNU General Public License for more details (http://www.gnu.org/licenses/gpl-3.0.html)\n"
 */

// mmeas configuration
#define MMEAS_USR_PROFILE_N 8
#define MMEAS_CYC_TOKS     4
#define MMEAS_USRN_TOKS   10
#define MMEAS_RUN_TOKS    10


/////////////////////////
// Declarations
/////////////////////////

/////////////////////////
// Imports
/////////////////////////

/////////////////////////
// Module Global Variables
/////////////////////////

// mmeas user-defined profiles
static meas_profile_t mmeas_usr_profile_table[MMEAS_USR_PROFILE_N];
// mmeas operation profile (persistent across calls)
static meas_profile_t op_cfg_s={0};

/////////////////////////
// Function Definitions
/////////////////////////


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

/// ROOT COMMANDS_***********************************

/// COMMAND:<b>  GET [cmd] </b>@n
/// Send rest of command to "get" list.
return_code_t cli_get(int argc, char **argv)
{
    return ui_parse(argc, argv, cmdsGet);
}

/// COMMAND:<b>  SET [cmd] </b>@n
/// Send rest of command to "set" list.
return_code_t cli_set(int argc, char **argv)
{
    char *s=pargcat(argc,argv," ");
    log_syslog("set [%s]",s);
    pfree(s);
    return ui_parse(argc, argv, cmdsSet);
}

/// COMMAND:<b>  CLEAR|CLR [cmd]    </b>@n
/// Send rest of command to "clr" list.
return_code_t cli_clr(int argc, char **argv)
{
    char *s=pargcat(argc,argv," ");
    log_syslog("clr [%s]",s);
    pfree(s);
    return ui_parse(argc, argv, cmdsClr);
}

/// COMMAND:<b>  PULSE [cmd]    </b>@n
/// Send rest of command to "pulse".
return_code_t cli_pulse(int argc, char **argv)
{
    char *s=pargcat(argc,argv," ");
    log_syslog("pulse [%s]",s);
    pfree(s);
    return ui_parse(argc, argv, cmdsPulse);
}

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

/// COMMAND:<b>  TEST [cmd]    </b>@n
/// Send rest of command to "test" list.
return_code_t cli_test(int argc, char **argv)
{
//    char *s=pargcat(argc,argv," ");
////    log_syslog("test [%s]",s);
//    pfree(s);
    return ui_parse(argc, argv, cmdsTest);
}

/// COMMAND:<b>  SLEEP    </b>@n
/// Sleep immediately
return_code_t cli_sleep(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    uint32_t next_event = sys_next_event();

    if(argc>0 && strcmp(argv[0],"V")==0){
        // verbose output
        OUT_CTTY("\r\n%s: t[%lu ms] TTO[%lu ms]\r\n",__func__, next_event,SYS_MIN_SLEEP_MSEC);
        serTxWait(SER_CONS);
        serTxWait(SER_HOST);
    }
    if ((SYS_MIN_SLEEP_MSEC < next_event)) {
        // sleep if there's time
        // and the UI is timed out
        // wait for serial TX to complete
        retval=tkDoIdle(next_event);
    }
    return retval;
}

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

/// show, manipulate counter (CX0:CFG_CX_COUNT)
/// CREG CXn|* [CXn<OP><VAL>] (32-bit unsigned))
/// OP : =,--,++,+=,-=,*=,/=
/// VAL: uint32 (unsigned)
return_code_t cli_creg(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;

    char *ops[]={"=","++","--","+=","-=","*=","/=","%=","|=","&=","&~","~"};
    int i=0;
    int err_count=0;
    typedef enum {DEC,HEX}ofmt_t;
    ofmt_t ofmt=DEC;
    int owid=1;
    int out_b=-1;
    int out_e=-1;

    for(i=0;i<argc;i++){
       int32_t chg=0;
       uint32_t val=0;
       char sreg[5]={0};
       int ireg=-1;
       char sop[3]={0};
       int xreg=-1;
       char *reg=NULL;
       char *op=NULL;
       int indent=3;

        out_b=-1;
        out_e=-1;
        owid = (owid<=0 ? 1 : owid);

        if(strcmp(argv[i],"OX")==0 || sscanf(argv[i],"OX%d",&owid)==1){
            ofmt=HEX;
        }else if(strcmp(argv[i],"OD")==0 || sscanf(argv[i],"OD%d",&owid)==1){
            ofmt=DEC;
        }else if(sscanf(argv[i],"W%d",&owid)==1){

        }else if(NULL!=strstr(argv[i],ops[3]) && (sscanf(argv[i],"CX%d+=%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d+=X%lX",&ireg,&chg)==2)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[3];
        }else if(NULL!=strstr(argv[i],ops[4]) && (sscanf(argv[i],"CX%d-=%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d-=X%lX",&ireg,&chg)==2)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[4];
        }else if(NULL!=strstr(argv[i],ops[5]) && (sscanf(argv[i],"CX%d*=%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d*=X%lX",&ireg,&chg)==2)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[5];
        }else if(NULL!=strstr(argv[i],ops[6]) && (sscanf(argv[i],"CX%d/=%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d/=X%lX",&ireg,&chg)==2)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[6];
        }else if(NULL!=strstr(argv[i],ops[7]) && (sscanf(argv[i],"CX%d%%=%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d%%=X%lX",&ireg,&chg)==2)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[7];
        }else if(NULL!=strstr(argv[i],ops[8]) && (sscanf(argv[i],"CX%d|=%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d|=X%lX",&ireg,&chg)==2)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[8];
        }else if(NULL!=strstr(argv[i],ops[9]) && (sscanf(argv[i],"CX%d&=%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d&=X%lX",&ireg,&chg)==2)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[9];
        }else if(NULL!=strstr(argv[i],ops[10]) && (sscanf(argv[i],"CX%d&~%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d&~X%lX",&ireg,&chg)==2)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[10];
        }else if(NULL!=strstr(argv[i],ops[11]) && (sscanf(argv[i],"CX%d~",&ireg)==1 || sscanf(argv[i],"CX%d&~",&ireg)==1)){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[11];
        }else if(NULL!=strstr(argv[i],ops[1]) && sscanf(argv[i],"CX%d++",&ireg)==1){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[1];
            chg=1.;
        }else if(NULL!=strstr(argv[i],ops[2]) && sscanf(argv[i],"CX%d--",&ireg)==1){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[2];
            chg=1.;
        }else if(sscanf(argv[i],"CX%d=CX%d",&ireg,&xreg)==2){
            sprintf(sreg,"CX%d",xreg);
            if(config_get(sreg,&chg)==ERR_SUCCESS){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[0];;
            }
        }else if(sscanf(argv[i],"CX%d%c%cCX%d",&ireg,&sop[0],&sop[1],&xreg)==4){
            sprintf(sreg,"CX%d",xreg);
            if(config_get(sreg,&chg)==ERR_SUCCESS){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            sop[2]='\0';
            op=sop;
            }
        }else if(sscanf(argv[i],"CX%d=%ld",&ireg,&chg)==2 || sscanf(argv[i],"CX%d=X%lX",&ireg,&chg)==2){
            sprintf(sreg,"CX%d",ireg);
            reg=sreg;
            op=ops[0];
        }else if(sscanf(argv[i],"CX%d",&ireg)==1 && ireg>=0 && ireg<CFG_CX_COUNT){
             out_b=ireg;
             out_e=ireg+1;
        }else if(strcmp(argv[i],"*")==0){
            out_b=0;
            out_e=CFG_CX_COUNT;
        }

        if(out_b>=0){
            // output specified values
            int j=0;
            for(j=out_b;j<out_e;j++){
                // just show
               if(ofmt==DEC)
               OUT_CTTY("%*s%d = %*lu\r\n",indent,"CX",j, owid, config_cx_val(j));
               else
               OUT_CTTY("%*s%d = %0*lX\r\n",indent,"CX",j, owid, config_cx_val(j));
            }
        }

        if(NULL!=reg && config_get(reg,&val)==ERR_SUCCESS){
            retval=ERR_SUCCESS;

            if(strcmp(op,"=")==0){
                val=chg;
            }else if(strcmp(op,"++")==0){
                val++;
            }else if(strcmp(op,"--")==0){
                val--;
            }else if(strcmp(op,"*=")==0){
                val*=chg;
            }else if(strcmp(op,"/=")==0){
                if(chg!=0){
                    val/=chg;
                }else{
                    EMSG("division by zero");
                    err_count++;
                    retval=ERR_FAILURE;
                }
            }else if(strcmp(op,"+=")==0){
                val+=chg;
            }else if(strcmp(op,"-=")==0){
                val-=chg;
            }else if(strcmp(op,"%=")==0){
                val=val%chg;
            }else if(strcmp(op,"|=")==0){
                val|=chg;
            }else if(strcmp(op,"&=")==0){
                val&=(chg);
            }else if(strcmp(op,"&~")==0){
                val&=~(chg);
            }else if(strcmp(op,"~")==0){
                val=~(val);
            }else{
                 err_count++;
                 retval=ERR_FAILURE;
            }

            if(retval==ERR_SUCCESS && NULL!=reg ){
                char vstr[16]={0};
                sprintf(vstr,"%lu",val);
                retval=config_parse(reg,vstr);
            }
        }// if reg...
    }// for args
    retval=(err_count==0 ? ERR_SUCCESS : ERR_FAILURE);
    return retval;
}

/// show, manipulate counter (DX0:CFG_DX_COUNT)
/// DREG DXn|* [DXn<OP><VAL>] (32-bit double))
/// OP : =,--,++,+=,-=,*=,/=
/// VAL: double (signed)
return_code_t cli_dreg(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;

    char *ops[]={"=","++","--","+=","-=","*=","/="};
    int i=0;
    int err_count=0;
       int indent=3;
       int owid=1;
       int oprec=6;
       typedef enum{OF=0,OE,OG}ofmt_t;
       ofmt_t ofmt=OF;
       int out_b=-1;
       int out_e=-1;
    for(i=0;i<argc;i++){
       double chg=0.0;
       double val=0.0;
       char sreg[5]={0};
       int ireg=-1;
       char sop[3]={0};
       int xreg=-1;
       char *reg=NULL;
       char *op=NULL;

        out_b=-1;
        out_e=-1;
//        owid = (owid<=0 ? 1 : owid);
        oprec = (oprec<=0 ? 6 : oprec);
        if(sscanf(argv[i],"P%d",&oprec)==1){

        }else if(sscanf(argv[i],"W%d",&owid)==1){

        }else if(strcmp(argv[i],"OF")==0 || sscanf(argv[i],"OF%d/%d",&owid,&oprec)>0){
            ofmt=OF;
        }else if(strcmp(argv[i],"OE")==0 || sscanf(argv[i],"OE%d/%d",&owid,&oprec)>0){
            ofmt=OE;
        }else if(strcmp(argv[i],"OG")==0 || sscanf(argv[i],"OG%d/%d",&owid,&oprec)>0){
            ofmt=OG;
        }else if(NULL!=strstr(argv[i],ops[3]) && sscanf(argv[i],"DX%d+=%le",&ireg,&chg)==2){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[3];
        }else if(NULL!=strstr(argv[i],ops[4]) && sscanf(argv[i],"DX%d-=%le",&ireg,&chg)==2){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[4];
        }else if(NULL!=strstr(argv[i],ops[5]) && sscanf(argv[i],"DX%d*=%le",&ireg,&chg)==2){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[5];
        }else if(NULL!=strstr(argv[i],ops[6]) && sscanf(argv[i],"DX%d/=%le",&ireg,&chg)==2){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[6];
        }else if(NULL!=strstr(argv[i],ops[1]) && sscanf(argv[i],"DX%d++",&ireg)==1){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[1];
            chg=1.;
        }else if(NULL!=strstr(argv[i],ops[2]) && sscanf(argv[i],"DX%d--",&ireg)==1){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[2];
            chg=1.;
        }else if(sscanf(argv[i],"DX%d=DX%d",&ireg,&xreg)==2){
            sprintf(sreg,"DX%d",xreg);
            if(config_get(sreg,&chg)==ERR_SUCCESS){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[0];;
            }
        }else if(sscanf(argv[i],"DX%d%c%cDX%d",&ireg,&sop[0],&sop[1],&xreg)==4){
            sprintf(sreg,"DX%d",xreg);
            if(config_get(sreg,&chg)==ERR_SUCCESS){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            sop[2]='\0';
            op=sop;
            }
        }else if(sscanf(argv[i],"DX%d=%le",&ireg,&chg)==2){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[0];
        }else if(sscanf(argv[i],"DX%d=%lf",&ireg,&chg)==2){
            sprintf(sreg,"DX%d",ireg);
            reg=sreg;
            op=ops[0];
        }else if(sscanf(argv[i],"DX%d",&ireg)==1 && ireg>=0 && ireg<CFG_DX_COUNT){
            out_b=ireg;
            out_e=ireg+1;
        }else if(strcmp(argv[i],"*")==0){
            out_b=0;
            out_e=CFG_DX_COUNT;
        }

        if(out_b>=0){
            // output specified values
            int j=0;
            for(j=out_b;j<out_e;j++){
                // just show
                const char *dx="DX";
                if(ofmt==OF){
                    OUT_CTTY("%*s%d = %*.*lf\r\n",indent,dx,j, owid, oprec, config_dx_val(j));
                }else if(ofmt==OE){
                     OUT_CTTY("%*s%d = %*.*le\r\n",indent,dx,j, owid, oprec, config_dx_val(j));
                }else if(ofmt==OG){
                     OUT_CTTY("%*s%d = %*.*lg\r\n",indent,dx,j, owid, oprec, config_dx_val(j));
                }
            }
       }
        if(NULL!=reg && config_get(reg,&val)==ERR_SUCCESS){
            retval=ERR_SUCCESS;

            if(strcmp(op,"=")==0){
                val=chg;
            }else if(strcmp(op,"++")==0){
                val++;
            }else if(strcmp(op,"--")==0){
                val--;
            }else if(strcmp(op,"*=")==0){
                val*=chg;
            }else if(strcmp(op,"/=")==0){
                if(chg!=0){
                    val/=chg;
                }else{
                    EMSG("division by zero");
                    err_count++;
                    retval=ERR_FAILURE;
                }
            }else if(strcmp(op,"+=")==0){
                val+=chg;
            }else if(strcmp(op,"-=")==0){
                val-=chg;
            }else{
                 err_count++;
                 retval=ERR_FAILURE;
            }

            if(retval==ERR_SUCCESS && NULL!=reg ){
                char vstr[16]={0};
                sprintf(vstr,"%le",val);
                retval=config_parse(reg,vstr);
            }
        }// if reg...
    }// for args
    retval=(err_count==0 ? ERR_SUCCESS : ERR_FAILURE);
    return retval;
}

/// show, manipulate flag (FX0:31)
/// FREG FXn|* [OP [VAL]]
/// OP : =,!
/// VAL: 0|1 (unsigned)
return_code_t cli_freg(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;

    char *ops[]={"=","!"};
    int i=0;
    int err_count=0;
    int out_b=-1;
    int out_e=-1;
    for(i=0;i<argc;i++){
       char chg='\0';
       bool val=false;
       char sreg[5]={0};
       int ireg=-1;
       char *reg=NULL;
       char *op=NULL;
       int indent=3;

       out_b=-1;
       out_e=-1;

        if(strstr(argv[i],ops[0])!=NULL && sscanf(argv[i],"FX%d=%c",&ireg,&chg)==2){
            sprintf(sreg,"FX%d",ireg);
            reg=sreg;
            op=ops[0];
        }else if(strstr(argv[i],ops[1])!=NULL && sscanf(argv[i],"FX%d!",&ireg)==1){
            sprintf(sreg,"FX%d",ireg);
            reg=sreg;
            op=ops[1];
        }else if(sscanf(argv[i],"FX%d",&ireg)==1 && ireg>=0 && ireg<CFG_FX_COUNT){
            out_b=ireg;
            out_e=ireg+1;
//            // just show
//            OUT_CTTY("%*s = %c\r\n", indent,argv[i], (config_fx_val(ireg) ? '1':'0'));
        }else if(strcmp(argv[i],"*")==0){
            out_b=0;
            out_e=CFG_FX_COUNT;
//            int j=0;
//            for(j=0;j<CFG_FX_COUNT;j++){
//                // just show
//                OUT_CTTY("%*cFX%d = %c\r\n", indent,' ',j, (config_fx_val(j)?'1':'0'));
//            }
        }

        if(out_b>=0){
            int j=0;
            for(j=out_b;j<out_e;j++){
                // just show
                OUT_CTTY("%*s%d = %c\r\n", indent,"FX",j, (config_fx_val(j)?'1':'0'));
            }
        }
        if(NULL!=reg && config_get(reg,&val)==ERR_SUCCESS){
            retval=ERR_SUCCESS;

            if(strcmp(op,"=")==0){
                if(chg=='0' || chg=='N' || chg=='F')
                    val=false;
                else
                    val=true;
            }else if(strcmp(op,"!")==0){
                val=!(val);
            }else{
                 err_count++;
                 retval=ERR_FAILURE;
            }

            if(retval==ERR_SUCCESS && NULL!=reg ){
                char vstr[16]={0};
                sprintf(vstr,"%c",(val?'1':'0'));
                retval=config_parse(reg,vstr);
            }
        }// if reg...
    }// for args
    retval=(err_count==0 ? ERR_SUCCESS : ERR_FAILURE);
    return retval;
}

/// get file size
return_code_t cli_fsize(int argc, char** argv)
{
    return_code_t retval= ERR_SUCCESS;
#if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
    FILE_T file_s, *fp=&file_s;
#else
    FILE_T *fp;
#endif
        DSHOW_TOKS(argc,argv);
//        OUT_CTTY("%s - argv[%d] [%s]\r\n",__func__,i,argv[i]);

    int i=0;
    char *del="\r\n";
    char *dp=NULL;
    for(i=0;i<argc;i++){

        if( (dp=strstr(argv[i],"DEL:"))!=NULL){
            dp+=4;
            del=ui_unescape(dp);
        }else if(iow_fopen(&fp,argv[i],IOW_READ)==0){
            OUT_CTTY("%"FMT_U32"%s",iow_fsize(fp),(i<(argc-1)?del:""));
        }else{
            OUT_CTTY("-1%s",(i<(argc-1)?del:""));
        }
    }
    OUT_CTTY("\r\n");
    return retval;
}

/// queue entry count
return_code_t cli_qcount(int argc, char **argv)
{
    return_code_t retval= ERR_SUCCESS;
    queue_handle_t hnd=QUEUE_MEAS;
    int i=0;
    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"I%d",&hnd)==1){
            continue;
        }else if(strcmp(argv[i],"M")==0){
            hnd=0;
            continue;
        }else if(strcmp(argv[i],"C")==0){
            hnd=1;
            continue;
        }
    }
    int n = log_queue_count(hnd);
    OUT_CTTY("%d\r\n",n);
    return retval;
}


/// read and erase queue entries
/// qpop Im [VQ] [*] [ 1<=n<=N ]
/// I : queue ID - queue index/handle 0:measurement queue 1: cycle queue
/// V : verbose - show additional details where applicable
/// Q : quiet   - delete queue entries (no output)
/// * : all     - all queue entries
/// n : entries - first n queue entries
/// N : elements in queue (qcount)
return_code_t cli_qpop(int argc, char **argv)
{
    return_code_t retval= ERR_FAILURE;

    queue_handle_t hnd=QUEUE_MEAS;
    uint32_t qc=log_queue_count(hnd);


    bool verbose = false;
    bool quiet = false;
    int32_t i=0;
    uint32_t n=0;
    size_t data_sz=0;
    queue_output_flag oflags=0;

    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"I%d",&hnd)==1){
           // queue id
            data_sz=log_queue_entry_size(hnd);
            qc=log_queue_count(hnd);
            n=(n>0?n:1);
            continue;
        }else if(strcmp(argv[i],"M")==0){
            hnd=0;
           // queue id
            data_sz=log_queue_entry_size(hnd);
            qc=log_queue_count(hnd);
            n=(n>0?n:1);
            continue;
        }else if(strcmp(argv[i],"C")==0){
            hnd=1;
           // queue id
            data_sz=log_queue_entry_size(hnd);
            qc=log_queue_count(hnd);
            n=(n>0?n:1);
            continue;
        }else if(argv[i][0]=='V'){
            verbose=true;
        }else if(argv[i][0]=='Q'){
            quiet=true;
        }else if(argv[i][0]=='*'){
            n=(int32_t)qc;
        }else if(strstr(argv[i],"OUT:")!=NULL){

            char *px = strstr(argv[i],":")+1;
//            DPRINT("px[%p/%s]\r\n",px,px);
            char *acpy = pstrdup(px);//pstrdup(argv[i]+4);
            if(NULL!=acpy){
                char *tok=NULL;

                // parse destination
                if((tok=strtok(acpy,","))!=NULL){
//                  DPRINT("dest[%s]\r\n",tok);
                  if(strstr(tok,"C")!=NULL){
                      oflags|=QOF_CONSOLE;
                  }
                  if(strstr(tok,"H")!=NULL){
                      oflags|=QOF_HOST;
                  }
                  if(strstr(tok,"L")!=NULL){
                      oflags|=QOF_LOG;
                  }
                }
                // parse format
                if(NULL!=tok && (tok=strtok(NULL,","))!=NULL){
//                  DPRINT("fmt[%s]\r\n",tok);
                  if(strstr(tok,"P")!=NULL){
                      oflags|=QOF_PRETTY;
                  }
                  if(strstr(tok,"R")!=NULL){
                      oflags|=QOF_RAW;
                  }
                  if(strstr(tok,"B")!=NULL){
                      oflags|=QOF_BASE85;
                  }
                  if(strstr(tok,"C")!=NULL){
                      oflags|=QOF_CSV;
                  }
                  if(strstr(tok,"M")!=NULL){
                      oflags|=(QOF_CSV|QOF_CSVX);
                  }
                  if(strstr(tok,"U")!=NULL){
                      oflags|=QOF_UUENCODE;
                  }
                  if(strstr(tok,"XX")!=NULL){
                      oflags|=QOF_PRETTY_HEX;
                  }else if(strstr(tok,"X")!=NULL){
                      oflags|=QOF_HEX;
                  }
                  if(strstr(tok,"H")!=NULL){
                      oflags|=QOF_HEADER;
                  }
                  if(strstr(tok,"D")!=NULL){
                      oflags|=QOF_DATA;
                  }
                  if(strstr(tok,"V")!=NULL){
                      oflags|=QOF_VERBOSE;
                  }
                }
                pfree(acpy);
            }
            continue;
        }else if( isdigit(argv[i][0]) ){
            sscanf(argv[i],"%lu",&n);
        }
    }
    // output to console by default
    if((oflags&QOF_DEV_FLAGS)==0)oflags|=QOF_CONSOLE;
    if((oflags&QOF_FMT_FLAGS)==0)oflags|=QOF_PRETTY;
    if((oflags&QOF_CONTENT_FLAGS)==0)oflags|=(QOF_HEADER|QOF_DATA);

    byte qe_data[data_sz];
    queue_entry_t qe={0,0,qe_data}, *pqe=&qe;
    ZERO_MEM(qe_data,data_sz);
//    DPRINT("%s - n[%lu] qc[%lu] hnd[%d] data_sz[%zu] quiet[%d] verbose[%d] oflags[%06lX]\r\n",__func__,n,qc,hnd,data_sz,(quiet?1:0),(verbose?1:0),oflags);
    if(n>0 && qc>0 && n<=qc && data_sz>0){
        // reuse qc as err count
        qc=0;
        uint32_t j=0;
        for(j=0;j<n;j++){
            if( log_queue_pop(hnd, pqe)==ERR_SUCCESS ){
                if(quiet==false){
                    qc+=((licor_queue_out((licor_zrecord_t *)pqe->data, oflags)==ERR_SUCCESS) ? 0 : 1);
                }
            }else{
                qc++;
            }
        }
        if(qc==0){
            retval=ERR_SUCCESS;
        }
    }
    DPRINT("errs[%lu/%u] retval[%d]\r\n",qc,qc,retval);
    return retval;
}

/// read queue entries
/// qpeek Im [V] [HCL] [ARBUX] [*] [ 0<=i<N ]  [ 1<=n<=N ]
/// I : queue ID - queue index/handle 0:measurement queue 1: cycle queue
/// V : verbose - show additional details where applicable
/// * : all     - all queue entries
/// i : index   - first entry
/// n : entries - number of entries
/// N : elements in queue (qcount)

return_code_t cli_qpeek(int argc, char **argv)
{
    return_code_t retval= ERR_FAILURE;

    queue_handle_t hnd=QUEUE_MEAS;
    uint32_t qc=log_queue_count(hnd);

    bool verbose = false;
    int32_t idx=0;
    int32_t i=0;
    uint32_t n=1;
     bool pflag=false;
     size_t data_sz=0;
     queue_output_flag oflags=0;

    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"I%d",&hnd)==1){
           // queue id
            continue;
        }else if(strcmp(argv[i],"M")==0){
            hnd=0;
            continue;
        }else if(strcmp(argv[i],"C")==0){
            hnd=1;
            continue;
        }else if(argv[i][0]=='V'){
            verbose=true;
            continue;
        }else if(argv[i][0]=='*'){
            idx=0;
            n=(int32_t)qc;
            continue;
        }else if(strstr(argv[i],"OUT:")!=NULL){

            char *px = strstr(argv[i],":")+1;
//            DPRINT("px[%p/%s]\r\n",px,px);
            char *acpy = pstrdup(px);//pstrdup(argv[i]+4);
            if(NULL!=acpy){
                char *tok=NULL;

                // parse destination
                if((tok=strtok(acpy,","))!=NULL){
//                  DPRINT("dest[%s]\r\n",tok);
                  if(strstr(tok,"C")!=NULL){
                      oflags|=QOF_CONSOLE;
                  }
                  if(strstr(tok,"H")!=NULL){
                      oflags|=QOF_HOST;
                  }
                  if(strstr(tok,"L")!=NULL){
                      oflags|=QOF_LOG;
                  }
                }
                // parse format
                if(NULL!=tok && (tok=strtok(NULL,","))!=NULL){
//                  DPRINT("fmt[%s]\r\n",tok);
                  if(strstr(tok,"P")!=NULL){
                      oflags|=QOF_PRETTY;
                  }
                  if(strstr(tok,"R")!=NULL){
                      oflags|=QOF_RAW;
                  }
                  if(strstr(tok,"B")!=NULL){
                      oflags|=QOF_BASE85;
                  }
                  if(strstr(tok,"C")!=NULL){
                      oflags|=QOF_CSV;
                  }
                  if(strstr(tok,"M")!=NULL){
                      oflags|=(QOF_CSV|QOF_CSVX);
                  }
                  if(strstr(tok,"U")!=NULL){
                      oflags|=QOF_UUENCODE;
                  }
                  if(strstr(tok,"XX")!=NULL){
                      oflags|=QOF_PRETTY_HEX;
                  }else if(strstr(tok,"X")!=NULL){
                      oflags|=QOF_HEX;
                  }

                  if(strstr(tok,"H")!=NULL){
                      oflags|=QOF_HEADER;
                  }
                  if(strstr(tok,"D")!=NULL){
                      oflags|=QOF_DATA;
                  }
                  if(strstr(tok,"V")!=NULL){
                      oflags|=QOF_VERBOSE;
                  }

                }
                pfree(acpy);
            }
            continue;
        }else if( isdigit(argv[i][0]) ){
            if(!pflag){
                if(sscanf(argv[i],"%ld",&idx)==1){
                    pflag=true;
                }
            }else{
                sscanf(argv[i],"%lu",&n);
            }
            continue;
        }
     }

    // output to console by default
    if((oflags&QOF_DEV_FLAGS)==0)oflags|=QOF_CONSOLE;
    if((oflags&QOF_FMT_FLAGS)==0)oflags|=QOF_PRETTY;
    if((oflags&QOF_CONTENT_FLAGS)==0)oflags|=(QOF_HEADER|QOF_DATA);
    data_sz=log_queue_entry_size(hnd);
    qc=log_queue_count(hnd);

    byte qe_data[data_sz];
    queue_entry_t qe={0,0,qe_data}, *pqe=&qe;
    ZERO_MEM(qe_data,data_sz);

    if(idx>=0 && idx<qc && n>0 && n<=(qc-idx) ){
        // reuse qc as error counter
        qc=0;
        for(i=idx;i<(idx+n);i++){
           if( log_queue_peek(hnd, i, pqe)==ERR_SUCCESS){
//               OUT_CTTY("peek oflags[%08"FMT_FLAG"]\r\n",oflags);
//               OUT_CTTY("pqe->data :\r\n");
//               hex_show((byte *)pqe->data,LI_ZREC_SIZEN(1),16,5,true);

               if(licor_queue_out((licor_zrecord_t *)pqe->data, oflags)!=ERR_SUCCESS){
                   qc++;
                }
            }else{
                // keep trace
                TRACE();
                // don't set an error
                // just quit [stop at last valid]
                break;//qc++;
            }
        }
    }
    retval= (qc==0?ERR_SUCCESS:retval);
     return retval;
}

/// read and erase queue entries
return_code_t cli_qclear(int argc, char **argv)
{
    return_code_t retval= ERR_FAILURE;

    int hnd=QUEUE_MEAS;

    int i=0;
    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"I%d",&hnd)==1){
            continue;
        }else if(strcmp(argv[i],"M")==0){
            hnd=0;
            continue;
        }else if(strcmp(argv[i],"C")==0){
            hnd=1;
            continue;
        }
    }

    retval=log_queue_clear(hnd);

    return retval;
}


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

/// log annotation
/// max message length set by LOG_SYSLOG_MSG_BYTES
return_code_t cli_log(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;

    log_handle_t hnd=LOG_SYSLOG;
    int i=0;
    char *msg=NULL;

    for(i=0;i<argc;i++){
        if(strcmp(argv[i],"S")==0){
            hnd=LOG_SYSLOG;
        }else if(strcmp(argv[i],"C")==0){
            hnd=LOG_CBIN;
        }else{
            msg=argv[i];
            break;
        }
        // for future expansion
    }

    if(NULL!=msg){
        if(log_printf(hnd,msg)>0){
            retval=ERR_SUCCESS;
        }
    }

//    if(argc>0 && log_syslog("%s",argv[0])>0){
//        retval=ERR_SUCCESS;
//    }

    return retval;
}
#ifdef WITH_LOG_UTILS

//    use    : lcat <file> [s<start>] [c<count>] [l<count>] [out:px[x*]bcrhdv]
//    brief  : show selected log records using specified formatting
//    input  : file - log file name (e.g. dat2003.000)
//    input  : s - start record
//                 start : start record index (zero-based)
//    input  : c - number of records
//                 count : number of records
//    input  : l - last <count> records
//    input  : out - format output (default: PDHV, start=0 c=-1 (all))
//                 P  : pretty
//                 XX : pretty hex
//                 X  : hex
//                 X* : pretty hex + hex
//                 B  : base85
//                 C  : CSV
//                 R  : raw
//                 H  : header
//                 D  : data
//                 V  : verbose (show record index/offset)
//     return : none

return_code_t cli_lcat(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
      retval=( lutil_lcat(argc,argv)==0 ? ERR_SUCCESS : ERR_FAILURE);

    return retval;
}

//    use    : linfo <file>
//    brief  : show log record summary
//    input  : file - log file name (e.g. dat2003.000)
//    return : summary

return_code_t cli_linfo(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;

    if(lutil_linspect(argc,argv)==0){
        retval=ERR_SUCCESS;
    }

    return retval;
}

//    use    : lless <file> [<out:[pbcrx[x*]hdv]>]
//    brief  : interactive log inspection
//    input  : file - log file name (e.g. dat2003.000)
//    input  : out  - output format control (may be changed interactively)
//    return : none
//    summary: There are three types of inputs:
//              Movement : navigating records
//              Command  : search, output settings
//              Session  : control session, help, etc.
//
//                Movement :
//                  First/Last [ g <    ] / [ G >  ]
//                  Next/Prev  [ dD<CR> ] / [ uU   ]
//                  This       [  <SPC> ]
//                  Jump +/-   [ j<n>   ] / [ k<n> ]
//
//                Session  :
//                  Help [h H ?]   Quit [q Q x X]
//
//                Command  :
//                  Enter cmd mode [ / ]
//                   Search -
//                    tm<msec> ti<iso1806>
//                    r<rtid> rm<mtid> mm=<mmid>
//                    i<record index>
//                    Next match [n]
//                    Prev match [N]
//                  Output -
//                   out:[pbcrx[x*]hdv]
//                    P  : pretty
//                    XX : pretty hex
//                    X  : hex
//                    X* : pretty hex + hex
//                    B  : base85
//                    C  : CSV
//                    R  : raw
//                    H  : header
//                    D  : data
//                    V  : verbose (show record index/offset)

return_code_t cli_lless(int argc, char **argv)
{
     return_code_t retval=ERR_FAILURE;
       retval=( lutil_lless(argc,argv)==0 ? ERR_SUCCESS : ERR_FAILURE);

    return retval;
}

//    use    : lseq [OZC]
//    brief  : show next sequence number  (from file)
//    input  : O - orecord log
//    input  : Z - zrecord log
//    input  : C - cycle log
//    return : sequence number
return_code_t cli_lseq(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    log_handle_t hnd=LOG_ZBIN;
    int i=0;

    for(i=0;i<argc;i++){
        if(strcmp(argv[i],"Z")==0){
            hnd=LOG_ZBIN;
        }
        if(strcmp(argv[i],"O")==0){
            hnd=LOG_OBIN;
        }
        if(strcmp(argv[i],"C")==0){
            hnd=LOG_CBIN;
        }
        // for future expansion
    }
    uint32_t seqno=0;
    if(log_get_seq_number(hnd,&seqno,false)==0){
        OUT_CTTY("%lu\r\n",seqno);
        retval=ERR_SUCCESS;
    }
    return retval;
}

//    use    : lsetseq [OZC] <value>
//    brief  : show next sequence number  (from file)
//    input  : O - orecord log
//    input  : Z - zrecord log
//    input  : C - cycle log
//    return : sequence number
return_code_t cli_lsetseq(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    log_handle_t hnd=LOG_HND_INVALID;
    int i=0;
    uint32_t seq_n=0,prev_seq=0;

    for(i=0;i<argc;i++){
        if(strcmp(argv[i],"Z")==0){
            hnd=LOG_ZBIN;
        }else if(strcmp(argv[i],"O")==0){
            hnd=LOG_OBIN;
        }else if(strcmp(argv[i],"C")==0){
            hnd=LOG_CBIN;
        }else if(sscanf(argv[i],"%"FMT_U32"",&seq_n)==1){
            // OK
        }
        // for future expansion
    }

    if(hnd!=LOG_HND_INVALID){
        log_get_seq_number(hnd,&prev_seq,false);
        if(log_set_seq_number(hnd,seq_n)==0){
            OUT_CTTY("%"FMT_U32" (%"FMT_U32")\r\n",seq_n,prev_seq);
            retval=ERR_SUCCESS;
        }
    }else{OUT_CTTY("Must specify log (O|Z|C) and value\r\n");}
    return retval;
}

#endif // WITH_LOG_UTILS

//    use    : lshow [<hnd>]
//    brief  : show summary of log data structures
//    input  : hnd - log handle 0:syslog 1:data 2:raw
//    return : none
return_code_t cli_lshow(int argc, char **argv)
{

    log_handle_t hnd[2]={0,LOG_HND_COUNT-1},x;
    if(argc>0 && sscanf(argv[0],"%d",&x) && LOG_HRANGE_VALID(x) )
        hnd[0]=hnd[1]=x;

    int i=hnd[0];
    for(i=hnd[0];i<=hnd[1];i++){
        log_show(i,true,5);
        OUT_CTTY("\r\n");
    }

    return ERR_SUCCESS;
}

// wrapper for dlist (ls)
// causes variable substitution to be performed
// before calling dlist.
return_code_t cli_ls(int argc, char **argv)
{
    return dlist(argc,argv);
}

//    use    : lseg <hnd> [<val>]
//    brief  : get/set log segment size
//    input  : hnd - log handle 0:syslog 1:data 2:raw
//    input  : val - log segment size, n>0
//             may also use K,M,G suffix (e.g. 10M) for KByte, MByte, GByte
//    return : none
return_code_t cli_lseg(int argc, char **argv)
{
    return_code_t retval=ERR_SUCCESS;
    log_handle_t hnd=LOG_HND_INVALID,x;

    int32_t val=-1;
    uint32_t oval=0;
    uint32_t mult=1;
    if(argc>0){
        if(sscanf(argv[0],"%d",&x) && LOG_HRANGE_VALID(x) )
            hnd=x;
        if(argc>1){
            sscanf(argv[1],"%ld",&val);
            char *cp=argv[1];
            while(isdigit(*cp) && *cp!='\0')cp++;
            switch(*cp){
                case 'K':
                    mult=KBYTE;
                    break;
                case 'M':
                    mult=MBYTE;
                    break;
                case 'G':
                    mult=GBYTE;
                    break;
                default:
                    mult=1;
                    break;
            }
        }
        if(val>=0){

            retval=log_set_segsize(hnd,val*mult);
        }

        retval=(retval==ERR_SUCCESS ? log_segsize(hnd,&oval) : retval);

        if(retval==ERR_SUCCESS)
            OUT_CTTY("%lu\r\n",oval);
    }else{
        retval=ERR_INVALID;
    }
    return retval;
}

return_code_t cli_upload(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    MPRINT(MOD_ACT,"UPLOAD[%d]\r\n",argc);
    if(argc>0 && argv[0]!=NULL){
        int test=0;
 #if defined(__PIC24FJ256GA106__) || defined(__PIC24FJ256GA606__) || defined(__PIC24FJ1024GA606__)
    FILE_T file_s, *fp=&file_s;
#else
    FILE_T *fp;
#endif
        byte bcur=0;
        byte bpre=0;
        size_t wb=1;
        bool quit=false;
        bool eol_auto=true;//(argc>1 && strcmp(argv[0],"NO-EOL")==0 ? false : true);
        bool bin_mode=false;
        uint32_t bin_len=0xFFFFFFFF;
        uint32_t bin_count=0;
        Timer tmout;
        uint32_t to_msec=ACT_UPLOAD_TO_MSEC;

        bool pair_pending=false;
        // output file name is last argument
        char *fpath = NULL;
        bool quiet=false;
        int i=0;

        for(i=0;i<argc;i++){
            if(strcmp(argv[i],"NO-EOL")==0){
                eol_auto=false;
            }else if(i<(argc-1) && sscanf(argv[i],"T%"FMT_U32"",&to_msec)==1){
                if(to_msec<1000UL)
                to_msec*=1000UL;
                // parsed timeout
            }else if(i<(argc-1) && strcmp(argv[i],"Q")==0){
                quiet=true;
            }if(i<(argc-1) && strstr(argv[i],"B:")!=NULL){
                sscanf(argv[i],"B:%lu",&bin_len);
                bin_mode=true;
            }else if(i==(argc-1)){
                // is path (must be last arg)
                fpath=argv[i];
            }
        }

        if( (test=iow_fopen(&fp,fpath,IOW_WRITE|IOW_CREATE_ALWAYS)) == 0){
            if(!quiet){
            OUT_CTTY("Waiting for file path[%s] tmout[%"FMT_U32"]\r\n",fpath,to_msec);
            OUT_CTTY("Interrupt/end using CTRL-D\r\n");
            }
            tkTmrSet(&tmout,to_msec);
            tkTmrStart(&tmout);
            if(!quiet)
            serPrintf(SER_CONS,"\r\n%s - bin_len %lu\r\n",__FUNCTION__,bin_len);
            while(!quit){
                bcur=0;
                tkTmrUpdate(&tmout);
                if(tmout.ticksLeft<=0){
                    if(!quiet){
                        OUT_CTTY("\r\n%s - timeout\r\n",__FUNCTION__);
                    }
                    if(bin_mode){
                        OUT_CTTY("\r\n%s - BIN_TO\r\n",__FUNCTION__);
                    }
                    quit=true;
                }else if( serIsAvail(g_ctty) && serGetByte(g_ctty, &bcur)){
                     // reset timeout timer
                    tkTmrSet(&tmout,to_msec);
                    int i=0;
                    wb=-1;
                    if(bin_mode){
//                        serPrintf(SER_CONS,(isprint(bcur)?"%c":"[%02X]"),bcur);
                        if(!quiet){
                            OUT_CTTY(isprint(bcur)?"%c":"<%02X> ",bcur);
                        }
                        wb = iow_fwrite(fp,&bcur,1);
                        bin_count++;

                        if(bin_count>=bin_len){
                            OUT_CTTY("\r\n%s - %lu/%lu BIN_EOT\r\n",__FUNCTION__,bin_count,bin_len);
                            quit=true;
                        }
                    }else{
                    switch(bcur){
                        case 0x04:
                        case 0x17:
                        case 0x18:
                        // break on CTRL-D or CTRL-W (EOT) CTRL-X (CAN))
                        DPRINT("\r\n%s - interrupted [%02X]",__FUNCTION__,bcur);
                        if(!quiet){
                            OUT_CTTY("\r\n");
                        }
                        quit=true;
                        break;
                       case '\r':
                       case '\n':
                           // CRLF byte
                           if(eol_auto){
                               // auto end-of-line:
                               // ensure CRLF line ends, preserve blank lines
                               // replace \r\r, \n\n with \r\n\r\n
                               // replace .\r., .\n. with .\r\n.
                               // add \r\n to end of file if it doesn't exist
                               if(pair_pending){
                                    if(bpre==bcur){
                                        i=2;
                                    }else{
                                        i=1;
                                    }
                                    while(i>0){
                                        if(!quiet){
                                            serPutChar(g_ctty,'\r');
                                            serPutChar(g_ctty,'\n');
                                        }
                                        wb = iow_fwrite(fp,"\r\n",2);
                                        i--;
                                    }
                                    pair_pending=false;
                                }else{
                                   // if none pending, set flag
                                   // and pend on next byte to write
                                    pair_pending=true;
                                }
                           }else{
                                // eof_auto disabled, just output as-is
                                if(!quiet){
                                    if(bcur>=0x20){
                                        serPutChar(g_ctty,bcur);
                                    }else{
                                        OUT_CTTY("<%02X>",bcur);
                                    }
                                }
                                wb = iow_fwrite(fp,&bcur,1);
                           }
                           // only update previous char if valid (non-break)
                           bpre=bcur;
                           break;
                        default:
                            // non-CRLF byte
                            if(eol_auto){
                                if(pair_pending){
                                    // write pending EOL to file
                                    if(!quiet){
                                        serPutChar(g_ctty,'\r');
                                        serPutChar(g_ctty,'\n');
                                    }
                                    wb = iow_fwrite(fp,"\r\n",2);
                                }
                            }
                            // output to console
                            if(!quiet){
                                OUT_CTTY(isprint(bcur)?"%c":"<%02X> ",bcur);
                            }
                            wb = iow_fwrite(fp,&bcur,1);
                            pair_pending=false;
                            // only update previous char if valid (non-break)
                            bpre=bcur;
                          break;
                    }// switch
                    }// else not bin_mode
                    if( wb < 0 ){
                         // write error
                        EPRINT("\r\n%s - write failed[%d] cur/pre[%02hx/%02hx]",__FUNCTION__,wb,(short unsigned)bcur,(short unsigned)bpre);
                    }
                }// serGetByte
            }// while !quit
            if(!bin_mode && NULL!=fp && eol_auto && ( pair_pending || !ISCRLF(bpre)) ){
                // write EOL to end of file if it doesn't exist
                DPRINT("EOF - writing line end pp[%c] bp[%02X]\r\n",(pair_pending?'Y':'N'),bpre);
                wb = iow_fwrite(fp,"\r\n",2);
            }
            retval=ERR_SUCCESS;
            iow_fclose(fp);
        }else{
            EPRINT("file open failed[%d]",test);
        }
        // record upload in syslog
        log_syslog("upload [%s][%c][%02X/%02X][%lu/%d]",fpath,(eol_auto?'Y':'N'),bcur,bpre,tmout.ticksLeft,retval);
    }
    return retval;
}

#ifdef WITH_XCAT
// use: xcat [h v<n>] [r:<rspec>] <file>
// brief: Show text file regions, specified by time, lines, offset
// input: h       -  help message
// input: v[<n>]  -  verbose output
// input: <file>  -  file name
// input: <rspec> -  range specifier
// comma separated list of range modes and values:
// <start_mode>,<val>,<end_mode>,<val> [1,2]
// <mode> : range type [blims][+-]
// b - byte mode
// l - line mode
// i - time mode (ISO8601)             [3]
// m - time mode (epoch msec)          [3]
// s - time mode (epoch sec)           [3]
// - - relative to end of file         [4]
// <val> : range value
// line/byte count (index >=0)
// timestamp (epoch sec/msec or ISO8601)
// Notes
// [1] range specifiers optional; use empty field (no space) if unspecified (,,)
// [2] valid end modes include [blims]; others ignored. Start/end time formats must match.
// [3] file lines must start with specified timestamp (leading space OK)
// [4] line/byte start modes only
// -  Defaults: line mode, start at beginning of file, show all
// -  Use CR or CTRL-D to interrupt
//
// examples:
// # show last 10 lines of sys2000.000
// xcat r:-,10 sys2000.000
// # show first 10 lines of sys2000.000
// xcat r:,,,10 sys2000.000
// # show first 10 lines of sys2000.000 >= 2020-03-28T00:00:00
// xcat r:i,2020-03-28T00:00:00,l,10 sys2000.000
// # show first 10 lines of foo.csv >= 1592974762 (Tue Jun 23 21:59:22 PDT 2020)
// xcat r:s,1592974762,l,10 foo.csv
// # show 100 bytes starting at line 10 sys2000.000
// xcat r:l,10,b,100 sys2000.000
return_code_t cli_xcat(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    if(argc>=1 && NULL!=argv){
        xcat_config_t cfg_s={
            0,
            0,
            NULL,
            NULL,
            -1,
            {0,0},
            {0,0}
        },*cfg=&cfg_s;

        char *file=NULL;
        int i=0;
        uint32_t bcat_start=0;
        uint32_t bcat_count=0;
        bool bin_mode=false;
        bool bin_raw=false;

        for(i=0;i<argc;i++){
            if(strcmp(argv[i],"h")==0 || strcmp(argv[i],"H")==0){
                xcat_help();
                return ERR_SUCCESS;
            }else if(sscanf(argv[i],"V%d",&cfg->verbose)==1 ||
                     sscanf(argv[i],"v%d",&cfg->verbose)==1){
            }else if(strcmp(argv[i],"V")==0 ||
                     strcmp(argv[i],"v")==0 ){
                cfg->verbose=1;
            }else if(strstr(argv[i],"R:")!=NULL){
                char *acpy=pstrdup(argv[i]);
                strtok(acpy,":");
                char *ostr=strtok(NULL,"");
                char *ocpy=pstrdup(ostr);
                char *cur=ocpy;
                char *opts[4]={NULL,NULL,NULL,NULL};
                int i=0;
                while( (isspace(*cur) ) && (*cur!='\0') )cur++;
                char *del;
                while( (del=strstr(cur,","))!=NULL && i<4){
                    opts[i++]=cur;
                    *del='\0';
                    cur=del+1;
                }
                if(i<=3)opts[i]=cur;

                char *mode=opts[0];
                char *range=opts[1];

                if(NULL!=mode){
                    if(strstr(mode,"b")!=NULL || strstr(mode,"B")!=NULL)cfg->bflags|=XCAT_MODE_BYTES;
                    if(strstr(mode,"l")!=NULL || strstr(mode,"L")!=NULL)cfg->bflags|=XCAT_MODE_LINES;
                    if(strstr(mode,"i")!=NULL || strstr(mode,"I")!=NULL)cfg->bflags|=(XCAT_TFMT_ISO|XCAT_MODE_TIME);
                    if(strstr(mode,"m")!=NULL || strstr(mode,"M")!=NULL)cfg->bflags|=(XCAT_TFMT_POSIX_MSEC|XCAT_MODE_TIME);
                    if(strstr(mode,"s")!=NULL || strstr(mode,"S")!=NULL)cfg->bflags|=(XCAT_TFMT_POSIX_SEC|XCAT_MODE_TIME);
                    if(strstr(mode,"+")!=NULL )cfg->bflags&=(~XCAT_REF_TAIL);
                    if(strstr(mode,"-")!=NULL )cfg->bflags|=XCAT_REF_TAIL;
                }
                if(NULL!=range){
                    cfg->start_tok=pstrdup(range);
                }

                mode=opts[2];
                range=opts[3];
                if(NULL!=mode){
                    if(strstr(mode,"b")!=NULL || strstr(mode,"B")!=NULL)cfg->eflags|=XCAT_MODE_BYTES;
                    if(strstr(mode,"l")!=NULL || strstr(mode,"L")!=NULL)cfg->eflags|=XCAT_MODE_LINES;
                    if(strstr(mode,"i")!=NULL || strstr(mode,"I")!=NULL)cfg->eflags|=(XCAT_TFMT_ISO|XCAT_MODE_TIME);
                    if(strstr(mode,"m")!=NULL || strstr(mode,"M")!=NULL)cfg->eflags|=(XCAT_TFMT_POSIX_MSEC|XCAT_MODE_TIME);
                    if(strstr(mode,"s")!=NULL || strstr(mode,"S")!=NULL)cfg->eflags|=(XCAT_TFMT_POSIX_SEC|XCAT_MODE_TIME);
                }
                if(NULL!=range){
                    cfg->stop_tok=pstrdup(range);
                }

                OUT_CTTY("opt[%s] bmod[%s] bval[%s] emod[%s] eval[%s]\r\n",ostr,opts[0],opts[1],opts[2],opts[3]);

                pfree(acpy);
                pfree(ocpy);
            }else if(strstr(argv[i],"B:")!=NULL){
                // RB: or B:,,R
                char *cp=argv[i]+2;
                if(sscanf(cp,"-,%lu,%lu",&bcat_start,&bcat_count)>0){
                    cfg->bflags|=XCAT_REF_TAIL;
                }else if(sscanf(cp,"%lu,%lu",&bcat_start,&bcat_count)>0){
                }
                bin_mode=true;
                if(strstr(argv[i],"R")!=NULL){
                    bin_raw=true;
                }
            }else if(sscanf(argv[i],"L%"FMT_U32"",&bcat_start)==1){
                // Ln shortcut for R:-,n
                cfg->bflags|=XCAT_REF_TAIL;
                cfg->start_tok=pstrdup(strstr(argv[i],"L"));
                cfg->range_n[0]=bcat_start;
            }else if(sscanf(argv[i],"S%"FMT_U32"C%"FMT_U32"",&bcat_start,&bcat_count)==2){
                // SnCn shortcut for R:l,n,l,m
                cfg->bflags|=XCAT_MODE_LINES;
                char *cstok[2]={ strstr(argv[i],"S"), strstr(argv[i],"C") };
                if(NULL!=cstok[0] && NULL!=cstok[1]){
                    cfg->start_tok=pstrdup(cstok[0]+1);
                    cfg->stop_tok=pstrdup(cstok[1]+1);
                    cfg->range_n[0]=bcat_start;
                    cfg->range_n[1]=bcat_count;
                }
            }
            file=argv[i];
        }// for


        if(bin_mode){
            FILE_T file_s, *fp=&file_s;
            if(iow_fopen(&fp,file,IOW_READ)==0){

                if( (cfg->bflags&XCAT_REF_TAIL)!=0 ){
                    bcat_start=iow_fsize(fp)-bcat_start;
                }

                iow_fseek(fp,(bcat_start>=0?bcat_start:0));
                uint32_t count=0;
                char hrbuf[16]={0};

                while(!iow_feof(fp)){
                    byte ibyte=0;
                    if(iow_fread(fp,&ibyte,1)==1){

                        if(!bin_raw && count>0 && (count%16)==0){
                            if(!bin_raw){
                                // output human-readable buffer
                                int j=0;
                                OUT_CTTY(" | ");
                                for(j=0;j<16;j++){
                                    OUT_CTTY("%c",hrbuf[j]);
                                }
                                // clear hrbuf
                                memset(hrbuf,0,16);
                            }
                            OUT_CTTY("\r\n");
                        }
                        if(bin_raw){
                            int retries=10;
                            while(serPutChar(g_ctty,ibyte)==FALSE){
                                tkDelayTicks(1);
                                if(retries <= 0 ){
                                     OUT_CONS("%s - IO error\r\n",__func__);
                                     break;
                                }
                                retries--;
                            }
                        }else{
                            // buffer human-readable version
                            // to print at end of row
                            hrbuf[count%16] = ( isprint(ibyte) ? ibyte : '.');
                            // output hex version
                            OUT_CTTY("%02x ",ibyte);
                        }
                        count++;
                    }
                    serTxWait(g_ctty);

                    if(bcat_count>0 && count>=bcat_count)
                        break;

                }

                if(!bin_raw){
                    // output partial row, if any
                    int j=0;
                    int k=0;
                    if( (j=(count%16))>0){
                        for(k=0;k<(16-j);k++)OUT_CTTY("   ");
                    }
                    OUT_CTTY(" | ");

                    for(k=0;k<(j!=0?j:16);k++){
                        OUT_CTTY("%c",hrbuf[k]);
                    }
                    OUT_CTTY("\r\n");
                }
                iow_fclose(fp);
                retval=ERR_SUCCESS;
            }
        }else{

            // use line bmode if none set
            if(XC_MODE(cfg->bflags)==0 ){
                cfg->bflags|=XCAT_MODE_LINES;
            }

            // use bmode if emode unset
            if(XC_MODE(cfg->eflags)==0){
                cfg->eflags|=(XC_MODE(cfg->bflags));
            }

            // start/end time formats must match
            if( XC_MODE(cfg->eflags)==XCAT_MODE_TIME ) {
                if( XC_MODE(cfg->bflags)==XCAT_MODE_TIME){
                    if(XC_TFMT(cfg->eflags)!=XC_TFMT(cfg->bflags)){
                        OUT_CTTY("WARN - end time format mismatch - using start time format\r\n");
                        cfg->eflags&=~(XCAT_TFMT_FLAGS);
                        cfg->eflags|=(XC_TFMT(cfg->bflags));
                    }
                }else{
                    // emode/bmode different (emode==time)
                    // set btime format to match
                    cfg->bflags&=~(XCAT_TFMT_FLAGS);
                    cfg->bflags|=(XC_TFMT(cfg->eflags));
                }
            }else{
                // emode/bmode different (bmode==time)
                // set etime format to match
                cfg->eflags&=~(XCAT_TFMT_FLAGS);
                cfg->eflags|=(XC_TFMT(cfg->bflags));
            }

            if(XC_MODE_TIME(cfg->bflags) ){
                if(XC_TFMT_ISO(cfg->bflags)){
                    cfg->range_t[0]=parse_iso8601(cfg->start_tok,NULL);
                }else{
                    if(NULL!=cfg->start_tok)
                        if(sscanf(cfg->start_tok,"%"FMT_U64"",&cfg->range_t[0])==1){
                        }
                }
            }else{
                if(NULL!=cfg->start_tok)
                    if(sscanf(cfg->start_tok,"%"FMT_U32"",&cfg->range_n[0])==1){
                    }
            }

            if(XC_MODE_TIME(cfg->eflags) || (XC_MODE(cfg->eflags)==0 && XC_MODE_TIME(cfg->bflags)) ){
                if(XC_TFMT_ISO(cfg->bflags)){
                    cfg->range_t[1]=parse_iso8601(cfg->stop_tok,NULL);
                }else{
                    if(NULL!=cfg->stop_tok)
                        if(sscanf(cfg->stop_tok,"%"FMT_U64"",&cfg->range_t[1])==1){
                            //                    TRACE();
                        }
                }
            }else{
                if(NULL!=cfg->stop_tok)
                    if(sscanf(cfg->stop_tok,"%"FMT_U32"",&cfg->range_n[1])==1){
                        //                TRACE();
                    }
            }// if argc

            if(cfg->verbose>0){
                OUT_CTTY("    file: %s\r\n",file);
                OUT_CTTY("  bflags: 0x%08X\r\n",cfg->bflags);
                OUT_CTTY("  eflags: 0x%08X\r\n",cfg->eflags);
                OUT_CTTY("   start: %s\r\n",cfg->start_tok);
                OUT_CTTY("    stop: %s\r\n",cfg->stop_tok);
                OUT_CTTY(" range_t: %"FMT_U64"/%"FMT_U64"\r\n",cfg->range_t[0],cfg->range_t[1]);
                OUT_CTTY(" range_n: %"FMT_U32"/%"FMT_U32"\r\n",cfg->range_n[0],cfg->range_n[1]);
            }
            if(xcat(file,cfg)==ERR_SUCCESS){
                retval=ERR_SUCCESS;
            }
        }
        if(NULL!=cfg->start_tok)pfree(cfg->start_tok);
        if(NULL!=cfg->stop_tok)pfree(cfg->stop_tok);
    }
    return retval;
}
#else
// cat <file> [<ofs>] [<mode H|T>]
// defaults ofs=0, mode=H
return_code_t cli_cat(int argc, char **argv)
{
    return_code_t retval = ERR_FAILURE;
    if(argc>=1){
        char *path = argv[0];
        uint32_t ofs = 0ul;
        byte mode = FP_MODE_HEAD;
        int i=1;
        for(i=0;i<argc;i++){
            if(sscanf(argv[i],"T%lu",&ofs)==1){
                mode=FP_MODE_TAIL;
            }else if(sscanf(argv[i],"H%lu",&ofs)==1){
                mode=FP_MODE_HEAD;
            }else{
                path=argv[i];
            }
        }
        //        OUT_CTTY("file[%s] ofs[%ul] mode[%d]\r\n",path,ofs,mode);
        retval = fcat(path,mode,(size_t)ofs);
    }
    return retval;
}
#endif // WITH_XCAT

/// page text files
/// less <file> [<lines>]
return_code_t cli_less(int argc, char **argv)
{
    return_code_t retval = ERR_FAILURE;
    if(argc>0){
        uint32_t page_lines=IOF_LESS_PAGE_LINES;
        if(argc>1){
            sscanf(argv[1],"%lu",&page_lines);
        }
        retval = fless(argv[0],page_lines);
    }
    return retval;
}

/// count lines
/// lc <file>
return_code_t cli_line_count(int argc, char **argv)
{
    uint32_t t=0;
    uint32_t lc = sys_count_lines(argv[0], &t);
    OUT_CTTY("\r\n %lu (%.3f s)\r\n",lc,t/1000.0);

    return ERR_SUCCESS;
}

/// COMMAND:<b>  HELP|?     </b>@n
/// Print a list of all commands.
return_code_t cli_help(int argc, char **argv)
{

    bool dfl=true;

    if(argc>0 && NULL!=argv[0] && (strstr(argv[0],"v") || strstr(argv[0],"V"))){
#ifdef WITH_IOF_LESS
        if(fless(SYS_HELP_DFL,IOF_LESS_PAGE_LINES)==ERR_SUCCESS){
            dfl=false;
        }
#else
        if(fcPrint(SYS_HELP_DFL,FP_MODE_HEAD,(size_t)0)==ERR_SUCCESS){
            dfl=false;
        }
#endif

    }// if verbose
    if(dfl){
        OUT_CTTY("Group [PRI]\r\n");
        s_cmd_list_show(cmdsRoot);

        OUT_CTTY("Group [GET]\r\n");
        s_cmd_list_show(cmdsGet);

        OUT_CTTY("Group [SET]\r\n");
        s_cmd_list_show(cmdsSet);

        OUT_CTTY("Group [CLR]\r\n");
        s_cmd_list_show(cmdsClr);

        //    OUT_CTTY("File Commands\r\n");
        //    s_cmd_list_show(cmdsFl);

        OUT_CTTY("Group [LI]\r\n");
        s_cmd_list_show(cmdsLi);
        OUT_CTTY("Group [SCR]\r\n");
        s_cmd_list_show((CmdStruct *)cmdsScr);
#ifdef WITH_TELEM
        OUT_CTTY(" Orbcomm Commands\r\n");
        s_cmd_list_show(cmdsOrb);
#endif
    }

    return ERR_SUCCESS;
}

#ifdef WITH_UI_HISTORY
return_code_t cli_history(int argc, char **argv)
{
    return_code_t retval=ERR_SUCCESS;
    bool verbose=false;
    int hist_id=-1;

    if(argc>0){
        int i=0;
        for(i=0;i<argc;i++){
            if(argv[i][0]=='V')verbose=true;
            else if(sscanf(argv[i],"%d",&hist_id)==1)continue;
        }
    }
    if(hist_id<0)
        ui_hist_show(verbose);
    if(hist_id>=0)
        retval=ui_hist_exec(hist_id);

    return retval;
}
#endif // WITH_UI_HISTORY

// wrapper for fcopy
// causes variable substitution to be performed
// before calling fcopy.
return_code_t cli_cp(int argc, char **argv)
{
    return fcopy(argc,argv);
}

/// 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 cli_con(int argc, char **argv)
{
    BYTE in, out;
    int port, pow, tran;
    BYTE isOn = 0;

    if (argv[0][0]=='L') {
        port = SER_LICOR;
        pow = LICOR_DC;
        tran = XCVR_LICOR;
    } else if(argv[0][0]=='H') {
        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);
}

#ifdef WITH_MICRON
//    use    : cron [options...]
//    brief  : manage cron schedules
//             Options:
//              +/-[t]  - enable/disable/suspend cron
//              L:USR|SYS|*
//                 list tab file contents
//              S:USR|SYS|*
//                 show tab status
//              C:USR|SYS,<flags>
//                 configure tab flags
//                   +/-E: enable/disable tab
//                   +/-X: enable/disable exclusive mode
//                  +/-Sn: suspend/resume task[n]
//              F:USR|SYS,<file>
//                 set tab file
//              T:USR|SYS,<i>,<sch>,<scr>
//                 set task
//              R:USR|SYS,<i>
//                 reset task
//
//    input  : argc - number of arguments
//    input  : argv - argument array
//    return : ERR_SUCCESS on success, ERR_FAILURE otherwise
return_code_t cli_cron(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    int i=0;
    DSHOW_TOKS(argc,argv);
    for(i=0;i<argc;i++){
        char *cp=argv[i];
        while(*cp!='\0'){
            if(*cp=='C' || *cp=='L' || *cp=='F' || *cp=='S' || *cp=='T' || *cp=='R' ){
                break;
            }

            if(*cp=='+'){
               micron_resume(CRON_HND_DFL);
            }else if(*cp=='-'){
                // suspend/disable time (indefinite (0) default)
                uint32_t suspend_msec=0;
                // read suspend time, if specified
                sscanf(cp+1,"%"FMT_U32"",&suspend_msec);
                micron_suspend(CRON_HND_DFL,suspend_msec);
            }else if(*cp=='X'){
                Time now={0};
                rtc_mktime(&now);
                uint32_t next=micron_next_event(CRON_HND_DFL,&now);
                uint32_t sus_sec=0UL;
                char stat_str[16]={0};
                if(micron_get_enable(CRON_HND_DFL)){
                    sprintf(stat_str,"+");
                }else if(micron_suspended(CRON_HND_DFL,&sus_sec)){
                    sprintf(stat_str,"s %.2f",(sus_sec/1000.0));
                }else{
                   sprintf(stat_str,"-");
                }
                OUT_CTTY("%.2f %s\r\n",(next/1000.0),stat_str);
            }else if(*cp=='H'){
                if(sys_sdc_ok()){
                    // help info is large; cat help file
                   char path[]=SYS_CRONMAN_DFL;
                   char *av[]={path};
                   cli_xcat(1,av);
                }
            }
            cp++;
        }

        if( strstr(cp,"L:")!=NULL){
            int id=-1;
            if(strstr((cp+2),"*")!=NULL){
                id=-1;
            }else if(strstr((cp+2),USR_CRONTAB_STR)!=NULL){
                id=TABID_USR;
            }else if(strstr((cp+2),SYS_CRONTAB_STR)!=NULL){
                id=TABID_SYS;
            }
            retval=micron_tab_list(CRON_HND_DFL, id);
        }else if( strstr(cp,"S:")!=NULL){
            int id=-1;
            if(strstr((cp+2),"*")!=NULL){
                id=-1;
            }else if(strstr((cp+2),USR_CRONTAB_STR)!=NULL){
                id=TABID_USR;
            }else if(strstr((cp+2),SYS_CRONTAB_STR)!=NULL){
                id=TABID_SYS;
            }
            if(id==-1){
                micron_show(micron_get_instance(CRON_HND_DFL),5);
            }else{
                micron_tab_stat(CRON_HND_DFL, id, 3);
            }
        }else if(strstr(cp,"C:")!=NULL){
            // USR|SYS,[X...]]
            char *toks[2]={NULL,NULL};
            char *cpy=pstrdup((cp+2));
            if(NULL!=cpy){
                int ntoks=tokenize(cpy,toks,",",2);
                if(ntoks==2){
                    cron_tab_t *tab=micron_tab_slookup(CRON_HND_DFL,toks[0]);
                    retval = ( micron_tab_parse_flags(tab,toks[1])==0 ? ERR_SUCCESS : ERR_FAILURE);
                }
            }
            pfree(cpy);
        }else if(strstr(cp,"F:")!=NULL){
            char *toks[2]={NULL,NULL};
            char *cpy=pstrdup((cp+2));
            if(NULL!=cpy){
                int ntoks=tokenize(cpy,toks,",",2);
                if(ntoks==2){
                    cron_tab_t *tab=micron_tab_slookup(CRON_HND_DFL,toks[0]);
                    retval = ( micron_tab_set_path(tab,toks[1],true)==0 ? ERR_SUCCESS : ERR_FAILURE) ;
                }
            }
            pfree(cpy);
        }else if(strstr(cp,"T:")!=NULL){
            char *toks[4]={NULL,NULL,NULL,NULL};
            char *cpy=pstrdup((cp+2));
            if(NULL!=cpy){
                int ntoks=tokenize(cpy,toks,",",4);
                schedule_t sch={0};
                char *sp=micron_qtrim_str(toks[2]);
                int sch_stat=sched_parse(sch,sp);
                int task=-1;
                sscanf(toks[1],"%d",&task);
                cron_tab_t *tab=micron_tab_slookup(CRON_HND_DFL,toks[0]);
                char *scr = (ntoks>3 ? toks[3] : NULL);

                if(NULL!=tab &&
                        sch_stat==0 &&
                        task>=0 && task<CRON_MAX_TASKS ){

                    int test=micron_tab_set_task(tab,task,sch,scr);
                    if(test==0){
                        retval=ERR_SUCCESS;
                    }else{
                        OUT_CTTY("ERR - set_task failed [%d]\r\n",test);
                        OUT_CTTY("Note: options containing whitespace must be enclosed in quotes\r\n");
                   }

                }else{
                    OUT_CTTY("ERR - tab[%c] task[%c] sch[%c] scr[%c]\r\n",
                            BOOLCHAR((NULL!=tab )), BOOLCHAR((sch_stat==0 )),
                            BOOLCHAR((task>=0 && task<CRON_MAX_TASKS)), BOOLCHAR((NULL!=scr)) );
                }
           }
            pfree(cpy);
        }else if(strstr(cp,"R:")!=NULL){
            // reset task
            char *toks[2]={NULL,NULL};
            char *cpy=pstrdup((cp+2));
            if(NULL!=cpy){
                int ntoks=tokenize(cpy,toks,",",2);
                if(ntoks==2){
                    int task=-1;
                    cron_tab_t *tab=micron_tab_slookup(CRON_HND_DFL,toks[0]);
                    bool task_valid=(sscanf(toks[1],"%d",&task)==1 && task>=0 && task<CRON_MAX_TASKS);
                    if(NULL!=tab && task_valid){
                        retval=( micron_tab_reset_task(tab,task)==0 ? ERR_SUCCESS : ERR_FAILURE);
                    }else{
                        OUT_CTTY("ERR - tab[%c] task[%c]\r\n",BOOLCHAR((NULL!=tab )), BOOLCHAR(task_valid) );
                    }//invalid arg
                }
            }
            pfree(cpy);
        }
    }

    retval=ERR_SUCCESS;
    return retval;
}
#endif // WITH_MICRON

/// set module debug flags
return_code_t cli_set_debug(int argc, char **argv)
{
    return_code_t retval = ERR_SUCCESS;
    DSHOW_TOKS(argc,argv);
    uint32_t mask=0;
    char state=0;
    char quiet=0;
    int i=0;

    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"%c%lx",&state,&mask)==2){
            if(state=='+'){
                SYS_SET_MDFLAGS(mask);
            }else if(state=='-'){
                SYS_CLR_MDFLAGS(mask);
            }else if(state=='='){
                SYS_ASSIGN_MDFLAGS(mask);
            }else{
                retval = ERR_INVALID;
            }
        }else if(strcmp(argv[i],"H")==0){
            OUT_CTTY(" MOD_ACT  x%08lX\r\n",(uint32_t)MOD_ACT);
            OUT_CTTY(" MOD_SEQ  x%08lX\r\n",(uint32_t)MOD_SEQ);
            OUT_CTTY(" MOD_TMR  x%08lX\r\n",(uint32_t)MOD_TMR);
            OUT_CTTY(" MOD_SCR  x%08lX\r\n",(uint32_t)MOD_SCR);
            OUT_CTTY(" MOD_LIC  x%08lX\r\n",(uint32_t)MOD_LIC);
            OUT_CTTY(" MOD_SER  x%08lX\r\n",(uint32_t)MOD_SER);
            OUT_CTTY(" MOD_CFG  x%08lX\r\n",(uint32_t)MOD_CFG);
            OUT_CTTY(" MOD_PRO  x%08lX\r\n",(uint32_t)MOD_PRO);
            OUT_CTTY(" MOD_SCH  x%08lX\r\n",(uint32_t)MOD_SCH);
            OUT_CTTY(" MOD_CRON x%08lX\r\n",(uint32_t)MOD_CRON);
            OUT_CTTY(" MOD_MDIO x%08lX\r\n",(uint32_t)MOD_MDIO);
            OUT_CTTY(" MOD_MEAS x%08lX\r\n",(uint32_t)MOD_MEAS);
            OUT_CTTY(" MOD_MAIN x%08lX\r\n",(uint32_t)MOD_MAIN);
            OUT_CTTY(" MOD_TOKS x%08lX\r\n",(uint32_t)MOD_TOKS);
            OUT_CTTY(" MOD_SYS  x%08lX\r\n",(uint32_t)MOD_SYS);
            OUT_CTTY(" MOD_AUX  x%08lX\r\n",(uint32_t)MOD_AUX);
            OUT_CTTY(" MOD_LOG  x%08lX\r\n",(uint32_t)MOD_LOG);
            OUT_CTTY("\r\n");
        }
    }

    if(quiet!='Q'){
        OUT_CTTY(" SYS_MDFLAGS=x%08lx\r\n",SYS_MDFLAGS);
        OUT_CTTY("\r\n");
    }

    return retval;
}
#ifdef WITH_UI_ECHO

/// COMMAND:<b>  ECHO msg  </b>@n
/// echo debug info to console/host (expands variables and escape sequences)
return_code_t cli_echo(int argc, char **argv)
{
    MPRINT(MOD_ACT,"ECHO[%d]\r\n",argc);
    int shft = 0;
    bool to_host=false;
    bool to_cons=true;
    int i=0;
    for(i=0;i<2;i++){
        if(argc>i){
            if(argv[i][0]=='+'){
                if(strstr(argv[i],"H")!=NULL){
                    to_host=true;
                }
               if(strstr(argv[i],"C")!=NULL){
                    to_cons=true;
                }
                shft++;
            }
            if(argv[i][0]=='-'){
                if(strstr(argv[i],"H")!=NULL){
                    to_host=false;
                }
               if(strstr(argv[i],"C")!=NULL){
                    to_cons=false;
                }
                shft++;
            }
        }
    }

    char *s=pargcat((argc-shft),&argv[shft]," ");

    if(ui_unescape(s)!=NULL){
        if(to_host)
            OUT_HOST("%s\r\n",s);
        if(to_cons)
            OUT_CTTY("%s\r\n",s);
    }
    pfree(s);

    return ERR_SUCCESS;
}
#endif //WITH_UI_ECHO

/// set VPRINT DPRINT debug level
return_code_t cli_set_print(int argc, char **argv)
{
    return_code_t retval = ERR_FAILURE;
    DSHOW_TOKS(argc,argv);
    int val=0;
    uint32_t u32val=0;
    int i=0;

    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"D%d",&val)==1){
           SET_DPRINT(val);
         retval=ERR_SUCCESS;
       }else if(sscanf(argv[i],"V%d",&val)==1){
            SET_VPRINT(val);
            retval=ERR_SUCCESS;
       }else if(sscanf(argv[i],"X=%"FMT_X32"",&u32val)==1){
            SET_XPRINT(u32val);
            retval=ERR_SUCCESS;
       }else if(sscanf(argv[i],"X+%"FMT_X32"",&u32val)==1){
            g_xprint |= u32val;
            retval=ERR_SUCCESS;
       }else if(sscanf(argv[i],"X-%"FMT_X32"",&u32val)==1){
            g_xprint &= ~(u32val);
           retval=ERR_SUCCESS;
       }
    }
    return retval;
}

/// get VPRINT DPRINT debug level
return_code_t cli_get_print(int argc, char **argv)
{
    return_code_t retval = ERR_FAILURE;
    DSHOW_TOKS(argc,argv);
    int i=0;
    if(argc<=1){
        OUT_CTTY("D%d\r\n",g_dprint);
        OUT_CTTY("V%d\r\n",g_vprint);
        OUT_CTTY("X%"FMT_X32"\r\n",g_xprint);
        retval=ERR_SUCCESS;
  }else
    for(i=0;i<argc;i++){
        if(strstr(argv[i],"D")!=NULL){
            OUT_CTTY("D%d\r\n",g_dprint);
            retval=ERR_SUCCESS;
       }else if(strstr(argv[i],"V")!=NULL){
            OUT_CTTY("V%d\r\n",g_vprint);
            retval=ERR_SUCCESS;
       }else if(strstr(argv[i],"M")!=NULL){
            OUT_CTTY("X%"FMT_X32"\r\n",g_xprint);
            retval=ERR_SUCCESS;
       }
    }
    return retval;
}

// use    : MDIO [ options... ]
//
// brief  : Assert digital IO and macros w/ optional delay between operations.
//          Multiple options may be provided.
//
// Options:
//
//  <macro>   - Run macro (commonly used IO sequences, see notes)
//
//  <preset>  - Actuate pre-defined valve sets or pumps (see notes)
//
//  PHL, PLH  - pulse IO bit(s) HI/LO, LO/HI (multiple cycles, e.g. pump)
//              use: phl:<iobits,ton,toff,cycles,[post-delay]>
//
//  SHL, SLH  - pulse IO bit(s) HI/LO, LO/HI (single cycle, toff=0 e.g. valve)
//              use: shl:<iobits,ton,[post-delay]>
//
//  IOH, IOL  - set IO bit(s) HI/LO (i.e. ON/OFF)
//              use: ioh:<iobits,[post-delay]>
//
//  D         - delay for specified time (msec)
//              use: d<n> (e.g. d5000) or d$cx<n> (e.g. d$cx3)
//
//  PT        - preset valve pulse time (msec)
//              Applies only to IO presets
//              use: pt<n> (e.g. vt200)
//
//  PH        - preset pump pulse ton (msec)
//              Applies only to IO presets
//              use: ph<n> (e.g. ph200)
//
//  PL        - preset pump pulse toff (msec)
//              Applies only to IO presets
//              use: pl<n> (e.g. pl300)
//
//  PN        - preset pump pulse cycles
//              Applies only to IO presets
//              use: pn<n> (e.g. pn15)
//
//  MT        - macro valve pulse time (msec)
//              Applies only to macros
//              use: mt<n> (e.g. mt100)
//
//  MH        - macro pump pulse ton (msec)
//              Applies only to macros
//              use: mh<n> (e.g. mh150)
//
//  ML        - macro pump pulse toff (msec)
//              Applies only to macros
//              use: ml<n> (e.g. ml300)
//
//  MN        - macro pump pulse cycles
//              Applies only to macros
//              use: mn<n> (e.g. mn20)
//
//  +V,-V     - enable/disable verbose output
//
//  SHOW      - show state and preset/macro names
//
// where:
//
//   iobits     - IO bits (use SET BITS syntax, separated by '/')
//   ton        - pulse ON time  (msec)
//   toff       - pulse OFF time (msec)
//   cycles     - pulse cycles
//   post-delay - post-opertion delay (msec)
//   <n>        - integer number
//
//## <preset> Values (IO Presets) [2]:
//
//   +/-AIR       - air pump on/off
//   +/-SEA       - seawater pump on/off
//   +/-LI        - licor power on/off
//   APUMP        - run acid pump
//   SPUMP        - run spump
//   +/-IOPURGE   - purge vset
//   +/-IOFILL    - fill vset
//   +/-IOZERO    - zero vset
//   +/-IOZVENT   - zero vent vset
//   +/-IOZCAL    - zcal vent vset
//   +/-IOSZERO   - szero vset
//   +/-IOSZVENT  - szero vent vset
//   +/-IOSAMPLE  - sample measurement vset
//   +/-IOAIR     - air measurement vset
//   +/-IOSTD     - standard measurement vset
//   +/-IOSTDFILL - standard fill vset
//   +/-IOREST    - rest vset
//   +IOEXCERn    - exercise step n[1:8] vset (+ only)
//   +/-IOSCAL    - scal vset
//   +/-IOSCALGAS - scal gas vset
//
//## <macro> Values
//
//   FILL   - Fill volume
//   PURGE  - Empty volume
//   ACID   - Inject acid
//   ZERO   - Zero path
//   ZCAL   - Zero calibration
//   SZERO  - SZero calibration
//   SAMPLE - Sample measurement
//   AIR    - Air measurement
//   SCAL   - Standard calibration
//   STD    - Standard measurement
//   MTERM  - Turn of pump and licor
//   REST   - Put valves in rest state
//   VEX    - Exercise valves
//
// Examples:
//
//	IO preset:
//
//		  # Run purge macro (set up valve IO, w/ pump, delays, etc.)
//		   mdio purge
//
//		  # Set valve IO preset only
//		   mdio +iopurge
//
//		  # Clear valve IO preset only
//		   mdio -iopurge
//
//		 Using mdio with mmeas:
//
//		  # Use setup macro prepare for sample measurements
//			 mdio sample
//
//		  # Take measurements
//			 mmeas mmpbl prebl mms sample mmpob postb
//
//		  # Use mterm macro to turn off air pump and licor
//		  # after measurement
//			 mdio mterm
//
//	User defined IO sequence:
//
//		  # Execute the following sequence
//		  # - pulse v1a, v3a,b ON
//		  # - wait 5s
//		  # - pulse acid pump 15 cycles (300 ms ON, 200 ms OFF)
//		  # - turn on air pump
//		  # - wait 10s
//		  # - turn air pump OFF
//		  # - set pump cycles=10 and run sample pump with default pump pulse timing
//		  # - put IO in rest configuration
//
//			mdio vhl,v1a/v3,5000 phl,pa,300,200,15 +air d10000 -air pn10 spump rest
//
//Notes:
//
//     Operations are evaluated and executed left to right.
//
//     Command and options are case-insensitive
//
//     Command length limited by UI_CMD_BUF_BYTES (console context)
//     or SCR_LINE_BUFFER_BYTES (in script context). Use multiple lines
//     for long sequences.
//
// [1] options pt, ph, pl, pn, mt, mh, ml, mn
//     - are sticky (per command line), may set multiple times
//     - use a default value if unset
//     - defaults used if unset (see cli_impl.h)
//
// [2] pump cycles (pn) must be set >0 to enable pump IO presets e.g. "mdio pn10 spump"
//     - pn, mn are sticky (per command line)
//     - may be set multiple times on same line
//     - use phl/plh to actuate multiple pumps simultaneously

return_code_t cli_mdio(int argc, char **argv)
{
    return_code_t retval=ERR_SUCCESS;
    unsigned int pre_vpulse_msec=MX_VALVE_TON_MSEC;
    unsigned int pre_pump_on_msec=MX_PUMP_TON_MSEC;
    unsigned int pre_pump_off_msec=MX_PUMP_TOFF_MSEC;
    unsigned int pre_cycles=0;

    // these work the same as
    // pre_vpulse_msec, pre_pump_on_msec, etc.
    // except that
    // - they only apply to macros
    // - they are only used if set (per-macro default used otherwise)
    unsigned int mac_pulse_msec=0;
    unsigned int mac_on_msec=0;
    unsigned int mac_off_msec=0;
    unsigned int mac_cycles=0;

    unsigned long int x_io_bits=0;
    unsigned int x_on_msec=0;
    unsigned int x_off_msec=0;
    unsigned int x_cycles=0;
    unsigned int x_delay=0;

    unsigned int delay_msec=0;
    unsigned long int io_bits=0x0;
    unsigned long int pump_bits=0x0;
    bool verbose=false;
    char *cur[2]={NULL,NULL};

    int i=0;
    for(i=0;i<argc;i++){
        const sequence_t *sequence=NULL;
        const seq_actuation_t *action=NULL;
        seq_param_t pump_logic_s={{-1,-1,-1},-1},*pump_logic=NULL;
        seq_param_t valve_logic_s={{-1,-1,-1},-1},*valve_logic=NULL;
        seq_param_t io_logic_s={{-1,-1,-1},-1},*io_logic=NULL;
        if(verbose){
        OUT_CTTY("argv[%d][%s]\r\n",i,argv[i]);
        }
        io_bits=0x0;
        cur[0]=NULL;
        cur[1]=NULL;
        x_io_bits=0;
        x_on_msec=0;
        x_off_msec=0;
        x_cycles=0;

        if(sscanf(argv[i],"PT%u",&pre_vpulse_msec)==1){
        }else if(sscanf(argv[i],"PH%u",&pre_pump_on_msec)==1){
        }else if(sscanf(argv[i],"PL%u",&pre_pump_off_msec)==1){
        }else if(sscanf(argv[i],"PN%u",&pre_cycles)==1){
        }else if(sscanf(argv[i],"MT%u",&mac_pulse_msec)==1){
        }else if(sscanf(argv[i],"MH%u",&mac_on_msec)==1){
        }else if(sscanf(argv[i],"ML%u",&mac_off_msec)==1){
        }else if(sscanf(argv[i],"MN%u",&mac_cycles)==1){
        }else if(sscanf(argv[i],"D%u",&delay_msec)==1){
            MX_DELAY(delay_msec);
        }else if(strstr(argv[i],"DCX")!=NULL){
            char *pp=argv[i]+1;
            uint32_t u32=0;
            config_get(pp,&u32);
            MX_DELAY(u32);
        }else if( (sequence=seq_slookup(argv[i]))!=NULL){
            // run a named sequence (macro)
            if(mac_cycles>0 || mac_on_msec || mac_off_msec){
                // set pump logic non-NULL and
                // configure alternative timing if pump options set
                pump_logic=&pump_logic_s;
                // use values if set, or -1 to use default
                pump_logic->cycles=(mac_cycles>0 ? mac_cycles : -1);
                pump_logic->timing[ATIM_P0]=(mac_on_msec>0 ? mac_on_msec : -1);
                pump_logic->timing[ATIM_P1]=(mac_off_msec>0 ? mac_off_msec : -1);
            }
            if(mac_pulse_msec>0){
                // set valve logic non-NULL and
                // configure alternative timing if pump options set
                valve_logic=&valve_logic_s;
                 // use values if set, or -1 to use default
                valve_logic->timing[ATIM_P0]=(mac_pulse_msec>0 ? mac_pulse_msec : -1);
                valve_logic->timing[ATIM_P1]=0;
            }
            // pump_logic and valve_logic are NULL unless overridden
            // by M[THLN]]
            seq_run(sequence,pump_logic,valve_logic);
            pump_bits=0x0;
        }else if( (action=seq_actuation_slookup(argv[i]))!=NULL){
            // run IO preset
            switch(action->act){
                case ACT_VALVE_HL:
                case ACT_VALVE_LH:
                case ACT_ON:
                case ACT_OFF:
                    // run an IO switching action
                    io_logic=&io_logic_s;
                    io_logic->timing[ATIM_P0]=(pre_vpulse_msec>0 ? pre_vpulse_msec : MX_VALVE_TON_MSEC);
                    io_logic->timing[ATIM_P1]=0;
                    io_logic->cycles=1;
                    seq_actuation_run(action,io_logic);
                    break;
                case ACT_PUMP_HL:
                case ACT_PUMP_LH:
                    pump_logic=&pump_logic_s;
                    // use values if set, or -1 to use default
                    pump_logic->cycles=(pre_cycles>0 ? pre_cycles : -1);
                    pump_logic->timing[ATIM_P0]=(pre_pump_on_msec>0 ? pre_pump_on_msec : MX_PUMP_TON_MSEC);
                    pump_logic->timing[ATIM_P1]=(pre_pump_off_msec>0 ? pre_pump_off_msec : MX_PUMP_TOFF_MSEC);
                    seq_actuation_run(action,pump_logic);
                    break;
                default:
                    TRACE();
                    break;
            }
        }else if(strstr(argv[i],"PHL:")!=NULL || strstr(argv[i],"PLH:")!=NULL
                || strstr(argv[i],"SHL:")!=NULL || strstr(argv[i],"SLH:")!=NULL
                || strstr(argv[i],"IOH:")!=NULL || strstr(argv[i],"IOL:")!=NULL){

            uint32_t err_flags=0;
            typedef enum{
                EINVAL=0x1,
                ENULL=0x2,
                EPARSE=0x4,
                EPREV=0x8
             }ERR_TYPE_t;

            // point cur[0] at start of iobits sub-string
            // can't be NULL (strstr)
            cur[0]=strchr(argv[i],':')+1;
            // point cur[1] at timing parameters
            // cur[1] may be NULL in some cases
            cur[1]=strchr(cur[0],',');
            if(NULL!=cur[1]){
                // advance past ','
                cur[1]++;
            }

            // set action and timing defaults
            ACT_ID_t io_action = 0;
            if(argv[i][0]=='P'){
                io_action = (argv[i][1]=='H' ? ACT_PUMP_HL : ACT_PUMP_LH ) ;
            }else if(argv[i][0]=='S'){
                io_action = (argv[i][1]=='H' ? ACT_VALVE_HL : ACT_VALVE_LH );
            }else if(argv[i][0]=='I'){
                io_action = (argv[i][2]=='H' ? ACT_ON : ACT_OFF );
            }else{
                err_flags|=EINVAL;
             }
            x_on_msec=0;
            x_off_msec=0;
            x_cycles=0;
            x_delay=0;

            // use copy of iobits strtok
             char *acpy = pstrdup(cur[0]);
             char *cp=strchr(acpy,',');
            // remove timing from iobits string
            if(cp!=NULL){
                *cp='\0';
            }

            DPRINT("iobits/acpy  [%s]\r\n",acpy);
            DPRINT("logic/cur[1] [%s]\r\n",cur[1]);

            if(err_flags==0 && NULL!=acpy ){

                // /tokenize/parse iobits sub-string
                char *btok = strtok(acpy,"/");
                while(NULL!=btok){
                    unsigned long int bits=0;
                    if(dio_parse_bits(btok,&bits)==ERR_SUCCESS){
                        x_io_bits|=bits;
                        DPRINT("btok [%s] bits[%08lX] xio[%08lX]\r\n",btok,bits,x_io_bits);
                    }else{
                        EPRINT("parsing io [%s]\r\n",btok);
                        err_flags|=EPARSE;
                    }
                    btok=strtok(NULL,"/");
                }

                if(err_flags==0){
                    // parse logic parameters (ton,toff,cycles,delay)
                    if( io_action==ACT_PUMP_HL || io_action==ACT_PUMP_LH){
                        // ton,toff,cycles required
                        // delay optional
                        if(NULL!=cur[1] &&
                                sscanf(cur[1],"%u,%u,%u,%u",&x_on_msec,&x_off_msec,&x_cycles,&x_delay)<3){
                            err_flags|=0x8;
                        }
                    }else if( io_action==ACT_VALVE_HL || io_action==ACT_VALVE_LH){
                        // ton, required
                        // delay optional
                        if(NULL!=cur[1] &&
                                sscanf(cur[1],"%u,%u",&x_on_msec,&x_delay)<1){
                            err_flags|=0x10;
                        }
                    }else if(io_action==ACT_ON || io_action==ACT_OFF){
                        // delay optional
                        if(NULL!=cur[1] && strlen(cur[1])>0){
                            // must be valid if specified
                            if(sscanf(cur[1],"%u",&x_delay)!=1){
                               err_flags|=0x20;
                            }
                        }else{
                            // delay optional (OK if NULL)
                        }
                    }else{
                        err_flags|=EINVAL;
                    }
                }// else err_flags

            }else{
                err_flags|=(EPREV|ENULL);
            }

            // mask protected IO bits
            x_io_bits &= ~(IO_PROTECTED);

            if(verbose){
                OUT_CTTY("action     %08d\r\n",io_action);
                OUT_CTTY("x_io_bits  %08lX\r\n",x_io_bits);
                OUT_CTTY("x_on_msec  %8u\r\n",x_on_msec);
                OUT_CTTY("x_off_msec %8u\r\n",x_off_msec);
                OUT_CTTY("x_cycles   %8u\r\n",x_cycles);
                OUT_CTTY("x_delay    %8u\r\n",x_delay);
                OUT_CTTY("err_flags  %8lX\r\n",err_flags);
            }

            // execute the action
            if(x_io_bits>0 && err_flags==0){
                const seq_actuation_t usr_action = {"USR",x_io_bits,io_action};
                const seq_param_t usr_logic = {{x_on_msec,x_off_msec,x_delay},x_cycles};
                if(verbose){
                    seq_actuation_show(&usr_action,5);
                    seq_param_show(&usr_logic,5);
                }
                seq_actuation_run(&usr_action,&usr_logic);
            }else{
                EPRINT("Invalid format [%s] err_flags[x%lX]\r\n",argv[i],err_flags);
                if(verbose){
                OUT_CTTY("  phl/lh,iobits,ton,toff,cycles,[post-delay]\r\n");
                OUT_CTTY("  shl/lh,iobits,ton[,delay]\r\n");
                OUT_CTTY("  ioh/l,iobits[,delay]\r\n");
                }
            }

            // release string copies
            // pfree checks for NULL
            pfree(acpy);


        }else if(strcmp(argv[i],"+V")==0){
            verbose=true;
        }else if(strcmp(argv[i],"-V")==0){
            verbose=false;
        }else if(strcmp(argv[i],"SHOW")==0){
#ifdef WITH_MDIO_SHOW
            int indent=2;
            int wkey=18;
            int wval=9;
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"PT",wval,pre_vpulse_msec);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"PH",wval,pre_pump_on_msec);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"PL",wval,pre_pump_off_msec);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"PN",wval,pre_cycles);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"MT",wval,mac_pulse_msec);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"MH",wval,mac_on_msec);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"ML",wval,mac_off_msec);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"MN",wval,mac_cycles);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"D",wval,delay_msec);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"MX_VALVE_TON_MSEC",wval,MX_VALVE_TON_MSEC);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"MX_PUMP_TON_MSEC",wval,MX_PUMP_TON_MSEC);
            OUT_CTTY("%*s%*s %*u\r\n",indent,"",wkey,"MX_PUMP_TOFF_MSEC",wval,MX_PUMP_TOFF_MSEC);
            int j=0;
            OUT_CTTY("%*s%*s\r\n",indent,"",wkey,"----  Macros  ----");
            for(j=0;j<SEQ_IDX_COUNT;j++){
                const sequence_t *next=seq_ilookup(j);
                if(NULL!=next && NULL!=next->name){
                    OUT_CTTY("%*s%*s\r\n",indent,"",wkey,next->name);
                }
            }
            OUT_CTTY("%*s%*s\r\n",indent,"",wkey,"--- IO Presets ---");
            for(j=0;j<AIO_IDX_COUNT;j++){
                const seq_actuation_t *next=seq_actuation_ilookup(j);
                if(NULL!=next && NULL!=next->name){
                    OUT_CTTY("%*s%*s %*sx%07lX\r\n",indent,"",wkey,next->name,wval-8,"",next->iomask);
                }
            }
#endif
        }
   }
   return retval;
}

// run mmeas measurement profile
static int s_run_meas_profile(meas_profile_t *op_cfg, licor_mbuf_handle_t mbuf_id, li_handler_fn handler_fn, uint32_t *cycle_id, uint32_t *meas_seq, uint32_t flags)
{
    int retval=-1;
    // measurement execution block
    // validate args
    if(NULL!=op_cfg &&
            mbuf_id!=LI_MBUF_HND_INVALID &&
            op_cfg->period_s>0.0 &&
            op_cfg->meas_n>=0 &&
            op_cfg->to_msec>=0){

        if(op_cfg->op_id==MMID_SCAL && op_cfg->cal_wr=='+'){
           double spanc = 0.0;
            int test=0;
            if( (test=config_get(CK_LICOR_SPANC,&spanc))==ERR_SUCCESS && spanc>0.0){
                licor_cal(LI_CAL_S,spanc);
            }else{
                log_syslog("ERR - LI_SPANC invalid/not found[%d/%lf]",test,spanc);
            }
        }else if(op_cfg->op_id==MMID_ZCAL && op_cfg->cal_wr=='+'){
            licor_cal(LI_CAL_Z,0.0);
        }

        // to_msec==0 valid: wait for user interrupt (CTRL-D on g_ctty)
//        if(op_cfg->to_msec<=0){
//            op_cfg->to_msec=(op_cfg->blank_ms+1200UL*op_cfg->period_s*op_cfg->meas_n);
//        }

        licor_mbuf_set_mmid(mbuf_id, op_cfg->mmid);
        licor_mbuf_set_mperiod(mbuf_id, op_cfg->period_s);

        uint16_t rm=op_cfg->rmid;
        uint16_t mm=op_cfg->mmid;
        uint16_t raw_id=((rm&0xFF) << 8)+((mm&0xFF));
        char rid_str[8]={0};
        sprintf(rid_str,"%04X",raw_id);



        // orec_fmt: meas profile, session seq, flags
        licor_orecord_ctx_t octx={op_cfg,0,0,0,flags};
        licor_zctx_t zctx={mbuf_id,&octx};

        if(handler_fn == licor_handler_o){
            // for streaming handler
            // if no output flags set, default to console
            if( ((octx.flags&QOF_CONSOLE) == 0) && ((octx.flags&QOF_HOST) == 0) && ((octx.flags&QOF_LOG) == 0) ){
                octx.flags|=QOF_CONSOLE;
            }
            // if no format flags, set to CSV
            if( ((octx.flags&QOF_FMT_FLAGS) == 0)  ){
                octx.flags|=QOF_CSV;
            }

            octx.meas_seq = *meas_seq;
            (*meas_seq)++;
            octx.cycle_id = *cycle_id;

        }else if(handler_fn == licor_handler_z){
            // always log raw for zhandler
            flags|=QOF_LOG_RAW;
            octx.meas_seq = *meas_seq;
            (*meas_seq)++;
            octx.cycle_id = *cycle_id;

            // tentatively set mbuf zecord seq number
            licor_zrecord_t *mbuf_zrec = licor_mbuf_record(mbuf_id);
            mbuf_zrec->hdr.cycle_id=*cycle_id;
            if(NULL!=mbuf_zrec){
                log_get_seq_number(LOG_ZBIN,&mbuf_zrec->hdr.seq_n,false);
            }
        }
//        void *pargs = (handler_fn==licor_handler_z ? (void *)&mbuf_id : (void *)&octx);
        void *pargs = (handler_fn==licor_handler_z ? (void *)&zctx : (void *)&octx);

        MPRINT(MOD_MEAS,"mmeas: blank_ms[%lu] period[%.2f] n_samp[%lu] to_msec[%lu]\r\n",op_cfg->blank_ms, op_cfg->period_s, op_cfg->meas_n,op_cfg->to_msec);
        MPRINT(MOD_MEAS,"mmeas: pargs[%p] handler[%p] flags[%08"FMT_X32"/%08"FMT_X32"]\r\n",pargs,handler_fn,flags,octx.flags);
        if(licor_stream_x(op_cfg->blank_ms, op_cfg->period_s, op_cfg->meas_n, handler_fn, pargs, rid_str, op_cfg->to_msec, flags)==ERR_SUCCESS){

            Time rawTime;
            rtcRead(&rawTime);
            uint64_t timestamp = rtc_mktime(&rawTime)*1000ULL;
            licor_mbuf_set_rtime(mbuf_id, timestamp);
            // update measurement buffer state
            licor_mbuf_next(mbuf_id);
            retval=0;
        }else{
            MPRINT(MOD_ACT,"ERR - licor_stream_x\r\n");
        }
    }else{
        WMSG("Invalid measurement profile\r\n");
    }
    return retval;
}

static void s_parse_stream_oflags(char *fmt, queue_output_flag *oflags)
{
    char *fp=NULL;
    MPRINT(MOD_ACT,"%s ENTER fmt[%s] oflags[%"FMT_X32"]\r\n",__func__,fmt,*oflags);
    if(NULL!=fmt){
        if( fmt[0]=='C'){
            *oflags |= QOF_CONSOLE;
            fp=strstr(fmt,":");
        }else if( fmt[0]=='H'){
            *oflags |= QOF_HOST;
            fp=strstr(fmt,":");
        }
        if(NULL!=fp){
            if(NULL!=strstr(fp,"B")){
                *oflags |= QOF_BASE85;
            }
            if(NULL!=strstr(fp,"C")){
                *oflags |= QOF_CSV;
            }
            if(NULL!=strstr(fp,"M")){
                *oflags |= (QOF_CSVX|QOF_CSV);
            }
            if(NULL!=strstr(fp,"P")){
                *oflags |= QOF_PRETTY;
            }
            if(NULL!=strstr(fp,"R")){
                *oflags |= QOF_RAW;
            }
            char *xp=fp;
            while(*xp!='\0'){
                if(*xp=='X'){
                    if( (*(xp+1)=='X')){
                        *oflags |= QOF_PRETTY_HEX;
                        xp++;
                    }else if( (*(xp+1)=='*')){
                        *oflags |= (QOF_PRETTY_HEX|QOF_HEX);
                        xp++;
                    }else{
                        *oflags |= QOF_HEX;
                    }
                }
                xp++;
            }
        }
    }
    MPRINT(MOD_ACT,"%s - EXIT fmt[%s] oflags[%"FMT_X32"]\r\n",__func__,fmt,*oflags);
}

static int s_parse_profile_opts(char *opt, meas_profile_t *pprof,  queue_output_flag *oflags, li_handler_fn *r_handler, bool *r_run)
{
    int retval=-1;
    if(NULL!=opt && NULL!=pprof ){
        int16_t test_i16=-1;

        // flip return value good, change on errors
        retval=0;

        if(opt[0]=='N' && sscanf(opt,"N%lu",&pprof->meas_n)==1){

            pprof->to_msec=1200UL*pprof->period_s*pprof->meas_n;
            MPRINT(MOD_ACT,"N[%lu] to_msec[%lu]\r\n",pprof->meas_n,pprof->to_msec);

        }else if(opt[0]=='B' && sscanf(opt,"B%lu",&pprof->blank_ms)==1){

            MPRINT(MOD_ACT,"B[%lu]\r\n",pprof->blank_ms);

        }else if(opt[0]=='P' &&
                sscanf(opt,"P%f",&pprof->period_s)==1 &&
                (pprof->period_s==0.0 || pprof->period_s>=0.5)){

            pprof->meas_n = (pprof->meas_ms/1000ul)/pprof->period_s;
            pprof->to_msec=1200UL*pprof->period_s*pprof->meas_n;
            MPRINT(MOD_ACT,"P[%.1f] meas_n[%lu] to_msec[%lu]\r\n",pprof->period_s,pprof->meas_ms,pprof->to_msec);

        }else if(opt[0]=='T' &&
                sscanf(opt,"T%lu",&pprof->meas_ms)==1){

            pprof->meas_n = (pprof->meas_ms/1000ul)/pprof->period_s;
            pprof->to_msec=1200UL*pprof->period_s*pprof->meas_n;
            MPRINT(MOD_ACT,"T[%lu] meas_n[%lu] to_msec[%lu]\r\n",pprof->meas_ms,pprof->meas_n,pprof->to_msec);

        }else if( (strstr(opt,"RM")==opt) &&
                (sscanf(opt,"RM%u",&test_i16)==1 ||
                 sscanf(opt,"RMX%X",&test_i16)==1 ||
                (test_i16=licor_rmid_slookup(opt))!=RMID_INVALID)){

            pprof->rmid=test_i16;
            MPRINT(MOD_ACT,"RM[%u]\r\n",pprof->rmid);

        }else if( (strstr(opt,"RT")==opt) &&
                (sscanf(opt,"RT%u",&test_i16)==1 ||
                 sscanf(opt,"RTX%X",&test_i16)==1 ||
                (test_i16=licor_rtid_slookup(opt))!=RTID_INVALID)){

            pprof->rtid=test_i16;
            MPRINT(MOD_ACT,"RT[%u]\r\n",pprof->rtid);

        }else if( (strstr(opt,"MM")==opt) &&
                (sscanf(opt,"MM%u",&test_i16)==1 ||
                 sscanf(opt,"MMX%X",&test_i16)==1 ||
                (test_i16=licor_mmid_slookup(opt))!=MMID_INVALID)){

            pprof->mmid=test_i16;
            MPRINT(MOD_ACT,"MM[%u]\r\n",pprof->mmid);

        }else if(opt[0]=='O' &&
                (sscanf(opt,"O%u",&test_i16)==1 ||
                 sscanf(opt,"OX%X",&test_i16)==1 ||
                (test_i16=licor_mmid_slookup(opt+1))!=MMID_INVALID)){

            pprof->op_id=test_i16;
            MPRINT(MOD_ACT,"OP[%u]\r\n",pprof->op_id);

        }else if(opt[0]=='W' &&
                sscanf(opt,"W%c",(char *)&test_i16)==1 &&
                (test_i16=='+' || test_i16=='-')){

            pprof->cal_wr=test_i16;
            MPRINT(MOD_ACT,"W[%c]\r\n",(char)pprof->cal_wr);

        }else if(opt[0]=='D' &&
                sscanf(opt,"D%d",&test_i16)==1 &&
                test_i16>0){

                MPRINT(MOD_ACT,"D[%d]\r\n",test_i16);
                MX_DELAY(test_i16);
        }else if(strcmp(opt,"RUN")==0 ){
                if(NULL!=r_run){
                    // return run flag
                    *r_run = true;
                }else{
                    TRACE();
                    retval=-1;
                }
        }else if(strcmp(opt,"FO")==0){
            if(NULL!=r_handler){
                *r_handler=licor_handler_o;
                MPRINT(MOD_ACT,"FO\r\n");
            }else{
                TRACE();
                retval=-1;
            }

        }else if(strcmp(opt,"FZ")==0){
            if(NULL!=r_handler){
                *r_handler=licor_handler_z;
                MPRINT(MOD_ACT,"FZ\r\n");
             }else{
                TRACE();
                retval=-1;
            }

        }else if(opt[0]=='C'){
            s_parse_stream_oflags(opt, oflags);
            MPRINT(MOD_ACT,"opt[%s] oflags[%"FMT_X32"]\r\n",opt,*oflags);

        }else if(opt[0]=='H'){
            s_parse_stream_oflags(opt, oflags);
            MPRINT(MOD_ACT,"opt[%s] oflags[%"FMT_X32"]\r\n",opt,*oflags);

        }else if(opt[0]=='L'){
            if(NULL!=oflags){
                if(opt[1]=='O'){
                    *oflags |= QOF_LOG_OREC;
                }else if(opt[1]=='R'){
                    *oflags |= QOF_LOG_RAW;
                }else if(opt[1]=='*'){
                    *oflags |= (QOF_LOG_OREC|QOF_LOG_RAW);
                }else if(opt[1]=='N'){
                    *oflags &= ~(QOF_LOG_OREC|QOF_LOG_RAW);
                }
            }else{
                TRACE();
                retval=-1;
            }
            MPRINT(MOD_ACT,"L[%c] oflags[%"FMT_X32"]\r\n",(char)test_i16,*oflags);
        }else{
        // fall through - not recognized
        EPRINT("unsupported option [%s]\r\n",opt);
        retval=-1;
        }
    }
    return retval;
}

static int s_parse_out_opts(char *opt, queue_output_flag *oflags)
{
    int retval=-1;
    if(NULL!=opt && NULL!=oflags ){
         // parse destination
        char *tok=NULL;
        if((tok=strtok(opt,","))!=NULL){
            if(strstr(tok,"Q")!=NULL){
                *oflags|=QOF_QUEUE;
            }
            if(strstr(tok,"C")!=NULL){
                *oflags|=QOF_CONSOLE;
            }
            if(strstr(tok,"H")!=NULL){
                *oflags|=QOF_HOST;
            }
            if(strstr(tok,"L")!=NULL){
                *oflags|=QOF_LOG;
            }
        }
        // parse format
        if(NULL!=tok && (tok=strtok(NULL,","))!=NULL){
            VLPRINT(2,"fmt[%s]\r\n",tok);
            if(strstr(tok,"P")!=NULL){
                *oflags|=QOF_PRETTY;
            }
            if(strstr(tok,"R")!=NULL){
                *oflags|=QOF_RAW;
            }
            if(strstr(tok,"B")!=NULL){
                *oflags|=QOF_BASE85;
            }
            if(strstr(tok,"C")!=NULL){
                *oflags|=QOF_CSV;
            }
            if(strstr(tok,"M")!=NULL){
                *oflags|=(QOF_CSV|QOF_CSVX);
            }
            if(strstr(tok,"U")!=NULL){
                *oflags|=QOF_UUENCODE;
            }
            if(strstr(tok,"X*")!=NULL){
                *oflags|=QOF_PRETTY_HEX|QOF_HEX;
            }else if(strstr(tok,"XX")!=NULL){
                *oflags|=QOF_PRETTY_HEX;
            }else if(strstr(tok,"X")!=NULL){
                *oflags|=QOF_HEX;
            }
            if(strstr(tok,"H")!=NULL){
                *oflags|=QOF_HEADER;
            }
            if(strstr(tok,"D")!=NULL){
                *oflags|=QOF_DATA;
            }
            if(strstr(tok,"V")!=NULL){
                *oflags|=QOF_VERBOSE;
            }
        }
    }
    return retval;
}

// use    : mmeas <cmd>:[options]
//
// brief  : Configure and/or execute one or more licor measurement profiles and direct output
//
// Commands (<cmd> values):
//
//  CYC     -  Control measurement cycle parameters
//             use: CYC:[option,...]
//             Options:
//              <RTID>   - record type ID
//              <RMID>   - record measurement ID
//              RST      - reset measurement buffer (resets rtid, rmid)
//
//              Example:
//              Reset measurement buffer, Set record type to RTZ0, record measurement ID
//              to RMSAMPLE
//               cyc:rst,rtz0,rmsample
//
//
//  USR<n>  -  Configure user-defined measurement profile (sticky)
//              use: USR<n>:[option,...]
//              Options:
//                <MEAS_PRESET>    - copy measurement preset (optional, specify first)
//                <MEAS_PARAMETER> - measurement parameters
//                <RTID>
//                <RMID>
//                <MMID>           - record MMID (logged value)
//                <OPID>           - operation MMID
//                				   If MMSCAL, MMZCAL, enables/disables cal write to
//                				   Licor if cal_wr=='+'
//              Example:
//              Set user measurement profile, based on AIR preset;
//              overrides blanking time and number of samples.
//               usr2:AIR,b2000,n13
//
//              Change USR2 licor period to 1.0
//               usr2:p1.0
//
//  LOAD,TM -   Load measurement profile(s), optionally apply overrides and run automatically
//              LOAD will load and configure a profile, but requires explicit RUN to execute.
//              This enables load/inspect without running.
//              TM automatically runs specified profile(s) immediately after processing options.
//
//             use: LOAD:[option,...] or TM:[options]
//
//             Options:
//              <MEAS_PRESET>    - load measurement preset name
//              USR<n>           - load user-defined profile
//              <MEAS_PARAMETER> - measurement parameters (overrides)
//              RUN              - run current profile
//
//             Example:
//             Load and run SAMPLE preset, override number of licor samples
//              mmeas meas:sample,n14,run
//              mmeas tm:sample,b2000,air
//
//  OUTC   -   Output record buffer
//               use: outc:[CHLQ]*,[PBRCXXHDV]*
//
//  OUTM   -   Output last measurement
//               use: outm:[CHLQ]*,[PBRCXXHDV]*
//
//  OUT<n> -   Output record measurement[0<=n<12]
//               use: out[0-9]+[0-1]*:[CHLQ]*,[PBRCXXHDV]*
//
//               where
//
//                [CHLQ] - destination flags
//                 C : console
//                 H : host
//                 L : log
//                 Q : queue
//
//                [PBRCXX*HDV] - format flags (optional, for console only)
//
//                 P  : Pretty
//                 B  : Base85 (ASCII encoding, more efficient than uuencode)
//                 C  : CSV
//                 R  : raw
//                 X  : hex
//                 XX : PrettyHex
//                 X* : hex+PrettyHex
//                 H  : header
//                 D  : data
//                 V  : enable verbose content (if any)
//
//               Examples:
//				  mmeas outm        output last meas to console (Pretty)
//				  mmeas outm:c,b    output last meas to console (Base85)
//				  mmeas outm:c,pxx  output last meas to console (Pretty & PrettyHex)
//				  mmeas outr:q      output record to (cycle) queue
//				  mmeas outm:q      output measurement to (meas) queue
//				  mmeas out4:q      output 5th (i.e. index=4) measurement to (meas) queue
//
//  SHOW   -  Inspect record buffer summary and measurement profiles
//              use: show:[A*BHOPRU]
//
//              where
//
//               [A*BHOPRU] - destination flags
//
//                A,* : all
//                B   : measurement buffer
//                H   : help
//                O   : current operation profile
//                P   : preset profiles
//                R   : measurement record
//                U   : user-defined profiles
//
//
// Options:
//
//  ## <RTID> Values
//  RTZ0          - set record type to default
//  RTX<x>        - set record type (user defined)
//                  use: rtx<x> (e.g. rtx3e5)
//  ## <RMID> Values
//  RMSAMPLE,RMS  - set record measurement type sample
//  RMSCAL,RMSC   - set record measurement type scal
//  RMZCAL,RMSZ   - set record measurement type zcal
//  RMX<x>        - set record measurement type (user defined)
//
//  ## <MMID> Values
//  MMAIR,MMA     - set measurement ID air
//  MMSTD,MMST    - set measurement ID standard
//  MMZERO,MMZ    - set measurement ID zero
//  MMSAMPLE,     - set measurement ID sample
//  MMPRES,MMPRS  - set measurement ID pre-standard
//  MMPOSTS,MMPOS - set measurement ID post-standard
//  MMPREZ,MMPRZ  - set measurement ID pre-zero
//  MMPOSTZ,MMPOZ - set measurement ID post-zero
//  MMPREB,MMPRB  - set measurement ID pre-baseline
//  MMBLINE,MMB   - set measurement ID baseline
//  MMPOSTB,MMPOB - set measurement ID post-baseline
//  MMZCAL,MMZC   - set measurement ID z-calibration
//  MMSCAL,MMSC   - set measurement ID s-calibration
//  MMX<x>        - set measurement ID user-defined
//
// ## <MEAS_PARAMETER>  Values
//
//  P<n>          - set licor sample period (decimal sec, 0.0 or >=0.5)
//                  use: p<f> (e.g. p0.5)
//
//  T<n>          - set measurement time
//                  use: t<n> {e.g. t30000}
//
//  N<n>          - set measurement sample count
//                  use: n<n> {e.g. n16}
//
//  B<n>          - set blanking period (msec)
//                  use: b<n> {e.g. b2000}
//
//  W<n>          - enable/disable calibration value write to instrument
//                  use: w[+-] (e.g. w+)
//
//  D<n>          - delay for specified time (immediate, msec)
//                  use: d<n> {e.g. d12500}
//
// ## <MEAS_PRESET> Values
//
//  <preset>   - use pre-defined measurement parameters
//               FILL   - Fill the chamber
//               PURGE  - Purge gas
//               ACID   - Inject acid
//               ZERO   - Zero gas paths
//               ZCAL   - Zero calibration
//               SCAL   - Standard calibration
//               SZERO  - Zero standard paths
//               SAMPLE - Measure sample
//               AIR    - Measure air
//               STD    - Measure standard
//
// Examples:
//
//  # reset measurement buffer (e.g. at start of cycle)
//  # and set record measurement type to RMSAMPLE
//   mmeas cyc:rst,rms
//
//  # take sample measurement, output measurement to console
//   mmeas ts:sample outm:c,pdh
//
//  # take air sample (using mmid=99) and output record to cycle queue
//   mmeas ts:air,mm99 outr:q
//
//  # load user-defined profile and show measurement info
//   mmeas load:usr3 stat:OB
//
//  # output record to console, in pretty and pretty hex format, with data and headers
//   mmeas outr:c,pxxdh
//
//  # output 5th (i.e. index 0:4) measurement to (meas) queue
//   mmeas out4:q
//
//  # output last measurement to console (Pretty)
//  mmeas outm
//
//  # output record to console (Base85)
//  mmeas outm:c,b
//
//Notes:
//
//     Operations are evaluated and executed left to right.
//
//     Command and options are case-insensitive
//
//     Command length limited by UI_CMD_BUF_BYTES (console context)
//     or SCR_LINE_BUFFER_BYTES (in script context). Use multiple lines
//     for long sequences.

return_code_t cli_mmeas(int argc, char **argv)
{
    return_code_t retval=ERR_SUCCESS;
    meas_profile_t *op_cfg=&op_cfg_s, *usr_cfg=mmeas_usr_profile_table;
    queue_output_flag oflags=QOF_NONE,test_oflags=QOF_NONE,prev_oflags=QOF_NONE;

    static licor_mbuf_handle_t mbuf_id=LI_MBUF_CYCLE;
    li_handler_fn handler_fn=licor_handler_z;
    int16_t test_i16=0;
    static uint32_t meas_seq=0;
    static uint32_t cycle_id=CYCLE_ID_INVALID;
    static bool is_init=false;

    if(!is_init){
        licor_mbuf_reset(mbuf_id);
        licor_mbuf_set_rtid(mbuf_id,RTID_ZRECv0);
        memset(mmeas_usr_profile_table,0,MMEAS_USR_PROFILE_N*sizeof(meas_profile_t));
        meas_seq=0;
        is_init=true;
    }

    int i=0;
    for(i=0;i<argc;i++){

        // save output flags (for show)
        prev_oflags = oflags;
        // reset output flags
        oflags = QOF_NONE;

        MPRINT(MOD_ACT,"MMEAS argv[%d] - [%s]\r\n",i,argv[i]);

        // Cycle init - set RTID, RMID, reset meas buffer
        if(strstr(argv[i],"CYC:")==argv[i] ){

            char *toks[MMEAS_CYC_TOKS]={0};
            char *acpy=pstrdup(argv[i]+4);
            int ntoks=0;

            if(NULL!=acpy && (ntoks=tokenize(acpy,toks,",",MMEAS_CYC_TOKS))>0){
                // comma-separated args, order matters (RST first)
                // RTn or name  - record type ID (decimal, hex, name)
                // RMn or name  - record measurement ID (decimal, hex, name)
                // RST - reset measurement buffer (invalidates RMID, MMID)

                int i=0;
                for(i=0;i<ntoks;i++){

                    MPRINT(MOD_MEAS,"%s:%d - tok[%d][%s]\r\n",__func__,__LINE__,i,toks[i]);

                    uint16_t test=0;
                    if( (strstr(toks[i],"RT")==toks[i]) &&
                            (sscanf(toks[i],"RT%u",&test)==1 ||
                            sscanf(toks[i],"RTX%X",&test)==1 ||
                            (test=licor_rtid_slookup(toks[i]))!=RTID_INVALID)){
                        op_cfg->rtid=test;
                        licor_mbuf_set_rtid(mbuf_id, op_cfg->rtid);
                        //continue;
                    }else if( (strstr(toks[i],"RM")==toks[i]) &&
                            (sscanf(toks[i],"RM%u",&test)==1 ||
                            sscanf(toks[i],"RMX%X",&test)==1 ||
                            (test=licor_rmid_slookup(toks[i]))!=RMID_INVALID) ){
                        op_cfg->rmid=test;
                        licor_mbuf_set_rmid(mbuf_id, op_cfg->rmid);
                        //continue;
                    }else if(strcmp(toks[i],"RST")==0){
                        mbuf_id=LI_MBUF_CYCLE;
                        // init/reset state memory
                        licor_mbuf_reset(mbuf_id);
                        licor_mbuf_set_rtid(mbuf_id,RTID_ZRECv0);
                        licor_mbuf_set_rmid(mbuf_id, RMID_INVALID);
                        licor_mbuf_set_mmid(mbuf_id, MMID_INVALID);
                        memset(mmeas_usr_profile_table,0,MMEAS_USR_PROFILE_N*sizeof(meas_profile_t));
                        meas_seq=0;
                        op_cfg->rtid=RTID_ZRECv0;
                        op_cfg->rmid=RMID_INVALID;
                        op_cfg->mmid=MMID_INVALID;
                         //continue;
                    }else if(strcmp(toks[i],"START")==0){
                        log_get_seq_number(LOG_CBIN,&cycle_id,true);
                        log_truncate(LOG_CBIN);
                        meas_seq=0;
                    }else if(strcmp(toks[i],"END")==0){
                        cycle_id=CYCLE_ID_INVALID;
                        meas_seq=0;
                    }
                }// while tok
            }
            pfree(acpy);
            MPRINT(MOD_MEAS,"CYC RT[%u] RM[%u]\r\n",op_cfg->rtid,op_cfg->rmid);
            continue;
        }// CYC

        int delay_ms=0;
        if(sscanf(argv[i],"D%d",&delay_ms)==1 && delay_ms>0 ){
            MPRINT(MOD_MEAS,"D[%d]\r\n",delay_ms);
            MX_DELAY(delay_ms);
        } // DELAY

        // set USR[n] measurement configuration
        int16_t idx=-1;
        if(sscanf(argv[i],"USR%"FMT_D16":",&idx)==1 &&
                idx>=0 &&
                idx<MMEAS_USR_PROFILE_N){

            char *toks[MMEAS_USRN_TOKS]={0};
            char *acpy = pstrdup((strstr(argv[i],":")+1));
            int ntoks=0;

            if(NULL!=acpy && (ntoks=tokenize(acpy,toks,",",MMEAS_USRN_TOKS))>0){
                // comma-separated args, any order
                // n,t,p are interdependent
                // all change n_samples,to_msec

                // n<sample_n> (uint16, default: last n option value or 1 if unset)
                // p<licor_period_s> (decimal sec, default: last p option)
                // t<meas_time_s> (decimal sec, default: last t option)
                // b<blank_ms> (uint16, default:MX_BLANK_MSEC)
                // rt<record type ID>
                // rm<record meas ID>
                // mm<measument ID>
                // w<+/->
                meas_profile_t *pprof=usr_cfg+idx;

                // initialize
                memset(pprof,0,sizeof(meas_profile_t));
                pprof->rtid=RTID_INVALID;
                pprof->rmid=RMID_INVALID;
                pprof->mmid=MMID_INVALID;
                pprof->op_id=MMID_INVALID;

                int i=0;
                for(i=0;i<ntoks;i++){

                    MPRINT(MOD_MEAS,"%d - toks[%d][%s]\r\n",__LINE__,i,toks[i]);

                    // initialize using measurement
                    // usually first arg, then override
                    // should always override RT,RM
                    const licor_meas_def_t *meas=licor_meas_slookup(toks[i]);
                    if(NULL!=meas){
                        pprof->mmid=meas->mmid;
                        pprof->meas_n=meas->meas_n;
                        pprof->period_s=meas->period_s;
                        pprof->blank_ms=meas->blank_ms;
                        pprof->cal_wr=meas->cal_wr;
                        pprof->meas_ms=1000UL*pprof->period_s*pprof->meas_n;
                        pprof->to_msec=1200UL*pprof->period_s*pprof->meas_n;

                        pprof->op_id=meas->mmid;
                        // should always override these
                        pprof->rmid=RMID_INVALID;
                        pprof->rtid=RTID_INVALID;
                        continue;
                    }

                    test_i16=-1;
                    bool test_run=false;
                    if(s_parse_profile_opts(toks[i], pprof, &oflags, &handler_fn, &test_run)==0){
                        // run flag ignored
                        // USR context is for defining profiles
                        continue;
                    }

                    // fall through - not recognized
                    EPRINT("invalid arg [%s]\r\n",toks[i]);
                }// for toks
            }// if acpy
            pfree(acpy);

            if(idx>=0){
                OUT_CTTY("USR[%d] profile\r\n",idx);
                meas_profile_show(&usr_cfg[idx],5);
            }

            continue;
        }// USRn

        // Load measurement profile(s), optionally apply overrides and run
        if(strstr(argv[i],"LOAD:")==argv[i] ||
            strstr(argv[i],"TM:")==argv[i]    ){

            char *toks[MMEAS_USRN_TOKS]={0};
            char *acpy = pstrdup((strstr(argv[i],":")+1));
            int ntoks=0;

            // may not want to auto-run, e.g. to load and inspect
            // but parsing is same - so two variants.
            bool auto_run=(strstr(argv[i],"TM:")==argv[i]);
            bool run_flag=false;

            if(NULL!=acpy && (ntoks=tokenize(acpy,toks,",",MMEAS_RUN_TOKS))>0){

                // parse sub-options
                // invalidate
                meas_profile_t *pprof=NULL;

                int i=0;
                for(i=0;i<ntoks;i++){

                    MPRINT(MOD_ACT,"toks[%d][%s]\r\n",i,toks[i]);

                    int16_t idx=-1;

                    if(sscanf(toks[i],"USR%"FMT_D16",",&idx)==1 &&
                            idx>=0 &&
                            idx<MMEAS_USR_PROFILE_N){

                        if(auto_run && run_flag && NULL!=pprof){
                            // this is a new profile
                            // run any pending profile
                            if(MMOD_IS_EN(MOD_MEAS)){
                                OUT_CTTY("(auto) RUN profile\r\n");
                                meas_profile_show(pprof,5);
                            }
                            s_run_meas_profile(op_cfg, mbuf_id, handler_fn, &cycle_id, &meas_seq, oflags);
                        }
                        run_flag=true;

                       // copy usr[n] configuration into op_cfg
                        memcpy(op_cfg,(usr_cfg+idx),sizeof(meas_profile_t));
                        // set local config pointer
                        pprof=op_cfg;
                         continue;
                    }// uSRn

                    const licor_meas_def_t *meas=licor_meas_slookup(toks[i]);
                    if(NULL!=meas){

                        if(auto_run && run_flag && NULL!=pprof){
                            // this is a new profile
                            // run any pending profile
                            if(MMOD_IS_EN(MOD_MEAS)){
                                OUT_CTTY("(auto) RUN profile\r\n");
                                meas_profile_show(pprof,5);
                            }
                            s_run_meas_profile(op_cfg, mbuf_id, handler_fn, &cycle_id, &meas_seq, oflags);
                        }
                        run_flag=true;

                        op_cfg->mmid=meas->mmid;
                        op_cfg->meas_n=meas->meas_n;
                        op_cfg->period_s=meas->period_s;
                        op_cfg->blank_ms=meas->blank_ms;
                        op_cfg->cal_wr=meas->cal_wr;
                        op_cfg->meas_ms=1000UL*meas->period_s*meas->meas_n;
                        op_cfg->to_msec=1200UL*meas->period_s*meas->meas_n;
                        // op_id is true MMID (used to identify measurement
                        // as scal,zcal, e.g.). mmid field may be overridden
                        // and set to anything (it is what is logged)
                        op_cfg->op_id=meas->mmid;

                        pprof=op_cfg;
                        continue;
                    }// meas preset

                    if(NULL!=pprof){
                         test_i16=-1;

                        bool test_run=false;
                        if(s_parse_profile_opts(toks[i], pprof, &oflags, &handler_fn, &test_run)==0){

                            if(test_run){
                                if(MMOD_IS_EN(MOD_MEAS)){
                                   OUT_CTTY("RUN profile\r\n");
                                   meas_profile_show(pprof,5);
                                }
                                s_run_meas_profile(op_cfg, mbuf_id, handler_fn, &cycle_id, &meas_seq, oflags);
                                // clear the run flag so it doesn't run twice
                                run_flag=false;
                            }
                            continue;
                        }

                    }else{
                       // NULL profile
                        EPRINT("NULL config [%s] (profile expected before options)\r\n",toks[i]);
                        break;
                    }
                }// while tok

                if(auto_run && run_flag && NULL!=pprof){
                    // run any pending profile
                    // (e.g. only one specified)
                    if(MMOD_IS_EN(MOD_MEAS)){
                        OUT_CTTY("(auto) RUN profile\r\n");
                        meas_profile_show(pprof,5);
                    }
                    s_run_meas_profile(op_cfg, mbuf_id, handler_fn, &cycle_id, &meas_seq, oflags);
                }

                // release acpy (NULL OK)
                pfree(acpy);

            }// if acpy
            continue;
        }// MEAS

        char src_c='\0';
        int16_t omeas_idx=-1;
        if(sscanf(argv[i],"OUT%d",&omeas_idx)==1 ||
           sscanf(argv[i],"OUT%c",&src_c)==1 ){

            test_oflags=QOF_NONE;

            // parse source
            switch(src_c){
                case 'C':
                     test_oflags|=QOF_RECORD;
                break;
                case 'M':
                     test_oflags|=QOF_MLAST;
                break;
                default:
                    if(omeas_idx>=0)
                     test_oflags|=QOF_MIDX;
                break;
            }

            // only update if valid specifier
            if(test_oflags!=QOF_NONE){
                oflags=test_oflags;
                char *px = strstr(argv[i],":")+1;
                char *acpy = pstrdup(px);
                if(NULL!=acpy){
                    // parse OUT options
                    s_parse_out_opts(acpy, &oflags);
                    pfree(acpy);
                }// IF acpy
            }// IF test_oflags

            // use console if no device specified
            if( (oflags&QOF_DEV_FLAGS)==0){
                oflags|=QOF_CONSOLE;
            }
            if( (oflags&QOF_CONTENT_FLAGS)==0){
                oflags|=QOF_HEADER;
            }
            // set default format
            // if output to console and unspecified
            if( ((oflags&QOF_CONSOLE)!=0 || (oflags&QOF_HOST)!=0 ) ){
                if( (oflags&QOF_FMT_FLAGS)==0){
                    oflags|=QOF_PRETTY;
                }
            }

            VLPRINT(2,"DEV[%08"FMT_FLAG"]\r\n",(oflags&QOF_DEV_FLAGS));
            VLPRINT(2,"FMT[%08"FMT_FLAG"]\r\n",(oflags&QOF_FMT_FLAGS));
            VLPRINT(2,"SRC[%08"FMT_FLAG"]\r\n",(oflags&QOF_SRC_FLAGS));
            VLPRINT(2,"CON[%08"FMT_FLAG"]\r\n",(oflags&QOF_CONTENT_FLAGS));

            if( (oflags&QOF_MLAST)!=0){
                omeas_idx=licor_mbuf_mcount(mbuf_id)-1;
            }

            omeas_idx=( (omeas_idx>=0) && (omeas_idx<licor_mbuf_depth(mbuf_id)) ? omeas_idx : 0);
            VLPRINT(2,"oflags[%08"FMT_FLAG"]\r\n",oflags);
            if(oflags!=0){
                licor_mmeas_out(mbuf_id, omeas_idx, oflags);
            }
            continue;
        }// OUT

        // STAT, SHOW
        if(strstr(argv[i],"SHOW")==argv[i]||strstr(argv[i],"STAT")==argv[i]){

            char *opt_str=strstr(argv[i],":");
             if(NULL!=opt_str){
                opt_str++;
            }else{
                // default to ALL
                opt_str="A";
            }

            bool opt_all=(NULL!=strchr(opt_str,'A')|| NULL!=strchr(opt_str,'*'));
            char *popt=opt_str;
            while(*popt!='\0'){
                if(opt_all || *popt=='H' ){
                    OUT_CTTY("mmeas stat:*ABCHOPUS\r\n");
                    OUT_CTTY(" A,* - all\r\n");
                    OUT_CTTY("   B - meas buffer\r\n");
                    OUT_CTTY("   C - meas cycle record\r\n");
                    OUT_CTTY("   H - help\r\n");
                    OUT_CTTY("   O - op profile\r\n");
                    OUT_CTTY("   P - preset profiles\r\n");
                    OUT_CTTY("   U - user profiles\r\n");
                    OUT_CTTY("   S - state info\r\n");
                }

                if(opt_all || *popt=='B' ){
                    OUT_CTTY("--- Measurement Buffer ---\r\n");
                    // show record header twice if R option enabled
                    licor_mbuf_show(licor_mbuf_instance(mbuf_id), !(opt_all || NULL!=strchr(opt_str,'C')) , 5);
                }

                if(opt_all || *popt=='C' ){
                    OUT_CTTY("--- Measurement Cycle Record ---\r\n");
                    licor_zrecord_show(licor_mbuf_record(mbuf_id), true, 5);
                }

                if(opt_all || *popt=='O' ){
                    OUT_CTTY("--- OP Profile ---\r\n");
                    meas_profile_show(op_cfg,5);
                }

                if(opt_all || *popt=='S' ){
                    OUT_CTTY("--- State Info ---\r\n");
                    int wkey=12;
                    int wval=20;
                    int indent=5;
                    OUT_CTTY("%*s%*s %*s\r\n",indent,"",wkey,"handler",wval,(handler_fn==licor_handler_z?"licor_handler_z":"licor_handler_o"));
                    OUT_CTTY("%*s%*s %*"FMT_U32"\r\n",indent,"",wkey,"cycle_id",wval,cycle_id);
                    OUT_CTTY("%*s%*s %*"FMT_U32"\r\n",indent,"",wkey,"meas_seq",wval,meas_seq);
                    uint32_t next_seq=-1;
                    log_get_seq_number(LOG_CBIN,&next_seq,false);
                    OUT_CTTY("%*s%*s %*"FMT_U32"\r\n",indent,"",wkey,"next cycle",wval,next_seq);
                    log_get_seq_number(LOG_ZBIN,&next_seq,false);
                    OUT_CTTY("%*s%*s %*"FMT_U32"\r\n",indent,"",wkey,"next zrecord",wval,next_seq);
                    log_get_seq_number(LOG_OBIN,&next_seq,false);
                    OUT_CTTY("%*s%*s %*"FMT_U32"\r\n",indent,"",wkey,"next orecord",wval,next_seq);
                    OUT_CTTY("%*s%*s %*s%08lX\r\n",indent,"",wkey,"oflags [1]",wval-8,"x",prev_oflags);
                    OUT_CTTY("%*s%-*s %*s\r\n",indent,"",wkey,"[1]",wval,"previous directive, not sticky");
                }

                if(opt_all || *popt=='U' ){
                    OUT_CTTY("--- USR Profiles ---\r\n");
                    int i=0;
                    for(i=0;i<MMEAS_USR_PROFILE_N;i++){
                        OUT_CTTY("USR[%d]\r\n",i);
                        meas_profile_show(&usr_cfg[i],5);
                    }
                }

                if(opt_all || *popt=='P' ){
                    OUT_CTTY("--- Preset Profiles ---\r\n");
                    for(i=1;i<MMID_COUNT;i++){
                        const licor_meas_def_t *next = licor_meas_ilookup(i);
                        licor_meas_def_show(next,5);
                        OUT_CTTY("\r\n");
                    }
                }

                if(opt_all)break;
                popt++;
            }

           continue;
        }// SHOW
    } // for arg

    return retval;
}

/// COMMAND:<b>  print fmt [var...]  </b>@n
/// print formatted debug info to console/host
/// - supports escape sequences (\r\n\b\f etc.)
/// - case insensitive
return_code_t cli_print(int argc, char **argv)
{
    return_code_t retval=ERR_SUCCESS;

     // expects fmt var var...
    // where var is string or variable, w/o '$' prefix
    // e.g. " foo %ld  bar %3.2e" CX0 DX2
    char *pval=NULL;
    int i=1;
    if(argc>=1){
        char *cp=NULL,*ep=( argv[0]+strlen(argv[0]) );
        char *fp[2]={NULL,NULL};
        char *fmt=NULL;

        // convert escape sequences
        cp=ui_unescape(argv[0]);
        ep=(cp+strlen(cp));

        while(cp<ep && i<=argc){
            if(*cp=='%'){
                // mark start/end of format
                fp[0]=cp;
                fp[1]=cp+1;
                while(!isspace(*(fp[1]+1)) && *(fp[1]+1)!='\0' && !ISQUOTE(*(fp[1]+1)))fp[1]++;

                // get format string (must release using pfree)
                 if( (fmt = pstralloc(fp[0],fp[1]))!=NULL){
                     char *xp=fmt;
                     while(*xp!='\0'){
                         switch(*xp){
                             case 'x':
                             case 'X':
                             case 'g':
                             case 'G':
                             case 'e':
                             case 'E':
                                 // preserve (upper) case
                                 break;
                             default:
                                 // otherwise, format specifiers are lower case
                                 *xp=tolower(*xp);
                                 break;
                         }

                         xp++;
                     }

                     if(strcmp(fmt,"%%")==0){
                         serPrintf(SER_CTTY,"%%");
                     }else{
                        // get string value of variable, formatted with format string
                        // var is at argv[i] p
                        // pval points to var value string
                        pval=NULL;
                        if(config_str_val(argv[i], &pval, fmt)==ERR_SUCCESS && NULL!=pval){
                            // print var
                            serPrintf(SER_CTTY,"%s",pval);
                            i++;
                        }
                    }
                    // advance pointer past fmt
                    cp=fp[1];
                    // release format string buffer
                    pfree(fmt);
                 }

            }else{
                OUT_CTTY("%c",*cp);
            }
            cp++;
        }

    }else{
        OUT_ERR("ERR - invalid arguments\r\n");
        retval=ERR_FAILURE;
    }

    return retval;
}
return_code_t cli_println(int argc, char **argv)
{
    return_code_t retval=cli_print(argc,argv);
    OUT_CTTY("\r\n");
    return retval;
}
/// COMMAND: VER
/// show version info
return_code_t cli_version(int argc, char **argv)
{
    return_code_t retval=ERR_SUCCESS;

    sys_banner();
    return retval;
}

/// 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 cli_uptime(int argc, char **argv)
{
    TickCount_t up_ticks=tkGetTicks();
    uint64_t up_msec = up_ticks*MS_PER_TICK;
    uint64_t up_sec = up_msec/1000LL;
    uint64_t up_ms = up_msec%1000LL;
    OUT_CTTY("%02llu:%02llu:%02llu.%03llu\r\n", (up_sec/3600),
              ((up_sec%3600)/60), (up_sec%60),up_ms);
    serTxWait(SER_CTTY);
//    uint32_t utime=tkGetUptime();
//    OUT_CTTY(" %02lu:%02lu:%02lu\r\n", (utime/3600),
//              ((utime%3600)/60), (utime%60));
    return ERR_SUCCESS;
}
#else
return_code_t cli_uptime(int argc, char **argv)
{
    return ERR_NO_IMPL;
}
#endif

// wrapper for frename (ren)
// causes variable substitution to be performed
// before calling frename.
return_code_t cli_ren(int argc, char **argv)
{
    return frename(argc,argv);
}

/// COMMAND:<b> RESET   </b>@n
/// Reboot the system
return_code_t cli_reset(int argc, char **argv)
{
    asm("reset");
    return ERR_SUCCESS;
}

/// COMMAND:<b>  HELLO  </b>@n
/// a test function for what ever I need it to do
return_code_t cli_hello(int argc, char **argv)
{
    MPRINT(MOD_ACT,"hello[%d]\r\n",argc);
    //    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);
//    OUT_CTTY("hello");
    int i=0;
    for(i=0;i<argc;i++){
        OUT_CTTY(" %s",argv[i]);
    }
    OUT_CTTY("\r\n");

    serTxWait(g_ctty);
    return ERR_SUCCESS;
}

return_code_t cli_sweep(int argc, char **argv)
{
    int i=0;
    uint8_t delay=10;
    sscanf(argv[0],"%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);
}

/// COMMAND:<b> GET AD <port>[:DXn]  [<port>[:DXn]...]</b>@n
/// port : ADC 0:1
/// DXn  : store result in DXn n=0-CFG_DX_COUNT-1 (optional)

/// Power on an analog port and read voltage
return_code_t cli_get_analog(int argc, char **argv)
{
    return_code_t retval=ERR_INVALID;

    if(argc>=1){
        int port=-1;
        int pbit=-1;
        double volts;
        int i=0;
        char *op=NULL;
        for(i=0;i<argc;i++){

            pbit=-1;
            port=-1;
            port = atoi(argv[i]);
            op=strstr(argv[i],"DX");
            switch(port){
                case 0:
                pbit=ADC0;
                break;
                case 1:
                pbit=ADC1;
                break;
                case 2:
                pbit=ADC2;
                break;
                default:
                break;
            }

             if(pbit>=0){
                doSetBit(pbit, HI);

                tkDelayMs(500);

                volts = adVolts(port, 10);

                OUT_CTTY("ADC%d : %7.4f V %s\r\n", port, volts,(op==NULL?"":op));

                // if specified, write value to DX var
                if(NULL!=op){
                    if( config_set(op,&volts)==ERR_SUCCESS ){
                        retval = ERR_SUCCESS;
                    }
                }else{
                    retval = ERR_SUCCESS;
                }

                doSetBit(pbit, LO);
            }// else invalid port/power bit

        }// for argc
    }// else invalid args

    return retval;
}

/// COMMMAND:<b>  GET BITS [CXn]   </b>@n
/// CXn  : store result in CXn n=0-CFG_CX_COUNT-1 (optional)
/// Display state of IO-bits and Inst.Ports.
return_code_t cli_get_bits(int argc, char **argv)
{
    int i = 0;
    long unsigned bits=0;
    int b[2]={0};
    OUT_CTTY(" bit     SVn        (A/B)\r\n");
    for (i = 0; i < SV_COUNT; i++) {
        OUT_CTTY(" [%2d/%2d] SV%d        %d/%d\r\n",
                  2*i,2*i+1, i , b[0]=doGetBit(2*i),b[1]=doGetBit(2*i+1));
        bits |= b[0] << (2*i);
        bits |= b[1] << (2*i+1);
    }
    OUT_CTTY(" [%5d] Pump0 DC   %d\r\n",PUMP0_DC, b[0]=doGetBit(PUMP0_DC));
    bits |= (uint32_t) b[0] << PUMP0_DC ;
    OUT_CTTY(" [%5d] Pump1 DC   %d\r\n",PUMP1_DC, b[0]=doGetBit(PUMP1_DC));
    bits |= (uint32_t) b[0] << PUMP1_DC;
    OUT_CTTY(" [%5d] Pump2 DC   %d\r\n",PUMP2_DC, b[0]=doGetBit(PUMP2_DC));
    bits |= (uint32_t) b[0] << PUMP2_DC;
    OUT_CTTY(" [%5d] Pump3 DC   %d\r\n",PUMP3_DC, b[0]=doGetBit(PUMP3_DC));
    bits |= (uint32_t) b[0] << PUMP3_DC;
    OUT_CTTY(" [%5d] Licor DC   %d\r\n",LICOR_DC, b[0]=doGetBit(LICOR_DC));
    bits |= (uint32_t) b[0] << LICOR_DC;
    OUT_CTTY(" [%5d] ADC0       %d\r\n",ADC0, b[0]=doGetBit(ADC0));
    bits |= (uint32_t) b[0] << ADC0;
    OUT_CTTY(" [%5d] ADC1       %d\r\n",ADC1, b[0]=doGetBit(ADC1));
    bits |= (uint32_t) b[0] << ADC1;
    // ADC2 (VBAT) does not have a power enable bit
    OUT_CTTY(" [%5d] ADC2       %d\r\n",ADC2, 0);
    bits |= (uint32_t) 0 << ADC2;
    OUT_CTTY(" [%5d] HOST  XCVR %d\r\n",XCVR_HOST, b[0]=doGetBit(XCVR_HOST));
    bits |= (uint32_t) b[0] << XCVR_HOST;
    OUT_CTTY(" [%5d] LICOR XCVR %d\r\n",XCVR_LICOR, b[0]=doGetBit(XCVR_LICOR));
    bits |= (uint32_t) b[0] << XCVR_LICOR;
    OUT_CTTY(" [%5d] CONS  XCVR %d\r\n",XCVR_CONS, b[0]=doGetBit(XCVR_CONS));
    bits |= (uint32_t) b[0] << XCVR_CONS;

    if(argc>0){
        // optionally set CXn, if specified
        config_set(argv[0],(uint32_t *)&bits);
    }
    return ERR_SUCCESS;
}

//    use    : test bits [Q] [ MCXn BCXn OCXn ]  [M<mask>...] [<id>...]
//    brief  : test one or more IO bits.
//    input  : Q      - quiet mode (no console output, for scripting)
//    input  : BCXn   - store bits in CXn
//    input  : MCXn   - store mask in CXn
//    input  : OCXn   - store output (mask&bits) in CXn
//    input  : Mmask  - add <mask> (hex) to bitmask [1]
//    input  : id     - bit number or name (see SET BITS) [1]
return_code_t cli_test_bits(int argc, char **argv)
{
    int i = 0;
    long unsigned bits=0;
    for (i = 0; i < SV_COUNT; i++) {
        bits |= doGetBit(2*i) << (2*i);
        bits |= doGetBit(2*i+1) << (2*i+1);
    }
    bits |= (uint32_t) doGetBit(PUMP0_DC) << PUMP0_DC;
    bits |= (uint32_t) doGetBit(PUMP1_DC) << PUMP1_DC;
    bits |= (uint32_t) doGetBit(PUMP2_DC) << PUMP2_DC;
    bits |= (uint32_t) doGetBit(PUMP3_DC) << PUMP3_DC;
    bits |= (uint32_t) doGetBit(LICOR_DC) << LICOR_DC;
    bits |= (uint32_t) doGetBit(ADC0) << ADC0;
    bits |= (uint32_t) doGetBit(ADC1) << ADC1;
    // ADC2 (VBAT) does not have a power enable bit
    bits |= (uint32_t) 0 << ADC2;
    bits |= (uint32_t) doGetBit(XCVR_HOST) << XCVR_HOST;
    bits |= (uint32_t) doGetBit(XCVR_LICOR) << XCVR_LICOR;
    bits |= (uint32_t) doGetBit(XCVR_CONS) << XCVR_CONS;

    unsigned long mask=0L, mtest=0L;
    char *bits_out=NULL;
    char *mask_out=NULL;
    char *val_out=NULL;
    bool quiet=false;
    for(i=0;i<argc;i++){
        mtest=0L;
        if(strstr(argv[i],"MCX")==argv[i]){
            mask_out=argv[i]+1;
        }else if(strstr(argv[i],"BCX")==argv[i]){
            bits_out=argv[i]+1;
       }else if(strstr(argv[i],"OCX")==argv[i]){
            val_out=argv[i]+1;
        }else if(argv[i][0]=='Q'){
            quiet=true;
        }else if(sscanf(argv[i],"M%lX",&mtest)==1){
            mask|=mtest;
        }else{
            if(dio_parse_bits(argv[i],&mtest)==ERR_SUCCESS)
                mask|=mtest;
        }
    }

    if(!quiet){
       OUT_CTTY("bits[%08lX] mask[%08lX] m&b[%08lX] bout[%s] mout[%s] vout[%s]\r\n",
               bits,mask,(mask&bits),bits_out,mask_out,val_out);
   }
    if(NULL!=bits_out){
        config_set(bits_out,(uint32_t *)&bits);
    }
   if(NULL!=mask_out){
        config_set(mask_out,(uint32_t *)&mask);
    }
   if(NULL!=val_out){
        uint32_t val=bits;
        if(mask!=0L){
            val &= mask;
        }
        config_set(val_out,(uint32_t *)&val);
    }

    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 cli_get_data(int argc, char **argv) {
    tlmOutput(SER_HOST);
    tlmOutput(SER_CONS);
    return(ERR_SUCCESS);
}
#else
return_code_t cli_get_Data(int argc, char **argv)
{
    OUT_CTTY("%s - not implemented\r\n",__FUNCTION__);
    return ERR_NO_IMPL;
}
#endif

/// use : GET ERR [CX<n>...]]
/// show return value of last command
/// optionally store in one or more CX registers
return_code_t cli_get_err(int argc, char **argv)
{
    int i=0;
    uint32_t stat=g_cmd_status;
    bool quiet=false;
    for(i=0;i<argc;i++){
        if(strstr(argv[i],"CX")!=NULL){
            config_set(argv[i],&stat);
        }else if(strcmp(argv[i],"Q")==0){
            quiet=true;
        }
    }
    if(!quiet){
        OUT_CTTY("%d\r\n",g_cmd_status);
    }
    return ERR_SUCCESS;
}

/// COMMAND:<b>  GET NEXT   </b>@n
/// Display time until next task.
return_code_t cli_get_next_event(int argc, char **argv)
{
    double t_next=(sys_next_event()/1000.0);
    OUT_CTTY("%.2f s\r\n",t_next);
    return ERR_SUCCESS;
}

/// 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 cli_get_ser(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;

    int32_t max_bytes=0;
    int port = SER_INVALID;
    int test=0;
    int i=0;
    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"N%ld",&max_bytes)==1){
            // max bytes set
        }else if(strcmp(argv[i],"H")==0){
            port=SER_HOST;
        }else if(strcmp(argv[i],"L")==0){
            port=SER_LICOR;
        }else if(strcmp(argv[i],"C")==0){
            port=SER_CONS;
        }else if(sscanf(argv[i],"%d",&test)==1){
            port=test;
        }
    }
    char *ip=g_msg_buffer;
    if(port>=0 && port<UART_COUNT){
    OUT_CTTY("port[%d] max[%ld]{\r\n", port,max_bytes);
        int32_t rbytes=0;
        bool quit=false;
        while(!quit){

            if( (ip-g_msg_buffer)>=(SER_PRINTF_BUFFER_BYTES-1) ){
                // buffer full, empty it
                OUT_CTTY("%s",g_msg_buffer);
                ip=g_msg_buffer;
             }

            if(serGetByte(port,(unsigned char *)ip)){
                rbytes++;
                if(max_bytes<0 && (*ip==CTRL_D)){
                    // EOT in stream (max_bytes<0))
                    quit=true;
                }
               ip++;
                *ip='\0';
           }else if(max_bytes==0){
                // no data (max_bytes==0)
                quit=true;
            }
            if(max_bytes>0 && rbytes>=max_bytes){
                // max bytes received (max_bytes>0))
                quit=true;
            }
            // user interrupt (CTTY)
            int brk=iow_get_char(false);
            if(brk==CTRL_D)quit=true;

        }
    }
    // output any buffered bytes
    if(ip>g_msg_buffer)
        OUT_CTTY("%s",g_msg_buffer);
    OUT_CTTY("}\r\n");
    serTxWait(SER_CTTY);

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

    return retval;
}

/// _get_ COMMANDS_**********************************
/// COMMAND:<b>  GET TIME (u)   </b>@n
/// Display time from RTC, 'u' option prints time_t too.
return_code_t cli_get_time(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    // TODO: don't use g_msg_buffer

    Time now={0};
//    rtcRead(&now);
    time_t tt=rtc_mktime(&now);
    if (argc>0 && (*argv[0] == 'u' || *argv[0] == 'U')) {
//        OUT_CTTY("%lu ", (UINT32) mktime(&now));
        OUT_CTTY("%lu ", (UINT32) tt);
    }
    memset(g_msg_buffer,0,SER_PRINTF_BUFFER_BYTES);
    rtcPrint(g_msg_buffer, &now);
    OUT_CTTY("%s\r\n",g_msg_buffer);
    serTxWait(SER_CTTY);
    retval=ERR_SUCCESS;

    return retval;
}

/// 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 cli_get_var(int argc, char **argv)
{

    int i=0;

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

}

/// COMMAND:<b>  SET BAUD [0|H|1|L|2|C][new baud/100]     </b>@n
/// New speed must be 300*2^n for n=[0,7]
return_code_t cli_set_baud(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    int port;
    UINT16 newBaud;
    if(argc>1){
        switch(*argv[0]) {
        case '0':
        case 'H':
        port = SER_HOST;
        break;
        case '1':
        case 'L':
        port = SER_LICOR;
        break;
        case'2':
        case 'C':
        port = SER_CONS;
        break;
        default:
        return(ERR_FAILURE);
        }
            newBaud = atoi(argv[1]);
    switch (newBaud){
        case 300:
        case 600:
        case 1200:
        case 2400:
        case 4800:
        case 9600:
        case 19200:
        case 38400:
        break;
        default:
        return(ERR_FAILURE);
    }
    retval= (serSetBaud(port, newBaud));

    }

    return retval;
}

/// 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 cli_set_bits(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    DSHOW_TOKS(argc,argv);
    if(argc>0 && NULL!=argv){
        unsigned long int bits=0;
        int i=0;
        for(i=0;i<argc;i++){
            retval=dio_parse_bits(argv[i],&bits);
            if(retval==ERR_FAILURE){
            	break;
            }
        }
        if(retval==ERR_SUCCESS){
            doSetBits(bits,HI);
        }
    }
    return retval;
}

/// COMMAND:<b>  SET SER [0|H|1|L|2|C][string]    </b>@n
/// Send string and end line to serial port
return_code_t cli_set_ser(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    int port=-1;
    if(argc>=2 && NULL!=argv){
        switch(*argv[0]){
            case '0':
            case 'H':
            port = SER_HOST;
            break;
            case '1':
            case 'L':
            port = SER_LICOR;
            break;
            case'2':
            case'C':
            port = SER_CONS;
            break;
            default:
            break;
        }

        if(port>=0){

            char *cur[2]={0};
            int i=0;
            for(i=1;i<argc;i++){
                cur[0]=argv[i];
                if(*cur[0]=='\"'){
                    cur[1]=cur[0]+1;
                    cur[0]++;
                    while(*cur[1]!='\0'){
                        if(*cur[1]=='\"' && *(cur[1]-1)!='\\'){
                            *cur[1]='\0';
                            break;
                        }
                        cur[1]++;
                    }
                    if(*cur[1]=='\"')*cur[1]='\0';
                }else if(argv[i][0]=='\''){
                    cur[1]=cur[0]+1;
                    cur[0]++;
                    while(*cur[1]!='\0'){
                        if(*cur[1]=='\"' && *(cur[1]-1)!='\\'){
                            *cur[1]='\0';
                            break;
                        }
                        cur[1]++;
                    }
                    if(*cur[1]=='\"')*cur[1]='\0';
                }
                serPrintf(port,"%s ",cur[0]);
            }
            serPutString(port,"\r\n");
            retval=ERR_SUCCESS;
        }
    }

    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 cli_set_time(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    Time now;

    if(argc>=6 && NULL!=argv){
        int i=0;
        if(sscanf(argv[i++],"%d",&now.tm_year)==1 &&
        sscanf(argv[i++],"%d",&now.tm_mon)==1 &&
        sscanf(argv[i++],"%d",&now.tm_mday)==1 &&
        sscanf(argv[i++],"%d",&now.tm_hour)==1 &&
        sscanf(argv[i++],"%d",&now.tm_min)==1 &&
        sscanf(argv[i++],"%d",&now.tm_sec)==1 ){
            // adjust month (zero-indexed)
            now.tm_mon--;

            // adjust year
            if (now.tm_year < 100) {
                // entered YY
                // it's not the 1900s
                now.tm_year += 100;
            } else {
                // entered YYYY
                 //fix year offset
                now.tm_year -= 1900;
            }

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

            OUT_CTTY("new time is:");
            rtcRead(&now);
            memset(g_msg_buffer,0,SER_PRINTF_BUFFER_BYTES);
            rtcPrint(g_msg_buffer, &now);
            OUT_CTTY("%s\r\n",g_msg_buffer);
            retval=ERR_SUCCESS;
        }else{
            EMSG("bad date/time, not setting clock\r\n");
        }
    }
    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 cli_set_var(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;

    if(NULL!=argv && argc>=2){
        retval=config_parse(argv[0],argv[1]);
    }
    return retval;
}

/// COMMAND:<b>  CLR BITS [bits...]     </b>@n
/// Clear one or more IO-bits (set LO).
/// 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 cli_clr_bits(int argc, char **argv) {
///
    return_code_t retval=ERR_FAILURE;

    if(argc>0 && NULL!=argv){
        unsigned long int bits=0;
        int i=0;
        for(i=0;i<argc;i++){
            retval=dio_parse_bits(argv[i],&bits);
            if(retval==ERR_FAILURE){
                break;
            }
        }
        if(retval==ERR_SUCCESS){
            doSetBits(bits,LO);
        }
    }
    return retval;
}

return_code_t cli_pulse_bits(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    DSHOW_TOKS(argc,argv);

    if(argc>=3){
        // wrapper for pulse_nbits
        char *av[argc+2];
        int i=0;
        char *dstr=NULL;
        int32_t dval=-1;

        if(sscanf(argv[1],"X%lX",&dval)==1){
            dstr=(char *)palloc(16);
            sprintf(dstr,"%ld",dval);
        }

        av[0]=argv[0];
        av[1]=(NULL!=dstr ? dstr : argv[1]);
        av[2]="0";
        av[3]="1";
        for(i=2;i<argc;i++){
            av[4+(i-2)]=argv[i];
        }
        retval = cli_pulse_nbits(argc+2,av);
        pfree(dstr);
    }

    return(retval);
}

/// dir ta tb n
return_code_t cli_pulse_nbits(int argc, char **argv)
{
    return_code_t retval=ERR_FAILURE;
    DSHOW_TOKS(argc,argv);

    unsigned long int bits=0;
    short unsigned int delay[2]={0};
    int cycles=0;
    char state='?';
    char pulse[2]={0};

    if(argc>=4){
        if(sscanf(argv[0],"%c",&state)==1 &&
                sscanf(argv[1],"%hu",&delay[0])==1 &&
                sscanf(argv[2],"%hu",&delay[1])==1 &&
                sscanf(argv[3],"%d",&cycles)==1 && cycles>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=4;i<argc;i++){
                	dio_parse_bits(argv[i],&bits);
                }
                MPRINT(MOD_ACT,"dir[%c] del[%hu,%hu] n[%d] bits[x%08lX]\r\n",state,delay[0],delay[1],cycles,bits);
                for(i=0;i<cycles;i++){
                    doSetBits(bits,pulse[0]);
                    if(delay[0]>0)tkDelayMs(delay[0]);
                    doSetBits(bits,pulse[1]);
                    if(delay[1]>0)tkDelayMs(delay[1]);
                }
            }else{
                retval=ERR_FAILURE;
            }
        }
    }

    return(retval);
}
/// initialize Licor
/// args : none
return_code_t cli_licor_init(int argc, char **argv)
{
    return_code_t retval = ERR_NO_IMPL;
    MPRINT(MOD_ACT,"LI.INIT[%s]\r\n",argv[0]);
    retval=licor_initialize(NULL);
    return retval;
}

 ///   li cal <id> [D<delay>]
 ///   brief  : perform calibration
 ///   input  : id    - cal ID, id values include
 ///            z         zero
 ///            s:d.ddd   span
 ///            s2:d.ddd  span2
 ///   input  : delay - extend delay (in addition to 10s max implemented)
return_code_t cli_licor_cal(int argc, char **argv)
{
    return_code_t retval = ERR_FAILURE;
    double spanc=0.0;
    unsigned int delay=0;
    licor_cal_id cal_id = LI_CAL_INVALID;

    int i=0;
    for(i=0;i<argc;i++){
        if(strcmp(argv[i],"Z")==0){
            cal_id=LI_CAL_Z;
            continue;
        }
        if(sscanf(argv[i],"S:%lf",&spanc)==1){
            cal_id=LI_CAL_S;
            continue;
        }
       if(sscanf(argv[i],"S2:%lf",&spanc)==1){
            cal_id=LI_CAL_S2;
            continue;
        }
//        if(strcmp(argv[i],"S")==0){
//            cal_id=LI_CAL_S;
//            continue;
//        }
//        if(sscanf(argv[i],"S%d",&stype)==1){
//            cal_id=LI_CAL_S2;
//            continue;
//        }
//        if(sscanf(argv[i],"C%lf",&spanc)==1)continue;
        // may extend delay (LI_CAL_DELAY_MSEC (10s) max implemented in licor_cal )
        if(sscanf(argv[i],"D%u",&delay)==1)continue;
    }
    MPRINT(MOD_ACT,"cal_id[%d] spanc[%.3lf]\r\n",cal_id,spanc);
    switch(cal_id){
        case LI_CAL_INVALID:
                    // error: invalid
                    break;
        case LI_CAL_S:
        case LI_CAL_S2:
            if(spanc==0.0){
               // use default if set
                config_get(CK_LICOR_SPANC,&spanc);
                MPRINT(MOD_ACT,"using LI_SPANC[%.3lf]\r\n",spanc);
            }
            if(spanc==0.0){
                // error: span concentration required
                EMSG("span cal requires concentration - set LICOR_SPANC, S:d.ddd or S2:d.ddd\r\n");
                break;
            }
            // fall through
        case LI_CAL_Z:
        default:
            retval=licor_cal(cal_id,spanc);
            break;
    }

    if(delay>0)tkDelayMs(delay);

    return retval;
}

/// set/get Licor output rate
/// args
/// period : seconds (min 0.5)
return_code_t cli_licor_or(int argc, char **argv)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"LI.RATE[%d]\r\n",argc);
    if(argc>0 && NULL!=argv){
        float period_sec=0;
        if(sscanf(argv[0],"%f",&period_sec)==1){
            if( (retval=licor_set_outrate(period_sec))!=ERR_SUCCESS){
                log_syslog("or set err[%d]",retval);
            }
        }
    }else{
        float or = licor_outrate();
        if(or>=0.0){
            OUT_CTTY(" %.2f\r\n",or);
            retval=ERR_SUCCESS;
        }else{
            log_syslog("or get err[%f]",or);
        }
    }
    return retval;
}

/// set/get Licor model configuratipn
/// args
/// model : LI-COR model (830,850)
return_code_t cli_licor_model(int argc, char **argv)
{
    return_code_t retval = ERR_SUCCESS;
    MPRINT(MOD_ACT,"LI.MODEL[%d]\r\n",argc);
    int i=0;
    int16_t model=0;
    bool verbose=true;
    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"%"FMT_U16"",&model)==1){
            switch(model){
                case LI_MODEL_820:
                case LI_MODEL_830:
                case LI_MODEL_840:
                case LI_MODEL_850:
                    licor_set_model(model);
                    config_set(CK_LICOR_MODEL,&model);
                    break;
                default:
                    retval=ERR_INVALID;
                    break;
            }
        }else if(strcmp(argv[i],"Q")==0){
            verbose=false;
        }
    }
    if(verbose){
        OUT_CTTY("%d\r\n",model=licor_model());
    }
    return retval;
}

/// use: li stat [i<indent>] v]
/// brief: output licor status to console (formatted xml)
/// input : indent - number of space to indent
/// input : v      - verbose output
return_code_t cli_licor_stat(int argc, char **argv)
{
    return_code_t retval = ERR_FAILURE;
    DSHOW_TOKS(argc,argv);
    MPRINT(MOD_ACT,"LI.STATUS[%s]\r\n",argv[0]);
    char xml[2048]={0};
    int rb = 0;
    int indent=5;
    bool verbose=false;
    char *tag = NULL;

    if( (rb=licor_read_status(xml,2048))>0 ){
        if(verbose)OUT_CTTY("licor returned %d bytes\r\n",rb);

        // parse arguments
        int i=0;
        for(i=0;i<argc;i++){
            if(sscanf(argv[i],"I%d",&indent)==1){
                continue;
            }else if(argv[i][0]=='V'){
                verbose = true;
                continue;
            }else{
                // set tag, convert to lowercase
                tag=argv[i];
                char *cp=tag;
                while(*cp!='\0'){
                    *cp=tolower(*cp);
                    cp++;
                }
                // show it
                licor_status_show(xml,tag,true,indent);
                retval = ERR_SUCCESS;

                continue;
            }
        }

        if(NULL==tag){
            // if no tags specified show all
            licor_status_show(xml,tag,true,indent);
            retval = ERR_SUCCESS;
        }
    }else{
        OUT_CTTY("licor_read_status failed (licor may be off) [%d]\r\n",rb);
    }
    return retval;
}

/// use    : scr load [o<+/->] <file> [<file>...[o<+/->]...]
/// brief  : load specified script(s) into next available handle.
/// input  : file - script file name
/// input  : o    - enable/disable overwrite for files that follow (may use more than once)
return_code_t cli_scr_load(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
    int i=0;
    char ovr = '-';
    int ecount=0;
    for(i=0;i<argc;i++){

        if(strcmp(argv[i],"O+")==0){
            ovr='+';
            continue;
        }else if(strcmp(argv[i],"O-")==0){
            ovr='-';
            continue;
        }

        MPRINT(MOD_ACT,"SCR LOAD[%d] ovr[%c]\r\n",argc,ovr);
        if(script_load(argv[i],-1,(ovr=='+' ? true : false))<0){
            ecount++;
            break;
        }
    }
    if(ecount==0){
        retval=ERR_SUCCESS;
    }

    return retval;
}

return_code_t cli_scr_unload(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;

    DSHOW_TOKS(argc,argv);
    MPRINT(MOD_ACT,"hnd[%d] val[%c]\r\n",hnd,(SCR_HRANGE_VALID(hnd)?'Y':'N'));

    int i=0;
    script_hnd_t hx=SCR_HND_INVALID;
    for(i=0;i<argc && retval==ERR_SUCCESS;i++){
        hx=script_parse_hnd(argv[i]);

//        MPRINT(MOD_ACT,"argv[%d][%s] hx[%d]\r\n",i,argv[i],hx);
        if( !(SCR_HRANGE_VALID(hx) && script_unload(hx)==ERR_SUCCESS)){
            retval=ERR_FAILURE;
        }
    }

    return retval;
}

return_code_t cli_scr_reset(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
    if(argc>=1 && NULL!=argv[0]){

        if(!SCR_HRANGE_VALID(hnd)){
           hnd = script_parse_hnd(argv[0]);
        }

        MPRINT(MOD_ACT,"SCR RESET[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
        if(SCR_HRANGE_VALID(hnd)){
        script_reset(hnd);
        retval=ERR_SUCCESS;
        }
    }
    return retval;
}

return_code_t cli_scr_run(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;

    int i=0;
    char *scr= (argc>0 ? argv[argc-1]: NULL);
    serial_port_t otty=SER_CONS;
    for(i=0;i<argc;i++){
        if(strcmp(argv[i],"OUT:H")==0){
            otty=SER_HOST;
        }else if(strcmp(argv[i],"OUT:C")==0){
            otty=SER_CONS;
        }else{
            scr=argv[i];
        }
    }
    if(NULL!=scr){

        if(!SCR_HRANGE_VALID(hnd)){
           hnd = script_parse_hnd(scr);
        }

        MPRINT(MOD_ACT,"SCR RUN[%d] scr[%d/%s] tty[%d]\r\n",argc,hnd,script_name(hnd),g_ctty);
        if(SCR_HRANGE_VALID(hnd)){
            script_clr_mode(hnd,SM_MAN);
            script_set_otty(hnd,otty);
            script_run(hnd);
            retval=ERR_SUCCESS;
        }
    }
    return retval;
}

return_code_t cli_scr_jog(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
    if(argc>0 && NULL!=argv[0]){
        if(!SCR_HRANGE_VALID(hnd)){
           hnd = script_parse_hnd(argv[0]);
        }
        MPRINT(MOD_ACT,"SCR JOG[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
      if(SCR_HRANGE_VALID(hnd)){
        script_set_mode(hnd,SM_MAN);
        script_run(hnd);
        retval=ERR_SUCCESS;
      }
    }
    return retval;
}

return_code_t cli_scr_end(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
    if(argc>0 && NULL!=argv[0]){
       if(!SCR_HRANGE_VALID(hnd)){
           hnd = script_parse_hnd(argv[0]);
        }
   	 	MPRINT(MOD_ACT,"SCR END[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
       if(SCR_HRANGE_VALID(hnd)){
    	script_set_event(hnd,SE_END);
        retval=ERR_SUCCESS;
       }
    }
    return retval;
}

/// schedule script
/// scr sched <period> <sync Y|N> <hnd>
return_code_t cli_scr_schedule(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"SCR SCH[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
    int32_t period=-1;
    int32_t sync_delay=-1;
    script_hnd_t ohnd = SCR_HND_INVALID;
    serial_port_t otty=SER_INVALID;
    bool pflag=false;
    bool hflag=false;
    int i=0;
    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"P%lu",&period)==1){

        }else if(sscanf(argv[i],"D%ld",&sync_delay)==1){

        }else if(strcmp(argv[i],"D")==0){
            sync_delay=0;
        }else if(strcmp(argv[i],"OUT:H")==0){
            otty=SER_HOST;
        }else if(strcmp(argv[i],"OUT:C")==0){
            otty=SER_CONS;
        }else if(!SCR_HRANGE_VALID(hnd)){
            ohnd = script_parse_hnd(argv[i]);
            pflag=true;
        }

        if(pflag && !hflag && SCR_HRANGE_VALID(hnd) && period>=0){
            // reached option hnd, first end of options
            // use options so far to process passed hnd
            // BEFORE option ohnd
            retval=script_set_period(hnd,period,false);
            if(sync_delay>=0){
                retval=script_sync(hnd,(uint32_t)sync_delay);
            }
            if(otty!=SER_INVALID){
                script_set_otty(hnd,otty);
            }
            log_syslog("sch [%d/%s:P%ld,D%ld],TTY%d]",hnd,script_name(hnd),period,sync_delay,otty);
            // mark hnd processed so only done once
            hflag=true;
        }
        if(period>=0 && SCR_HRANGE_VALID(ohnd)){
            // process option ohnd(s))
           retval=script_set_period(ohnd,period,false);
            if(sync_delay>=0){
                retval=script_sync(ohnd,(uint32_t)sync_delay);
            }
            if(otty!=SER_INVALID){
                script_set_otty(hnd,otty);
            }
           log_syslog("sch [%d/%s:P%ld,D%ld,TTY%d]",ohnd,script_name(ohnd),period,sync_delay,otty);
            // invalidate so it's tested next iteration
            ohnd=SCR_HND_INVALID;
        }
    }

    if(!hflag && SCR_HRANGE_VALID(hnd) && period>=0){
        // process passed hnd if no other handle
        retval=script_set_period(hnd,period,false);

        if(sync_delay>=0){
            retval=script_sync(hnd,(uint32_t)sync_delay);
        }
        if(otty!=SER_INVALID){
            script_set_otty(hnd,otty);
        }
        log_syslog("sch [%d/%s:P%ld,D%ld,TTY%d]",hnd,script_name(hnd),period,sync_delay,otty);
        hflag=true;
    }

    return retval;
}

/// kill script (stop, un-schedule, reset)
/// scr kill <id>...
return_code_t cli_scr_kill(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"SCR KILL[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));

    if(SCR_HRANGE_VALID(hnd)){
        // kill passed arg handle first
        if( script_set_period(hnd,0,false)==ERR_SUCCESS &&
                script_reset(hnd)==ERR_SUCCESS ){
            retval=ERR_SUCCESS;
        }
       log_syslog("kill [%d/%s]",hnd,script_name(hnd));
    }
    int i=0;
    for(i=0;i<argc;i++){
        script_hnd_t ohnd=script_parse_hnd(argv[i]);
        if(SCR_HRANGE_VALID(ohnd)){
            // kill option handles (passed on command line)
            if( script_set_period(ohnd,0,false)==ERR_SUCCESS &&
                    script_reset(ohnd)==ERR_SUCCESS ){
                retval=ERR_SUCCESS;
            }
            log_syslog("kill [%d/%s]",ohnd,script_name(ohnd));
       }
    }
    return retval;
}

/// get script state
/// scr stat <id>...
return_code_t cli_scr_stat(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"SCR STAT[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));

    if(SCR_HRANGE_VALID(hnd)){
        // process passed arg handle first
        script_state_t stat=script_state(hnd);
        OUT_CTTY("%"FMT_D16"\r\n",stat);
        retval = ERR_SUCCESS;
    }
    int i=0;
    for(i=0;i<argc;i++){
        script_hnd_t ohnd=script_parse_hnd(argv[i]);
        if(SCR_HRANGE_VALID(ohnd)){
          script_state_t stat=script_state(ohnd);
          OUT_CTTY("%"FMT_D16"\r\n",stat);
          retval = ERR_SUCCESS;
       }
    }
    return retval;
}

/// restart script run timer
/// scr sync <delay> <id>...
return_code_t cli_scr_sync(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"SCR SYNC[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
    int32_t sync_delay=0;//-1;
    script_hnd_t ohnd = SCR_HND_INVALID;
    bool pflag=false;
    bool hflag=false;

    int i=0;
    for(i=0;i<argc;i++){
        if(sscanf(argv[i],"D%ld",&sync_delay)==1){

        }else if(strcmp(argv[i],"D")==0){
            sync_delay=0;
        }else if(!SCR_HRANGE_VALID(hnd)){
            ohnd = script_parse_hnd(argv[i]);
            pflag=true;
        }

        if(pflag && !hflag && SCR_HRANGE_VALID(hnd)  && sync_delay>=0){
            // reached option hnd, first end of options
            // use options so far to process passed hnd
            // BEFORE option ohnd
            retval=script_sync(hnd,(uint32_t)sync_delay);
            log_syslog("sync [%d/%s:D%ld]",hnd,script_name(hnd),sync_delay);
            // mark hnd processed so only done once
            hflag=true;
        }
        if(SCR_HRANGE_VALID(ohnd) && sync_delay>=0){
            // process option ohnd(s))
            retval=script_sync(ohnd,(uint32_t)sync_delay);
            log_syslog("sch [%d/%s:D%ld]",ohnd,script_name(ohnd),sync_delay);
            // invalidate so it's tested next iteration
            ohnd=SCR_HND_INVALID;
        }
    }

    if(!hflag && SCR_HRANGE_VALID(hnd) && sync_delay>=0){
        // process passed hnd if no other handle
        retval=script_sync(hnd,(uint32_t)sync_delay);
        log_syslog("sch [%d/%s:D%ld]",hnd,script_name(hnd),sync_delay);
        hflag=true;
    }

    return retval;
}

return_code_t cli_scr_show(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;
        int i=0;
    if(argc==0 || argv[0][0]=='*'){
        MPRINT(MOD_ACT,"SCR SHOW[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
        for(i=0;i<SCR_SCRIPT_COUNT;i++){
            OUT_CTTY("  SCR[%d]\r\n",i);
            script_show(i,true,5);
            OUT_CTTY("\r\n");
        }
    }else if(argc>0){
        for(i=0;i<argc;i++){
            hnd = script_parse_hnd(argv[i]);

        MPRINT(MOD_ACT,"SCR SHOW[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
        if (SCR_HRANGE_VALID(hnd)){
        script_show(hnd,true,5);
        }
        }
    }

    return retval;
}

return_code_t cli_scr_sdebug(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_INVALID;
    char state=0;
    script_hnd_t use_hnd=SCR_HND_INVALID;
    DSHOW_TOKS(argc,argv);

    if(argc>0 && NULL!=argv){


        switch (argc) {
            case 1:
            // called from script context
            sscanf(argv[0],"%c",&state);
            use_hnd=hnd;
            break;
            case 2:
            // called from CLI context
            sscanf(argv[0],"%c",&state);
            use_hnd = script_parse_hnd(argv[1]);

            break;
            default:
                OUT_ERR("Invalid state[%c]\r\n",argv[0]);
            break;
        }

        if( SCR_HRANGE_VALID(use_hnd) ){
            // if called from state machine
            if(state=='+'){
                MPRINT(MOD_ACT,"SCR DEBUG - set mode\r\n");
                script_set_mode(use_hnd,SM_DEBUG);
                retval=ERR_SUCCESS;
            }else if(state=='-'){
                MPRINT(MOD_ACT,"SCR DEBUG - clr mode\r\n");
                script_clr_mode(use_hnd,SM_DEBUG);
                retval=ERR_SUCCESS;
            }
        }else{
            OUT_ERR("Invalid hnd[%d]\r\n",use_hnd);
        }
        MPRINT(MOD_ACT,"SCR DEBUG[%d] scr[%d/%s] state[%c] uhnd[%d]\r\n",argc,hnd,script_name(use_hnd),state,use_hnd);
    }

    return(retval);
}

/// scr delay <period_ms> [hnd]
/// period_ms delay time (msec); indefinite if <0
/// hnd handle
return_code_t cli_scr_delay(int argc, char **argv, script_hnd_t hnd)
{
    DSHOW_TOKS(argc,argv);
    MPRINT(MOD_ACT,"SCR DELAY[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
    return_code_t retval=ERR_INVALID;
    int32_t delay_ms=0;
    script_hnd_t use_hnd=SCR_HND_INVALID;

    if(argc>0){
       if( sscanf(argv[0],"%ld",&delay_ms)==1 ){
           // delay is valid
         if(SCR_HRANGE_VALID(hnd)){
             if(argc>1){
                 // another script is referenced, use that
                 use_hnd = script_parse_hnd(argv[1]);
                 if(!SCR_HRANGE_VALID(use_hnd)){
                     // if other script invalid, fall back to this script
                     use_hnd=hnd;
                 }

             }else{
                use_hnd=hnd;
             }
         }else{
             if(argc>1){
                 // invoked from command line
                 use_hnd = script_parse_hnd(argv[1]);

             }
         }

        // if delay>0, delay else pause
        if(use_hnd!=SCR_HND_INVALID){
            if(delay_ms==0){
                retval=script_set_event(use_hnd,SE_RESUME);
            }else if(delay_ms>0){
                retval=script_delay(use_hnd,delay_ms);
            }else{
                retval=script_set_event(use_hnd,SE_PAUSE);
            }
        }

       }
    }

    return(retval);
}

/// pause script indefinitely
/// scr pause [hnd]
/// hnd handle
return_code_t cli_scr_pause(int argc, char **argv, script_hnd_t hnd)
{
    if(argc>=1){
    char *xargv[2]={"-1",argv[0]};
    return cli_scr_delay(2,xargv,hnd);
    }
    return ERR_INVALID;
}

/// resume paused script
/// scr resume [hnd]
/// hnd handle
return_code_t cli_scr_resume(int argc, char **argv, script_hnd_t hnd)
{
    if(argc>=1){
    char *xargv[2]={"0",argv[0]};
    return cli_scr_delay(2,xargv,hnd);
    }
    return ERR_INVALID;
}

/// goto script label
/// scr goto label <hnd>
return_code_t cli_scr_goto(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval = ERR_INVALID;
    MPRINT(MOD_ACT,"SCR GOTO[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
    DSHOW_TOKS(argc,argv);

    if(argc>=1 && NULL!=argv){

        // if handle not valid, try to get from args
        // TODO: may only call from CLI, so valid hnd required

        if(!SCR_HRANGE_VALID(hnd) && argc>1){
           hnd = script_parse_hnd(argv[argc-1]);
        }

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

                if(argv[1][0]=='$'){
                    if(config_str_val((argv[1]+1),&pvar,NULL)==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
    }
    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 cli_scr_if(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_FAILURE;
    MPRINT(MOD_ACT,"SCR IF[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
    if(argc>0 && NULL!=argv[0]){

        // TODO : IF statement semantics should quote action string
        // either reassemble the tokens into a line,
        // or change script_parse_boolex to accept token array

//        char *acpy = (char *)ptstrdup(argv[0],true,true);

        // rebuild expression line
        size_t len = 0;
        int i=0;
        // add string length + 1 for space/NULL
        while(i<argc)
            len+=strlen(argv[i++])+1;

        char *acpy = (char *)palloc(len);

        if(NULL!=acpy){
            char *ps=acpy;
            i=0;
            while(i<argc){
                sprintf(ps,"%s ",argv[i++]);
                ps=acpy+strlen(acpy);
            }
//            OUT_CTTY("PARSING IF[%s]\r\n",acpy);
            bool expr=false;
            char *action=strstr(acpy,"]");
           if( script_parse_boolex(acpy,&expr)==ERR_SUCCESS && expr==true){
                if( action!=NULL ){
                    action++;
                    while(isspace(*action) && *action!='\0')
                    action++;
//                    EPRINT("%s - dispatch(%s, %d)",__FUNCTION__,action,hnd);
                    retval = script_parse_line(action,strlen(action),hnd);
                }else{
                    EMSG("NULL action");
                }
            }
            //        else{
            //            EPRINT("boolex false");
            //        }
        }
        pfree(acpy);

    }
    return retval;
}

return_code_t cli_scr_nop(int argc, char **argv, script_hnd_t hnd)
{
    return_code_t retval=ERR_SUCCESS;
    MPRINT(MOD_ACT,"SCR NOP[%d] scr[%d/%s]\r\n",argc,hnd,script_name(hnd));
    return retval;
}
