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

/// Licor 8xx gas analyzer driver for carbon waveglider

/// @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
/////////////////////////

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#include "licor8xx.h"
#include "licor8xx.h"
#include "config.h"
#include "iodigital.h"
#include "ioserial.h"
#include "log.h"
#include "timers.h"
#include "z85.h"
#include "iowfile.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"
 */

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

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

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

// must accommodate largest raw licor data string
#define LI_IBUF_BYTES 400 //512
const char licor_cflag_mnemonics[]="C|CA|H|HD|HA|CT|CP|IV|FR|R|S|E|H|A|P";
const char licor_oflag_mnemonics[]="C|CA|H|HD|HA|CT|CP|IV|FR|RC|RCR|RH|RHR|P|A|N|RT|RX|O|S|TI|TP";

licor_t g_licor_instance={
    &g_licor_config,
    VTYPE_MONO
};
// indexed using licor_cal_id
const char *cal_type_strings[LI_CAL_COUNT]={
   "co2zero",
   "co2span",
   "co2span2",
   "invalid"
};

int mbuf_depth[LI_MBUF_COUNT]=
{
    LI_MBUF_CYCLE_DEPTH,
    LI_MBUF_XFER_DEPTH
};

int mbuf_len[LI_MBUF_COUNT]=
{
    LI_MBUF_CYCLE_BYTES,
    LI_MBUF_XFER_BYTES
};
// allocate buffers so that header and memory are contiguous
byte mbuf_cycle_mem[LI_MBUF_CYCLE_BYTES];
byte mbuf_xfer_mem[LI_MBUF_XFER_BYTES];

z_meas_buffer_t *mbuf_table[LI_MBUF_COUNT]=
{
    (z_meas_buffer_t *)mbuf_cycle_mem,
    (z_meas_buffer_t *)mbuf_xfer_mem
};

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

void licor_mbuf_reset(licor_mbuf_handle_t hnd)
{
    int idx = hnd%LI_MBUF_COUNT;
    ZERO_MEM((byte *)mbuf_table[idx],mbuf_len[idx]);
    mbuf_table[idx]->meas_idx=0;
    mbuf_table[idx]->depth=mbuf_depth[idx];
    mbuf_table[idx]->record.hdr.meas_count=0;
    mbuf_table[idx]->record.hdr.sync=LI_ZREC_SYNC;
    mbuf_table[idx]->record.hdr.len=0;
    // point to sdata memory (follows record header and data)
    mbuf_table[idx]->sdata=(sdata_Z_t *)((byte *)mbuf_table[idx]+sizeof(z_meas_buffer_t)+mbuf_depth[idx]*sizeof(mdata_Z_t));
}

z_meas_buffer_t *licor_mbuf_instance(licor_mbuf_handle_t hnd)
{
       return mbuf_table[hnd%LI_MBUF_COUNT];
}

void licor_mbuf_next(licor_mbuf_handle_t hnd)
{
    // advance index, counters, etc. and rollover automatically
    int idx = (hnd%LI_MBUF_COUNT);
    mbuf_table[idx]->meas_idx=(mbuf_table[idx]->meas_idx+1)%mbuf_depth[idx];
    mbuf_table[idx]->record.hdr.meas_count=( (mbuf_table[idx]->meas_idx-1)<0 ? mbuf_depth[idx] : (mbuf_table[idx]->meas_idx) );//(mbuf_table[idx]->record.hdr.meas_count+1)%mbuf_depth[idx];
    mbuf_table[idx]->record.hdr.len=(mbuf_table[idx]->record.hdr.meas_count*sizeof(mdata_Z_t));
//    DPRINT("%s - hnd/idx[%d/%d] meas_idx[%d] meas_count[%d] len[%lu]\r\n",__func__,hnd,idx,mbuf_table[idx]->meas_idx, mbuf_table[idx]->record.hdr.meas_count,mbuf_table[idx]->record.hdr.len);
    return;
}

licor_zrecord_t *licor_mbuf_record(licor_mbuf_handle_t hnd)
{
       int idx = hnd%LI_MBUF_COUNT;
       return (licor_zrecord_t *)(&mbuf_table[idx]->record);
}
size_t licor_mbuf_record_size(licor_mbuf_handle_t hnd)
{
       int idx = hnd%LI_MBUF_COUNT;
       return (sizeof(licor_zrecord_t)+mbuf_table[idx]->record.hdr.meas_count*sizeof(mdata_Z_t));
}

void licor_mbuf_set_rtime(licor_mbuf_handle_t hnd, uint64_t timestamp)
{
    int idx = hnd%LI_MBUF_COUNT;
    mbuf_table[idx]->record.hdr.timestamp=timestamp;
}
void licor_mbuf_set_rtid(licor_mbuf_handle_t hnd, uint16_t type_id)
{
    int idx = hnd%LI_MBUF_COUNT;
    mbuf_table[idx]->record.hdr.type_id=type_id;
}
void licor_mbuf_set_rmid(licor_mbuf_handle_t hnd, uint16_t meas_id)
{
    int idx = hnd%LI_MBUF_COUNT;
    mbuf_table[idx]->record.hdr.meas_id=meas_id;
}
void licor_mbuf_set_mmidn(licor_mbuf_handle_t hnd, int meas_idx, uint16_t meas_id)
{
    int idx = hnd%LI_MBUF_COUNT;
    int midx = meas_idx%mbuf_depth[idx];
    mdata_Z_t *mdata = LI_ZREC_PMDATA(&mbuf_table[idx]->record);
    mdata[midx].meas_id=meas_id;
}

void licor_mbuf_set_mmid(licor_mbuf_handle_t hnd, uint16_t meas_id)
{
    int idx = hnd%LI_MBUF_COUNT;
    int midx = mbuf_table[idx]->meas_idx;
    mdata_Z_t *mdata = LI_ZREC_PMDATA(&mbuf_table[idx]->record);
    mdata[midx].meas_id=meas_id;
}

void licor_mbuf_set_mperiodn(licor_mbuf_handle_t hnd, int meas_idx, float period)
{
    int idx = hnd%LI_MBUF_COUNT;
    int midx = meas_idx%mbuf_depth[idx];
     mdata_Z_t *mdata = LI_ZREC_PMDATA(&mbuf_table[idx]->record);
     mdata[midx].period=period;
}
void licor_mbuf_set_mperiod(licor_mbuf_handle_t hnd, float period)
{
    int idx = hnd%LI_MBUF_COUNT;
    int midx = mbuf_table[idx]->meas_idx;
     mdata_Z_t *mdata = LI_ZREC_PMDATA(&mbuf_table[idx]->record);
     mdata[midx].period=period;
}

