def __init__(me, io = None, timeout = 0.01) :
        me.io           = io
        me.timeout      = timeout
        me.ibuf         = ""
        me.scnt         = 1
        me.rx_when      = tzlib.elapsed_time() - 10000.0
        me.data         = []                    # queue of a_recording's driven by data coming in from the device

        if  not me.io   :
            me.reopen()
        pass
    def read(me, how_many = 1, timeout = None) :
        timeout         = timeout or me.timeout

        t               = nt    = tzlib.elapsed_time()
        r               = ""
        while True :
            rr          = me.ibuf[:how_many]
            me.ibuf     = me.ibuf[how_many:]
            how_many   -= len(rr)
            rc          = len(rr)
            r          += rr
            if  not how_many :
                break

            rr          = me.rx(how_many)
            how_many   -= len(rr)
            rc         += len(rr)
            r          += rr
            if  not how_many :
                break


            if  rc      :
                t       = nt

            if  nt - t >= timeout :
                if  len(r) :
                    break
                raise a_cms50_timeout_exception("read")

            nt          = tzlib.elapsed_time()

            if  not rc  :
                time.sleep(min(0.1, timeout / 2.0))
            pass

        # print "rxing", len(r), "%04x" % ( len(r) ), hexify(r)

        return(r)
def main(ident) :
    import      TZCommandLineAtFile
    import      TZKeyReady
    try :
        import  tz_google_chart
        import  tz_browser
    except ImportError :
        tz_google_chart = None


    if  ident :
        sys.argv.insert(1, ident)
        sys.argv.insert(1, '--ident')

    program_name    = sys.argv.pop(0)

    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)


    ident       = ""
    port        = 0             # my COM port, not yours
    port_list   = 0
    verbose     = 0


    help_str    = """
%s (options) (output_files_base_name)

    I get the streaming data from a CMS50E Pulse Oximeter.

Options:

    --port  port_number     Set the COM port number
    --port_list             List possible ports (twice, list all available ports)
    --version               Print the program version number.


""" % ( os.path.basename(program_name) )


    oi  = tzlib.array_find(sys.argv, [ "--help", "-?", "?", "-h", "/h", "/?", "?" ] )
    if  oi >= 0 :
        print help_str
        sys.exit(254)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--ident", "-i" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        if  (oi >= len(sys.argv)) or not len(sys.argv[oi]) :
            print "Program IDENT info not given!"
            sys.exit(101)
        ident       = sys.argv.pop(oi)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--version", "-v" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        print "version", _program_version_str(ident)
        if  not sys.argv :
            sys.exit(0)
        pass


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--port", "-p" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        if  (oi >= len(sys.argv)) or not len(sys.argv[oi]) :
            print "No COM port given!"
            sys.exit(102)
        port        = sys.argv.pop(oi)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--port_list" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        port_list  += 1

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--verbose" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        verbose    += 1



    ofile_name      = None
    if  len(sys.argv) >= 1 :
        ofile_name  = sys.argv.pop(0)

        if  ofile_name.startswith('-') :
            print "Put the whole path or a dot/slash before the output file name. Dashes are confusing: [%s]" % ofile_name
            sys.exit(104)

        pass

    if  len(sys.argv) :
        print "I don't understand %s (--help for options)!" % ( sys.argv )
        sys.exit(104)



    if  port_list :
        tz_usb.find_likely_COM_ports(vendor_id = USB_VENDOR_ID, product_id = USB_PRODUCT_ID, list_level = port_list)


    if  not port :
        port    = tz_usb.find_likely_COM_ports(vendor_id = USB_VENDOR_ID, product_id = USB_PRODUCT_ID)
        if  port :
            print   "Using port", port[0],
            if  len(port) > 1 :
                print "Found ports", port,
            print
            port    = port[0]
        pass

    if  not port :
        print "Please tell me a COM port to use with the --port option (e.g. --port 2 )!"
        sys.exit(103)

    try :
        port    = int(port)
        cport   = port - 1
    except ValueError :
        cport   = port

    try :
        io  = serial.Serial(port = cport, baudrate = 19200, parity = "O", timeout = 0.001)                # note: PC program sets 8O1. serial.Serial() multiplies timeout by 1000 before passing to windows (This 1 mill is minimum for windows. I don't know about other OS's.)
    except serial.SerialException :
        print "Port %s [%s] cannot be opened!" % ( str(port), str(cport) )
        sys.exit(111)

    me      = a_comm(io)

    samples = a_recording()

    if  ofile_name :
        if  os.path.splitext(ofile_name)[1].lower() == ".csv" :
            fo  = output_files.a_file(ofile_name)
            fo.write(a_full_sample.csv_header + "\n")
        else    :
            fo  = None
            fn  = get_output_file_name(ofile_name, program_name = program_name)
            print "Outputting to: ", samples.open_write_file(fn)
        pass

    prg     = a_progress_rtn()
    stopped = 1000
    finger  = False
    lay     = []
    mx      = -10000
    mn      =  10000
    msa     = [ 64 ] * 300
    mss     = float(sum(msa))
    ts      = tzlib.elapsed_time()
    rx_when = ts
    png_drs = [ 7.5, 15.0, 30.0, 60.0, 120.0, 10 * 60.0, 15 * 60.0, 60 * 60.0, 2 * 60 * 60.0, 6 * 60 * 60.0, 12 * 60 * 60.0, ]
    png_di  = 0
    sb      = 0xf5

    me.start_usb()              # in case he's not turned it on (though we'll do this every half second of silence from the device, anyway

    print "Type ? for help"

    while True :
        try :
            s       = me.read_sample(progress_rtn = prg.show_progress, verbose = verbose)
            if  s   :
                yy  = s.y

                if  False :
                    msa.append(s.y)
                    mss        += s.y
                    mss        -= msa[0]
                    del(msa[0])
                else            :
                    mss         = 64
                if  False       :
                    yy          = ((s.y) * ((s.bc & 0xf) + 1)) / 4.5        # tends to flatten out in the middle when there is a change to low amplitude waves
                    if  False :
                        if  yy  < 0 :
                            yy  = -math.log(-yy + math.e)
                        else    :
                            yy  =  math.log( yy + math.e)
                        pass
                    pass

                mx  = max(mx, yy)
                mn  = min(mn, yy)

                if  s.ac & 3 :
                    print "@@@@ ac=%u" % s.ac
                    lay.append(s.ac)

                if  not  finger :
                    samples.append(a_finger_sample(True))
                samples.append(s)
                if  ofile_name  :
                    if  fo      :
                        fo.write("%s\n" % s.csv_str())
                    samples.write_new_samples()
                ys      = (' ' * int((100.0 * (yy - mn)) / max(1.0, (mx - mn)))) + '*'
                # avya  = [ ox for ox in samples.samples[-5 * SAMPLE_RATE : ] if hasattr(ox, 'y') ]
                # avy   = sum([ ox.y for ox in avya ]) / float(max(1, len(avya)))
				#dreamtcs might want to skip this
                print ( s.print_str() )               # (100.0 * yy) / (yy + (s.bc & 0xf)), ys )

                if  not finger :
                    finger  = True
                    samples.flush_file()
                    print "Finger"
                stopped = max(stopped - 10, 0)

                if  len(samples.samples) > png_drs[-1] * SAMPLE_RATE * 2 :
                    samples.flush_file()
                    samples.forget_old_samples(png_drs[-1] * SAMPLE_RATE)
                rx_when = tzlib.elapsed_time()
            t   = tzlib.elapsed_time()
            if  t - ts > 59 :
                ts  = t
                samples.flush_file()
            pass
        except   a_cms50_no_finger_exception :
            if  finger  :
                finger  = False
                samples.append(a_finger_sample(False))
                print "No finger"
                samples.flush_file()
            pass
        except   a_cms50_data_exception, msg :
            samples.flush_file()
            print msg                                   # those bits are not, apparently, dupes of each other
            if  not stopped :
                sys.exit(199)
            stopped    -= 1
        except ( a_cms50_exception, a_cms50_timeout_exception, ) :
            t   = tzlib.elapsed_time()
            if  t - rx_when > 0.5 :
                rx_when = t
                ts      = t
                mx      = -10000
                mn      =  10000
                samples.flush_file()
                me.start_usb()
            pass
    def read_sample(me, progress_rtn = None, verbose = 0, mismatch_callback = None) :
        s       = None
        try     :
            b           = ord(me.read())
            me.rx_when  = tzlib.elapsed_time()

            if  b  == 128 :
                ba  = [ b ]
                t   = me.rx_when
                while (len(ba) < 5) and (t - me.rx_when < 0.1) :
                    t   =   tzlib.elapsed_time()
                    ba.append(ord(me.read()))

                me.scnt    += 1

                if  False and (ba[1] in [ 0, 0xff ]) :              # 321390_Kanograf_(8400).pdf
                    if  len(ba) == 5 :
                        print "@@@@ %02x:%02x:%02x:%02x:%02x" % ( ba[0], ba[1], ba[2], ba[3], ba[4] )
                    else    :
                        print "@@@@", str(ba)
                    pass

                raise a_cms50_no_finger_exception

            if  b   < 128 :
                # print "@@@@ toss read %02x" % b
                pass
            else    :
                y   = ord(me.read())
                ay  = ord(me.read())

                if  (b == 0xf2) and (y & 0x80) :
                    me.ibuf = chr(b) + chr(y) + chr(ay) + me.ibuf
                    dt      = me.read_upload(progress_rtn = progress_rtn, verbose = verbose)
                    if  dt  :
                        me.data.append(dt)

                    return(None)

                hr  = ord(me.read())
                if  (ay == 0xf2) and (hr & 0x80) :
                    me.ibuf = chr(ay) + chr(hr) + me.ibuf
                    dt      = me.read_upload(progress_rtn = progress_rtn, verbose = verbose)
                    if  dt  :
                        me.data.append(dt)

                    return(None)

                hr |= ((ay & 0x40) << 1)

                ox  = ord(me.read())

                if  ay & 0x80 :
                    # me.ibuf = chr(ay) + chr(hr) + chr(ox) + me.ibuf       # note: the few times this has happened have not indicated that this is a good idea - nor that interpreting the ay|hr|ox bytes as uploaded data is a good idea
                    return(None)                                            # punt as best we can

                bc  = (b & ~(64 | 128))
                ac  = ay >> 4
                # print "@@@@ %02x" % b, hr, ox, me.scnt, y, ay, bc, ac
                s   = a_full_sample(hr, ox, me.scnt, y, ((b & 64) and True) or False, bc = bc, ac = ac & 3)

                if  (ay & 0x0f) != (y / 8) :            # apparently, these 4 bits are a dupe of the top 4 or the 7 bits in "y" - let's force that to be so - crashes when device dumps memory
                    # print "@@@@ b=%02x ay & 0xf != y/8  ay:0x%02x != y:0x%02x" % ( b, ay, y )           # can happen if upload is starting
                    if  mismatch_callback :
                        mismatch_callback(s, b, ay)     # tell the caller this happened if he wants to know
                    s       = None

                me.scnt    += 1
            pass

        except ( a_cms50_exception, a_cms50_timeout_exception, ) :
            if  tzlib.elapsed_time() - me.rx_when > 0.5 :
                me.scnt     = 1
            raise

        return(s)
            while len(me.data) :
                dt  = me.data.pop(0)
                # print "@@@@", len(dt.samples), len(dt.fd), "at", dt.tm / 3600, (dt.tm / 60) % 60
                if  len(dt.samples) :
                    fn  = get_output_file_name(ofile_name, program_name = program_name, ext = ".dat")
                    # tzlib.write_whole_binary_file(fn, dt.fd)
                    dfn = dt.write_file(fn)
                    if  not dfn :
                        print "Probably no data to write, so file not written."
                    else :
                        print "Wrote driven upload to", fn, "and", dfn, len(dt.samples)
                    pass
                pass
            mx      = -10000
            mn      =  10000
            ts      = tzlib.elapsed_time()
            stopped = 1000

        k   = TZKeyReady.key_ready()
        if  k :
            print

            if  k in [ 'q', ] :
                break

            if  k == '?' :
                if  tz_google_chart :
                    pcmd    = ("""
p       Write .png file showing graph to a file named like %s.
        And write %s file with graphed data.
""" % ( get_output_file_name(ofile_name, program_name = program_name, ext = ".png"), a_recording.FILE_EXT, ) ).rstrip()