uint16_t licor_mbuf_depth(licor_mbuf_handle_t hnd)
{
    int idx = hnd%LI_MBUF_COUNT;
    return mbuf_table[idx]->depth;
}

uint16_t licor_mbuf_mcount(licor_mbuf_handle_t hnd)
{
    int idx = hnd%LI_MBUF_COUNT;
    return mbuf_table[idx]->record.hdr.meas_count;
}

return_code_t licor_mmeas_out(licor_mbuf_handle_t hnd, int16_t meas_idx, queue_output_flag oflags)
{

    return_code_t retval=ERR_FAILURE;

    licor_zrecord_t *src = licor_mbuf_record(hnd);
    licor_zrecord_t *xfer = licor_mbuf_record(LI_MBUF_XFER);
    size_t data_sz=0;
    queue_handle_t queue_id=QUEUE_CYCLE;

//    DPRINT("srchnd[%d] xferhnd[%d] src[%p] xfer[%p] meas_idx[%d] oflags[x%"FMT_X32"]\r\n",hnd,LI_MBUF_XFER,src,xfer,meas_idx,oflags);

    // output source record
    if(oflags&QOF_RECORD){
        data_sz=licor_mbuf_record_size(hnd);
        // dest queue
        if(oflags&QOF_QUEUE){
            int data_id='R';
            if(log_queue_push(queue_id, (byte *)src, data_id, data_sz)==ERR_SUCCESS){
                retval=ERR_SUCCESS;
            }else{
                EMSG("qpush err\r\n");
                log_syslog("qpush err [%d]",retval);
            }
        }

        // dest console
        if((oflags&QOF_CONSOLE)!=0){
            SerPort sav_tty = g_ctty;
            g_ctty = SER_CONS;
            licor_zrecord_outs(src, oflags);
            serTxWait(g_ctty);
            g_ctty = sav_tty;
        }
        // dest host
        if((oflags&QOF_HOST)!=0){
            TRACE();
            SerPort sav_tty = g_ctty;
            g_ctty = SER_HOST;
            licor_zrecord_outs(src, oflags);
            serTxWait(g_ctty);
            g_ctty = sav_tty;
        }

        // dest log
        if(oflags&QOF_LOG){
            retval=licor_zrecord_outl(src, data_sz, (LF_WITHOUT_REC_SEP|LF_WITHOUT_REC_DEL));
       }
    }

    // set src midx default
    int16_t src_midx=meas_idx;
      // output source measurement
    if( (oflags&QOF_MLAST)!=0 || (oflags&QOF_MIDX)!=0 ){

       // clear the transfer buffer
       licor_mbuf_reset(LI_MBUF_XFER);

       // update src midx if using last meas
        if( (oflags&QOF_MLAST)!=0 ){
          src_midx = licor_mbuf_mcount(hnd)-1;
          src_midx = (src_midx>=0 &&  src_midx<licor_mbuf_depth(hnd)? src_midx : 0);
        }

       // save mdata pointer
       mdata_Z_t *xfrm_mdata = LI_ZREC_PMDATA(xfer);//xfer->mdata;

       licor_mbuf_next(LI_MBUF_XFER);

       // copy header
       memcpy(xfer,src,sizeof(licor_zrecord_t));

       mdata_Z_t *src_tmp_mdata=LI_ZREC_PMDATA(src);
       mdata_Z_t *src_mdata = &src_tmp_mdata[src_midx];

//       OUT_CTTY("before src_mdata[%d]:\r\n",src_midx);
//       hex_show((byte *)src_mdata,sizeof(mdata_Z_t),16,5,true);

       // copy data
       memcpy(xfrm_mdata,src_mdata,sizeof(mdata_Z_t));

       // update header (contains one measurement)
       xfer->hdr.meas_count=(licor_mbuf_mcount(hnd)>0 ? 1 : 0);
       // len is mdata total, not incl header
       xfer->hdr.len=(xfer->hdr.meas_count*sizeof(mdata_Z_t));

//       OUT_CTTY("after copy xfer_mdata:\r\n");
//       hex_show((byte *)xfrm_mdata,sizeof(mdata_Z_t),16,5,true);

        // dest queue
        if(oflags&QOF_QUEUE){
             int data_id='M';
            data_sz=licor_mbuf_record_size(LI_MBUF_XFER);

            if(log_queue_push(QUEUE_MEAS, (byte *)xfer, data_id, data_sz)==ERR_SUCCESS){
                retval=ERR_SUCCESS;
            }else{
                EMSG("qpush err\r\n");
                log_syslog("qpush err [%d]",retval);
            }
        }

       // dest console
        if((oflags&QOF_CONSOLE)!=0){
            SerPort sav_tty = g_ctty;
            g_ctty = SER_CONS;
            licor_zrecord_outs(xfer, oflags);
            serTxWait(g_ctty);
            g_ctty = sav_tty;
        }
        // dest host
        if((oflags&QOF_HOST)!=0){
            SerPort sav_tty = g_ctty;
            g_ctty = SER_HOST;
            licor_zrecord_outs(xfer, oflags);
            serTxWait(g_ctty);
            g_ctty = sav_tty;
        }
        // dest log
        if(oflags&QOF_LOG){
            size_t data_sz = LI_ZREC_SIZE(xfer);
            retval=licor_zrecord_outl(xfer, data_sz, LF_WITHOUT_REC_SEP);
       }
    }

    return retval;
}

return_code_t licor_queue_out(licor_zrecord_t *self, queue_output_flag oflags)
{
    return_code_t retval=ERR_FAILURE;

    if(NULL!=self){
        if( (oflags&QOF_CONSOLE)!=0 || (oflags&QOF_HOST)!=0 ){
            licor_zrecord_outs(self, oflags);
        }
        if( (oflags&QOF_LOG)!=0 ){
            size_t data_sz = LI_ZREC_SIZE(self);
            retval=licor_zrecord_outl(self, data_sz, LF_WITHOUT_REC_SEP);
        }
        retval=ERR_SUCCESS;
    }else{TRACE();}

    return retval;
}

int licor_mbuf_show(z_meas_buffer_t * self, bool verbose, int indent)
{
    int wbytes=0;

    if( NULL!=self && NULL!=self->sdata){
        mdata_Z_t *mdata = LI_ZREC_PMDATA(&self->record);
        wbytes+=OUT_CTTY("%*s%12s  %13p%s",    indent,(indent>0?" ":""),"self", self,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13u%s",indent,(indent>0?" ":""),"depth",self->depth,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13d%s",indent,(indent>0?" ":""),"meas_idx",self->meas_idx,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13p%s",indent,(indent>0?" ":""),"record",&self->record,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13p%s",indent,(indent>0?" ":""),"sdata",self->sdata,LINEEND);
        wbytes+=OUT_CTTY("%*s%12s  %13p%s",indent,(indent>0?" ":""),"mdata",mdata,LINEEND);

        if(verbose){
        log_rec_header_t *hdr=&self->record.hdr;
        wbytes+=licor_logrechdr_show(hdr,verbose,indent);
        }
    }
    return wbytes;
}

return_code_t licor_zrecord_outl(licor_zrecord_t *self, size_t data_sz, log_flags_t lflags)
{

    return_code_t retval=ERR_FAILURE;
    if(NULL!=self){
        // set and increment sequence number
        log_get_seq_number(LOG_ZBIN,&self->hdr.seq_n,true);
        DPRINT("setting seq_n [%lu] data_sz[%"FMT_SZ"] lflags[x%lX]\r\n",self->hdr.seq_n,data_sz,lflags);
        // no additional timestamp
        Time *time=NULL;
        retval = log_addb(LOG_ZBIN, (byte *)self, data_sz, time, lflags);
    }
    return retval;
}

return_code_t licor_read_ack()
{
    return_code_t retval=ERR_FAILURE;
    byte i;
    licor_config_t *pc = &g_licor_config;
    char ack_str[32]={0};
    sprintf(ack_str,"<ack>true</ack></li%d>\r\n",pc->model);
    for (i = 0; i < LI_ACK_RETRIES; i++) {
        if (serScanTO(SER_LICOR, ack_str, LI_ACK_TO_MSEC)==TRUE) {
            retval=ERR_SUCCESS;
            break;
        }
    }
     return retval;
}

return_code_t licor_initialize(licor_config_t *pconf)
{
    return_code_t retval=ERR_NO_IMPL;
    licor_config_t *pc = (NULL==pconf ? &g_licor_config : pconf);

    int16_t tmp=0;

     // initialize using current config values
     pc->model = (config_get(CK_LICOR_MODEL,(int16_t *)&tmp)==ERR_SUCCESS ? tmp : pc->model);
     g_licor_instance.valve_type = (config_get(CK_VALVE_TYPE,&tmp)==ERR_SUCCESS? tmp : g_licor_instance.valve_type);

    // turn output off
    if( (retval=licor_set_outrate(0.0))==ERR_SUCCESS){
            serRxFlush(SER_LICOR);

         // open liNNN tag...
        LI_WRITE("<li%d>",pc->model);

        // write config elements...
        LI_WRITE( OTAG_FMT("cfg") );
        LI_WRITE(STAG_FMT("heater"),CFLAG2BSTR(pc,LCF_CFG_HEATER));
        LI_WRITE(STAG_FMT("alarms"),CFLAG2BSTR(pc,LCF_CFG_ALARM));
        LI_WRITE(ITAG_FMT("outrate"),pc->outrate);
        LI_WRITE(ITAG_FMT("filter"),pc->filter);
        LI_WRITE( CTAG_FMT("cfg") );

        // write RS232 elements..
        LI_WRITE( OTAG_FMT("rs232") );
        LI_WRITE(STAG_FMT("co2"),CFLAG2BSTR(pc,LCF_RS232_CO2));
        LI_WRITE(STAG_FMT("co2abs"),CFLAG2BSTR(pc,LCF_RS232_CO2ABS));
        if(pc->model==850){
        LI_WRITE(STAG_FMT("h2o"),CFLAG2BSTR(pc,LCF_RS232_H2O));
        LI_WRITE(STAG_FMT("h2oabs"),CFLAG2BSTR(pc,LCF_RS232_H2OABS));
        LI_WRITE(STAG_FMT("h2odewpoint"),CFLAG2BSTR(pc,LCF_RS232_H2ODEWPOINT));
        }
        LI_WRITE(STAG_FMT("celltemp"),CFLAG2BSTR(pc,LCF_RS232_CELLTEMP));
        LI_WRITE(STAG_FMT("cellpres"),CFLAG2BSTR(pc,LCF_RS232_CELLPRES));
        LI_WRITE(STAG_FMT("ivolt"),CFLAG2BSTR(pc,LCF_RS232_IVOLT));
        LI_WRITE(STAG_FMT("strip"),CFLAG2BSTR(pc,LCF_RS232_STRIP));

        LI_WRITE(STAG_FMT("echo"),CFLAG2BSTR(pc,LCF_RS232_ECHO));
        LI_WRITE(STAG_FMT("raw"),CFLAG2BSTR(pc,LCF_RS232_RAW));
        LI_WRITE( CTAG_FMT("rs232") );

       // close liNNN
        LI_WRITE("</li%d>",pc->model);

        // confirm ack
        if((retval=licor_read_ack())!=ERR_SUCCESS){
            DPRINT("licor_init - read_ack err[%d/%s]\r\n",retval,(retval==ERR_SUCCESS?"OK":"ERR"));
            if(NULL!=pconf){
                // if successful and the config was passed in
                // save it to the primary config
                memcpy(&g_licor_config,pconf,sizeof(licor_config_t));
            }
        }
    }else{
        OUT_ERR("licor_init - set_outrate err [%d/%s]\r\n",retval,(retval==ERR_SUCCESS?"OK":"ERR"));
    }

     serRxFlush(SER_LICOR);

    return retval;
}

return_code_t licor_cal(licor_cal_id type, double spanc)
{
    return_code_t retval=ERR_FAILURE;
    licor_config_t *pc = &g_licor_config;
    Time now={0};

    if(licor_set_outrate(0.0)==ERR_SUCCESS){
        serRxFlush(SER_LICOR);
        rtcRead(&now);

        // <li850><cal><co2zero>true</co2zero></cal></li850>
        // <li850><cal><co2span>1.234</co2span></cal></li850>
        // <li850><cal><co2span2>1.234</co2span2></cal></li850>
        // open liNNN tag
        LI_WR_LOTAG(pc->model);
        // write cal tags
        LI_WRITE( OTAG_FMT("cal") );
        LI_WR_DATETAG(now.tm_year+1900,now.tm_mon+1,now.tm_mday);
        switch(type){
            case LI_CAL_Z:
            LI_WRITE(STAG_FMT("co2zero"),"true");
            break;
            case LI_CAL_S:
            LI_WR_ETAG("co2span",spanc);
            break;
            case LI_CAL_S2:
            LI_WR_ETAG("co2span2",spanc);
            break;
            default:
                // error
                break;
        }
        LI_WRITE( CTAG_FMT("cal") );
        // close liNNN tag
        LI_WR_LCTAG(pc->model);

        // confirm ACK...
        if( (retval=licor_read_ack())==ERR_SUCCESS){
            retval=ERR_SUCCESS;
        }

        log_syslog("CAL type/spanc/ret[%d/%lf/%d]",type,spanc,retval);

        // once the cal is written, output stops
        // for ~8s until a cal string
        // is transmitted and output resumes
        char cal_term[3]={0};
        sprintf(cal_term,"\r\n");
        char cal_out[LOG_SYSLOG_ALLOC_BYTES]={0};
        if(serGetUntilTO(SER_LICOR, cal_term, cal_out, LOG_SYSLOG_ALLOC_BYTES, LI_CAL_DELAY_MSEC, false)>0){
            cal_out[LOG_SYSLOG_ALLOC_BYTES-1]='\0';
            log_printf(LOG_SYSLOG,"'%s'",cal_out);
            retval=ERR_SUCCESS;
        }
    }else{
        OUT_ERR("%s - type/spanc[%d (%s)/%lf] set orate failed\r\n",__FUNCTION__,type,CALTYPE_STR(type),spanc);
        log_syslog("ERR CAL orate type/spanc/ret[%d/%lf/%d]",type,spanc,retval);
    }

    return retval;

}

float licor_outrate()
{
    float retval=-1.0;
    licor_config_t *pc = &g_licor_config;

    // open liNNN tag
    LI_WR_LOTAG(pc->model);
    // write config query
    LI_WRITE(QTAG_FMT("cfg"));
    // close liNNN tag
    LI_WR_LCTAG(pc->model);
    char ack[LI_CFG_QUERY_BYTES]={0};
    if (serGetUntilTO(SER_LICOR, "\n", ack, LI_CFG_QUERY_BYTES, LI_ACK_TO_MSEC, true)>0){
        char *pv=NULL;
        if((pv=strstr(ack,"<outrate>"))!=NULL){
            retval=atof(pv+9);
        }
    }
    return retval;
}

return_code_t licor_set_outrate(float rate)
{
    return_code_t retval=ERR_FAILURE;
    licor_config_t *pc = &g_licor_config;

    // empty serial RX buffer
    serRxFlush(SER_LICOR);

    // <lixxx><cfg><outrate>0</outrate></cfg></lixxx>
    //<li850><cfg><outrate>0.0</outrate></cfg></li850>
    //<li850><cfg><outrate>0.5</outrate></cfg></li850>
    //<li850><cfg><outrate>1.0</outrate></cfg></li850>

     // open liNNN tag
    LI_WR_LOTAG(pc->model);
    // write config tags
    LI_WRITE( OTAG_FMT("cfg") );
    LI_WRITE(FTAG_FMT("outrate"),rate);
    LI_WRITE( CTAG_FMT("cfg") );
    // close liNNN tag
    LI_WR_LCTAG(pc->model);

    serTxWait(LI_UART);

    // confirm ACK...
    if( (retval=licor_read_ack())==ERR_SUCCESS){
        pc->outrate=rate;
    }else{
       DPRINT("li_set_orate[%.3f] failed - check transceiver and sensor model\r\n",rate);
       DPRINT("'set bits ul' to enable and/or 'get/set var LICOR_MODEL'\r\n");
    }

    return retval;
}

 return_code_t licor_set_valve_type(licor_vtype_t type)
{
    return_code_t retval=ERR_NO_IMPL;
    return retval;
}

licor_model_id licor_model()
{
    licor_config_t *pc = &g_licor_config;
    return pc->model;
}

return_code_t licor_set_model(licor_model_id model)
{
    return_code_t retval=ERR_FAILURE;
    licor_config_t *pc = &g_licor_config;
    switch(model){
        case LI_MODEL_820:
        case LI_MODEL_830:
        case LI_MODEL_840:
        case LI_MODEL_850:
            pc->model=model;
            retval=ERR_SUCCESS;
            break;
        default:
            break;
    }
    return retval;
}

 static char *s_print_tag(char *tag, char *end)
 {
    while(tag<end){
        if(*tag!='\n' && *tag!='\r' && *tag!='\t')OUT_CTTY("%c",*tag);
        if(*tag=='>')break;
        tag++;
    }
    return tag;
 }

 void licor_status_show(char *self, const char *tag, bool verbose, int indent)
{
     // format raw status string
     if(NULL!=self){
         // three cursors:
         //0: primary
         //1: lookahead/print
         //2: end
         char *cur[3];
         char dfl_tag[16]={0};
         licor_config_t *pc = &g_licor_config;
         sprintf(dfl_tag,"li%d",pc->model);
         // tag subset (default is all)
         const char *ptag = (NULL!=tag ? tag : dfl_tag);
         char tags[2][strlen(ptag)+4];
         sprintf(tags[0],"<%s>",ptag);
         sprintf(tags[1],"</%s>",ptag);

         if(indent<0)indent=0;
         int i=0,ilev=indent;

         cur[0]=strstr(self,tags[0]);
         cur[2]=strstr(self,tags[1]);
         if(cur[0]==NULL || cur[2]==NULL){
             OUT_CTTY("WARN - incomplete status\r\n");
             cur[2]=self+strlen(self);
         }else{
             cur[2]+=strlen(tags[1]);
         }
         cur[1]=cur[0];

         for(i=0;i<ilev;i++) OUT_CTTY(" ");

         while(cur[0]<=cur[2] && cur[1]<=cur[2]){

             if(*cur[0]=='<' && *(cur[0]+1)!='/'){
                 // element open
                 cur[1]=cur[0];
                 // print tag
                 cur[1]=s_print_tag(cur[1],cur[2]);
                 cur[1]++;

                // look ahead w/ cur[1]]
                 if(*cur[1]=='<' && *(cur[1]+1)!='/'){

                   // if next: element open
                    OUT_CTTY("\r\n");
                    ilev++;

                    for(i=0;i<ilev;i++) OUT_CTTY(" ");

                     cur[0]=cur[1];
                 }else if(*cur[1]=='<' && *(cur[1]+1)=='/'){
                    // if next: element close (empty)
                     // print tag
                     cur[1]=s_print_tag(cur[1],cur[2]);
                     cur[1]++;

                     // add line, adjust indent
                     OUT_CTTY("\r\n");
                     for(i=0;i<ilev;i++) OUT_CTTY(" ");

                     cur[0]=cur[1];

                }else{
                    // if next: content

                    while(cur[1]<=cur[2]){
                        if(*cur[1]!='\n' && *cur[1]!='\r' && *cur[1]!='\t')OUT_CTTY("%c",*cur[1]);
                        // in this context, stop on < (not '>') without printing
                        cur[1]++;
                        if(*cur[1]=='<')break;
                    }
                    cur[0]=cur[1];
                }
             }else if(*cur[0]=='<' && *(cur[0]+1)=='/'){
                // element close
                 cur[1]=cur[0];
                 // print tag
                 cur[1]=s_print_tag(cur[1],cur[2]);
                 cur[1]++;
                 cur[0]=cur[1];
                 OUT_CTTY("\r\n");
                 // if we closed, and the next is closed dec indent
                 if(*cur[0]=='<' && *(cur[0]+1)=='/')ilev--;
                 if(ilev<0)ilev=0;
                 for(i=0;i<ilev;i++) OUT_CTTY(" ");
             }
             else{
                 cur[0]++;
             }
         }
     }

     OUT_CTTY("\r\n");

    return;
}

return_code_t licor_set_strip(bool strip)
{
    return_code_t retval=ERR_FAILURE;
    licor_config_t *pc = &g_licor_config;

     // open liNNN tag
    LI_WR_LOTAG(pc->model);
    // write config elements
    LI_WRITE( OTAG_FMT("rs232") );
    LI_WRITE(STAG_FMT("strip"),(strip?"true":"false"));
    LI_WRITE( CTAG_FMT("rs232") );
    // close liNNN tag
    LI_WR_LCTAG(pc->model);

    // confirm ACK...
    if( (retval=licor_read_ack())==ERR_SUCCESS){
        SET_FLAGS(pc->cflags,LCF_RS232_STRIP);
        OUT_ERR("%s - OK\r\n",__FUNCTION__);
    }else{
        OUT_ERR("%s - ERR[%d]\r\n",__FUNCTION__,retval);
    }
    return retval;
}

return_code_t licor_req_measurement()
{
    licor_config_t *pc = &g_licor_config;

    // write data request

    // open liNNN tag
    LI_WR_LOTAG(pc->model);
    // write data query tag
    LI_WRITE( QTAG_FMT("data") );
     // close liNNN tag
    LI_WR_LCTAG(pc->model);

    serTxWait(SER_CONS);
    return ERR_SUCCESS;
}

size_t licor_read_measurement(char *dest, int len)
{
    size_t retval=0;

    char ackstr[10]={0};
    sprintf(ackstr,"\n");
    ZERO_MEM(dest,len);

    // read response
    if( (retval=serGetUntilTO(SER_LICOR, ackstr, dest, len, LI_READ_TO_MSEC,true))>0){
        dest[retval]='\0';
//        DPRINT("%s - serGetUntil ret[%d/%s]\r\n",__FUNCTION__,retval,dest);
//        DPRINT("%s - serGetUntilTO ret[%d]\r\n",__FUNCTION__,retval);
    }else{
        OUT_ERR("%s - serGetUntilTO ERR[%d/%s]\r\n",__FUNCTION__,retval,dest);
    }

    return retval;
}

int licor_read_status(char *dest, int len)
{
    int retval=0;

    char ackstr[10]={0};
    sprintf(ackstr,"\n");
    ZERO_MEM(dest,len);

    // stop output, clear input
    if(licor_set_outrate(0.0)!=ERR_SUCCESS){
        WMSG("set_orate failed");
    }
    serRxFlush(SER_LICOR);

    licor_config_t *pc = &g_licor_config;

    // write conf request

    // open liNNN tag
    LI_WR_LOTAG(pc->model);
    // write data query tag
    LI_WRITE( "?" );
     // close liNNN tag
    LI_WR_LCTAG(pc->model);

    serTxWait(SER_CONS);

    // read response
    if( (retval=serGetUntilTO(SER_LICOR, ackstr, dest, len, 2*LI_READ_TO_MSEC,true))>0){
        dest[retval]='\0';
//        DPRINT("%s - serGetUntil ret[%d/%s]\r\n",__FUNCTION__,retval,dest);
//        DPRINT("%s - serGetUntilTO ret[%d]\r\n",__FUNCTION__,retval);
    }else{
        OUT_ERR("%s - serGetUntilTO ERR[%d/%s]\r\n",__FUNCTION__,retval,dest);
    }

    return retval;
}

licor_orecord_t *licor_parse_measurement(licor_orecord_t *dest, char *src, uint64_t tstamp)
{
    licor_orecord_t *retval=NULL;

    if(NULL!=dest && NULL!=src){
        licor_orecord_t *pr=dest;
        char *pdata=strstr(src,"<data>");
        char *pdatac=strstr(src,"</data>");
        char *praw=strstr(src,"<raw>");
        char *prawc=strstr(src,"</raw>");
        char *ptag=NULL;

        // TODO: pass arg, get cfg or replace oflags
        uint32_t oflags=0xFFFFFFFFUL;
        uint32_t eflags=0x0;

        if(NULL!=pdata && NULL!=pdatac && NULL!=praw && NULL!=prawc){
            char *ftags[]={"<co2>","<co2abs>","<h2o>",
            "<h2odewpoint>","<h2oabs>","<celltemp>",
            "<cellpres>","<ivolt>","<flowrate>",
            "<co2>","<co2ref>","<h2o>","<h2oref>"};

            ZERO_MEM(pr,sizeof(licor_orecord_t));
            // take timestamp
            pr->timestamp = tstamp;

            int i=0;
            for(i=0; (i<FID_LICOR_FIELDS) && (eflags==0); i++){

                // disambiguate data/raw h2o,co2
                // b/c for some reason, LI-COR reused tag names
                if(i==FID_CO2 || i==FID_H2O){
                    ptag=strstr(pdata,ftags[i]);
                    if(NULL!=ptag && (ptag>praw && ptag<prawc)){
                        // invalidate if only found in raw
                        ptag=NULL;
                    }
                }else if(i==FID_RAW_CO2 || i==FID_RAW_H2O){
                    ptag=strstr(praw,ftags[i]);
                    if(NULL!=ptag && (ptag<praw && ptag>prawc)){
                        // invalidate if only found in data
                        ptag=NULL;
                    }
                }else{
                    ptag=strstr(pdata,ftags[i]);
                }

                if(NULL!=ptag){
                    if(ISSET(oflags,1<<i)){
                       pr->fields[i] = atof(ptag+strlen(ftags[i]));
                    }
                }else{
                    // set eflag and stop parsing if missing/invalid
                    eflags |= (1<<i);
                }
            }

            if(eflags==0){
                retval = dest;
            }else{
                EPRINT("eflags:%08lX\r\n",eflags);
            }
        }else{
            EMSG("data tag missing\r\n");
        }
    }
    return retval;
}

int licor_poll_raw(char *dest, int len)
{
    int retval=-1;
    licor_set_outrate(0.0);
    serRxFlush(SER_LICOR);
    if(licor_req_measurement()==ERR_SUCCESS){
        retval=licor_read_measurement(dest, len);
    }
    return retval;
}

/// Uses Welford's method of variance estimate (single pass)
/// from 1962 paper by B. P. Welford
/// from Donald Knuth?s Art of Computer Programming, Vol 2, page 232, 3rd edition
/// also described here https://www.johndcook.com/blog/standard_deviation/
// calc mean, sdev, var
//Initialize M1 = x1 and S1 = 0.
//
//For subsequent x?s, use the recurrence formulas
//
//Mk = Mk-1 + (xk - Mk-1)/k
//Sk = Sk-1 + (xk - Mk-1)*(xk - Mk).
//
//For 2 <= k <= n, the kth estimate of the variance is s^2 = Sk/(k - 1).

// The average can be understood this way:
// in general avg M = S/n or S = n*M
// The kth sum (S') is k*M'
// The (k-1)th sum (S) is (k-1)*M
// S' = kM', S = (k-1)M
// S'  = S + x;
// kM' = (k-1)M + x
// kM' = kM + (x - M)
//  M' = M + (x-M)/k

int licor_handler_z(licor_orecord_t *cur, void *args)
{
    int retval=-1;
    if(NULL!=cur && NULL!=args){

        // point to current record, state and state data
        licor_orecord_t *obs = (licor_orecord_t *)cur;
        licor_zctx_t *zctx = (licor_zctx_t *)args;
        licor_orecord_ctx_t *octx = zctx->octx;
        licor_mbuf_handle_t mbuf_hnd = zctx->mbuf_hnd;//*((licor_mbuf_handle_t *)args);
        z_meas_buffer_t *state = licor_mbuf_instance(mbuf_hnd);//(z_meas_buffer_t *)args;

        if( (NULL!=state->sdata) && (state->meas_idx>=0) ){
            // select destination record index (and state) using meas_idx (set by caller)
            int meas_idx = state->meas_idx;

            sdata_Z_t *sdata = &state->sdata[meas_idx];
            mdata_Z_t *pmdata = LI_ZREC_PMDATA(&state->record);//&state->record.mdata[meas_idx];
            mdata_Z_t *meas = &pmdata[meas_idx];

            // cast records to alternative struct definitions
            // with field arrays for iteration
            double *state_M_prev = (double *)sdata->M_prev;
            double *state_S_prev = (double *)sdata->S_prev;
            double *state_V_prev = (double *)sdata->V_prev;
            double *meas_M = (double *)meas->M;
            double *meas_S = (double *)meas->S;
            double *meas_MIN = (double *)meas->min;
            double *meas_MAX = (double *)meas->max;
            double *meas_X = (double *)meas->X;
            int i=0;

            // update orecord state
            cur->sync=LI_OREC_SYNC;
            cur->id = ((octx->meas_prof->rmid&0xFF) << 8)+((octx->meas_prof->mmid&0xFF));
            cur->cycle_id = octx->cycle_id;
            cur->meas_n = octx->meas_seq;
            cur->ens_n = octx->ensemble_seq;
            cur->ens_len = octx->meas_prof->meas_n;
            cur->period_s = octx->meas_prof->period_s;
            octx->ensemble_seq++;

    //       OUT_CTTY("mbuf_hnd[%d] meas_idx[%d] s->meas_idx[%d] stat_n[%d]\r\n",mbuf_hnd,meas_idx,state->meas_idx,meas->stat_n);
            if(meas->stat_n==0){
               // record start time
                meas->timestamps[0]=cur->timestamp;
                // initialize extra fields
                meas_X[ZID_CO2_EP0]=obs->fields[ZID_CO2];
                meas_X[ZID_CO2_EP1]=obs->fields[ZID_CO2];
                meas_X[ZID_CO2_AREA]=0;
                meas_X[ZID_MX]=obs->fields[ZID_CO2];
                meas_X[ZID_SX]=0;
                meas_X[ZID_SXY]=0;
                sdata->MX_prev=1.0;
                sdata->SX_prev=0.0;
                sdata->SXY_prev=0.0;
                // initialize min, max, mean, sdev
                // for licor parameters
                for(i=0;i<ZID_LICOR_FIELDS;i++){
                    meas_M[i] = obs->fields[i];
                    meas_S[i] = 0.0;
                    meas_MIN[i] = obs->fields[i];
                    meas_MAX[i] = obs->fields[i];
                }

            }else{
               // record end time
                meas->timestamps[1]=cur->timestamp;
                // update min, max, mean, sdev
                // M' = M + (x - M)/n
                // where
                // M' : current  (nth)   average
                // M  : previous (n-1)th average
                // x  : current value
                for(i=0;i<ZID_LICOR_FIELDS;i++){
                   meas_M[i] = state_M_prev[i] + (obs->fields[i]-state_M_prev[i])/meas->stat_n;
                    meas_S[i] = state_S_prev[i] + (obs->fields[i]-state_M_prev[i])*(obs->fields[i]-meas_M[i]);
    //                DPRINT("  M[%d]%.3f\r\n",i,meas_M[i]);
                    // update min, max
                    if(cur->fields[i] > meas_MAX[i]){
                        meas_MAX[i]=cur->fields[i];
                    }
                    if(cur->fields[i] < meas_MIN[i]){
                        meas_MIN[i]=cur->fields[i];
                    }
                }

                // the X values (time) are just n
                meas_X[ZID_MX]  = sdata->MX_prev  + (meas->stat_n - sdata->MX_prev)/meas->stat_n;
                meas_X[ZID_SX]  = sdata->SX_prev  + (meas->stat_n - sdata->MX_prev)*(meas->stat_n - meas_X[ZID_MX]);
                meas_X[ZID_SXY] = sdata->SXY_prev + (obs->fields[ZID_CO2] - meas_M[ZID_CO2])*(meas->stat_n - meas_X[ZID_MX]);

                // set up for next iteration
                // [current => prev]
                for(i=0;i<ZID_LICOR_FIELDS;i++){
                  state_M_prev[i] = meas_M[i];
                    state_S_prev[i] = meas_S[i];
                }

                sdata->MX_prev  = meas_X[ZID_MX];
                sdata->SX_prev  = meas_X[ZID_SX];
                sdata->SXY_prev = meas_X[ZID_SXY];

            }

           // update sample N
            meas->stat_n++;

            // update CO2 endpoint
            meas_X[ZID_CO2_EP1]=obs->fields[ZID_CO2];

            // update area...
            // [using trapezoid rule]
           // state_V_prev->timestamp  -> state->x_sdata[0].timestamp_prev
            uint64_t dt = (sdata->timestamp_prev==0 ? 0 : (cur->timestamp - sdata->timestamp_prev));
            double df = 0.5*((cur->fields[ZID_CO2] + state_V_prev[ZID_CO2]));
    //        DPRINT("%s - tsc[%llu] tsp[%llu] dt_ms[%llu] df[%lf] Ao[%.2lf]\r\n",__FUNCTION__,cur->timestamp,pa->records[2].timestamp,dt,df,pa->co2_area);
            meas_X[ZID_CO2_AREA] += df*dt/1000.0;

            // copy current values to prev
            memcpy(state_V_prev,cur->fields,ZID_LICOR_FIELDS*sizeof(double));
            sdata->timestamp_prev =  cur->timestamp;

            retval=0;
            }else{TRACE();}
    }else{OUT_ERR("cur[%p] args[%p]\r\n",cur,args);}

    return retval;
}

// a re-stream handler
int licor_handler_o(licor_orecord_t *cur, void *args)
{
    int retval=-1;
    if(NULL!=cur && NULL!=args){
        licor_orecord_ctx_t *octx = (licor_orecord_ctx_t *)args;
        if( ((octx->flags&QOF_CONSOLE) != 0) || ((octx->flags&QOF_HOST) != 0)){

            cur->sync=LI_OREC_SYNC;
            cur->id = ((octx->meas_prof->rmid&0xFF) << 8)+((octx->meas_prof->mmid&0xFF));
            if( (octx->flags&QOF_LOG_OREC)!=0){
                // if logging,
                // set and increment sequence number
                log_get_seq_number(LOG_OBIN,&cur->seq_n,true);
            }else{
                cur->seq_n = SEQN_INVALID;
            }
            cur->cycle_id = octx->cycle_id;
            cur->meas_n = octx->meas_seq;
            cur->ens_n = octx->ensemble_seq;
            cur->ens_len = octx->meas_prof->meas_n;
            cur->period_s = octx->meas_prof->period_s;

            licor_orecord_outs(cur, octx->flags);
        }

        octx->ensemble_seq++;
        retval=0;
    }
    return retval;
}

// profiling
//  uint32_t x[n],prof[5][n];
//  ZERO_MEM(x,n*sizeof(uint32_t));
//  ZERO_MEM(prof,5*n*sizeof(uint32_t));
//  prof[0][n-rcount]=tkGetTicks();
//  x[n-rcount]=ems;
//  prof[1][n-rcount]=tkGetTicks();
//  prof[2][n-rcount]=tkGetTicks();
//  prof[3][n-rcount]=tkGetTicks();
//  prof[4][n-rcount]=tkGetTicks();
//  int i=0;
//  for(i=0;i<n;i++)
//    DPRINT("ems[%2d][%5lu / %4lu]\r\n",i,x[i],(i>0?x[i]-x[i-1]:0));
//  for(i=1;i<n;i++)
//    DPRINT("[%2d] get[%4lu] parse[%4lu] fn[%4lu] log[%4lu] tc[%4lu] ems[%4lu]\r\n",i,
//    MS_PER_TICK*(prof[1][i]-prof[0][i]),
//    MS_PER_TICK*(prof[2][i]-prof[1][i]),
//    MS_PER_TICK*(prof[3][i]-prof[2][i]),
//    MS_PER_TICK*(prof[4][i]-prof[3][i]),
//    MS_PER_TICK*(prof[4][i]-prof[0][i]),
//    MS_PER_TICK*(prof[4][i]-prof[0][0]));
//    uint32_t eticks=prof[4][n-1]-prof[0][0];
//    DPRINT("total ems[%5lu]\r\n",eticks*MS_PER_TICK);

return_code_t licor_stream_x(uint32_t blank_ms, float orate, uint16_t n, li_handler_fn rec_fn, void *args, char *raw_id,  uint32_t tmout_ms, queue_output_flag flags)
{
    return_code_t retval=ERR_FAILURE;
    int rcount=n;
    licor_orecord_t cur={0};
    uint64_t tt_start=0;
    uint64_t tt_now=0;
    uint32_t start_ticks=0;
    uint32_t now_ticks=0;
    uint32_t ems=0;
    Time rawTime={0};
    const char *ackstr="\n";
    bool log_raw=( (flags&QOF_LOG_RAW) ==0 ? false : true);
    bool log_orec=( (flags&QOF_LOG_OREC) ==0 ? false : true);

    DPRINT("%s - orate[%.1lf] n[%d]  fn[%p] to[%ld] pcur[%p]\r\n",__FUNCTION__,orate,n,rec_fn, tmout_ms,&cur);
    DPRINT("%s - zhandler [%p]\r\n",__FUNCTION__,licor_handler_z);
    DPRINT("%s - ohandler [%p]\r\n",__FUNCTION__,licor_handler_o);
    DPRINT("%s - log raw[%c] orec[%c]\r\n",__FUNCTION__,BOOLCHAR(log_raw),BOOLCHAR(log_orec));
    // save current orate

    // set up buffers, init timing
    char *raw=(char *)palloc(LI_RAW_RECORD_STR_BYTES);

    if(NULL!=raw){

#ifdef WITH_LICOR_STREAMX_RESTORE_ORATE
         double oorate = licor_get_outrate();
#endif
        // stop output, clear input
        licor_set_outrate(0.0);
        serRxFlush(SER_LICOR);

        // set outrate
        if(licor_set_outrate(orate)==ERR_SUCCESS){

            log_open(LOG_RAW);
            log_open(LOG_OBIN);
            log_open(LOG_CBIN);

            serRxFlush(SER_LICOR);
            serScanTO(SER_LICOR,"\n",tmout_ms);

            Timer to;
            bool tmout_ok=true;
            bool tmout_start=false;

            rtcRead(&rawTime);
            time_t tt = mktime(&rawTime);
            tt_start=tt;
            start_ticks=tkGetTicks();

            // start read/process loop
            // break on timeout or completion

            while( (tmout_ms==0) || (rcount>0 && tmout_ok)){
                // read record
                size_t rb=0;

                if( (rb=serGetUntilTO(SER_LICOR, ackstr, raw, LI_RAW_RECORD_STR_BYTES, LI_READ_TO_MSEC,true))>0 ){

                   raw[rb]='\0';
                   char *xp=&raw[rb];
                   while(xp>raw && (isspace(*xp) || *xp=='\0') ){
                       *xp='\0';
                       xp--;
                   }
//                        DPRINT("%s - got raw meas ret/slen[%d/%d][%s]\r\n",__FUNCTION__,rb,strlen(raw),raw);
//                        DPRINT("%s - got raw meas ret/slen[%d/%d]\r\n",__FUNCTION__,rb,strlen(raw));

                    // take timestamp
                    // for rates > 1 Hz, RTC gives invalid readings.
                    // Must start+offset using system ticks
                    // (though resolution is still 1 s)
                    // i.e., at 2Hz, timestamp changes every 2 records
                    now_ticks=tkGetTicks();
                    ems=(now_ticks-start_ticks)*MS_PER_TICK;
                    tt_now=(tt_start*1000llu+ems);
                    rtcRead(&rawTime);
                    bool parse_ok=false;
                    // parse record, pass to processing function
                    if(blank_ms==0 || ems>blank_ms){
                        if(!tmout_start){
                            if(tmout_ms>0){
                            MPRINT(MOD_LIC,"starting tmout\r\n");
                            tkTmrSet(&to,tmout_ms);
                            tkTmrStart(&to);
                            }
                            tmout_start=true;
                        }
                        tmout_ok=(tkTmrUpdate(&to)>0?true:false);
                        if(licor_parse_measurement(&cur,raw,tt_now)!=NULL){
                            parse_ok=true;
                            if( rec_fn(&cur,args)==0){
//                                DPRINT("%s - parsed meas [%2d/%2d] raw_len[%4d] ems[%5lu]\r\n",__FUNCTION__,(n-rcount+1),n,strlen(raw),ems);
//                                DPRINT("%s - data len[%d]{%s}\r\n",__FUNCTION__,strlen(raw),raw);
                                // decrement counter on success
                                rcount--;
                            }else{MPRINT(MOD_LIC,"ERR - rec_fn\r\n");}
                        }
                    }else{
                        char *px=NULL;
                        float co2=0.0;
                        if( (px=strstr(raw,"<co2>"))!=NULL){
                            co2 = atof(px+5);
                        }

                        MPRINT(MOD_LIC,"[blanking] CO2[%e]\r\n",co2);
                    }
                    if(log_raw){
                        log_adds(LOG_RAW, (NULL!=raw_id?raw_id:""), &rawTime, LF_NOCLOSE|LF_WITHOUT_REC_SEP);
                        log_addb(LOG_RAW, (byte *)raw, strlen(raw), NULL, LF_WITHOUT_REC_DEL|LF_NOCLOSE);
                    }
                    if(parse_ok && log_orec){
                        // set and increment sequence number
                        // (unless orecord handler, which sets sequence number)
                        if(rec_fn!=licor_handler_o){
                            log_get_seq_number(LOG_OBIN,&cur.seq_n,true);
                        }
                        log_addb(LOG_OBIN, (byte *)&cur, sizeof(licor_orecord_t), NULL, LF_WITHOUT_REC_DEL|LF_WITHOUT_REC_SEP|LF_NOCLOSE);
                        log_addb(LOG_CBIN, (byte *)&cur, sizeof(licor_orecord_t), NULL, LF_WITHOUT_REC_DEL|LF_WITHOUT_REC_SEP|LF_NOCLOSE);
                    }
                }

                int brk=0;
                if( (brk=iow_get_char(false))==CTRL_D){
                    // user interrupt received
                    break;
                }
                // clear raw, parsed records
                ZERO_MEM(&cur, sizeof(licor_orecord_t));
                ZERO_MEM(raw,LI_RAW_RECORD_STR_BYTES);
            }// while
            log_close(LOG_RAW);
            log_close(LOG_OBIN);
            log_close(LOG_CBIN);
            if(rcount==0){
                MPRINT(MOD_LIC,"read[%d/%d]\r\n",(n-rcount),n);
                retval=ERR_SUCCESS;
            }else{
                OUT_ERR("%s - read[%d/%d]\r\n",__FUNCTION__,(n-rcount),n);
               log_syslog("%s - read[%d/%d]\r\n",__FUNCTION__,(n-rcount),n);
             }

#ifdef WITH_LICOR_STREAMX_RESTORE_ORATE
            // restore orate
           if(oorate==0.0 || oorate>=0.5){
               serRxFlush(SER_LICOR);
               licor_set_outrate(0.0);
                serRxFlush(SER_LICOR);
                licor_set_outrate(oorate);
            }else{
                    TRACE();
                 }
#endif
        }else{
          OUT_ERR("%s - set orate failed[%.1lf]\r\n",__FUNCTION__,orate);
        }

        pfree(raw);// this is the right place for this

   }else{
        OUT_ERR("%s - buffer alloc failed\r\n",__FUNCTION__);
   }
    return retval;
}

int licor_mnem2cflag(void *dest, char *src){return config_mnem2flag(dest, src, licor_cflag_mnemonics); }
int licor_mnem2oflag(void *dest, char *src){return config_mnem2flag(dest, src, licor_oflag_mnemonics); }
int licor_cflag2mnem(char *dest, void *src, size_t len){return config_flag2mnem(dest, src, len, licor_cflag_mnemonics, LI_CFLAG_MNEM_FIELDS); }
int licor_oflag2mnem(char *dest, void *src, size_t len){return config_flag2mnem(dest, src, len, licor_oflag_mnemonics, LI_OFLAG_MNEM_FIELDS); }
