#!/usr/bin/env python
#
# Copyright (C) Mar 2012  W. Trevor King <wking@drexel.edu>
#
# 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 2
# of the License, or (at your option) any later version.
#
# 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.

"""Emulate the C demo `insn.c`

This example does 3 instructions in one system call.  It does a
`gettimeofday()` call, then reads `N_SAMPLES` samples from an analog
input, and the another `gettimeofday()` call.
"""

import logging as _logging

import comedi as _comedi


MAX_SAMPLES = 128
LOG = _logging.getLogger('comedi-insn')
LOG.addHandler(_logging.StreamHandler())
LOG.setLevel(_logging.ERROR)


def insn_str(insn):
    return ', '.join([
            'insn: {}'.format(insn.insn),
            'subdev: {}'.format(insn.subdev),
            'n: {}'.format(insn.n),
            'data: {!r}'.format(insn.data),
            'chanspec: {}'.format(insn.chanspec),
            ])

def setup_gtod_insn(device, insn):
    insn.insn = _comedi.INSN_GTOD
    insn.subdev = 0
    insn.n = 2
    data = _comedi.lsampl_array(2)
    data[0] = 0
    data[1] = 0
    data.thisown = False
    insn.data = data.cast()
    insn.chanspec = 0
    return insn

def get_time_of_day(insn):
    assert insn.insn == _comedi.INSN_GTOD, insn.insn
    data = _comedi.lsampl_array.frompointer(insn.data)
    seconds = data[0]
    microseconds = data[1]
    return seconds + microseconds/1e6

class SetupReadInsn (object):
    def __init__(self, subdevice, channel, range, aref, n_scan):
        self.subdevice = subdevice
        self.channel = channel
        self.range = range
        self.aref = getattr(_comedi, 'AREF_{}'.format(aref.upper()))
        self.n_scan = n_scan

    def __call__(self, device, insn):
        insn.insn = _comedi.INSN_READ
        insn.n = self.n_scan
        data = _comedi.lsampl_array(self.n_scan)
        data.thisown = False
        insn.data = data.cast()
        insn.subdev = self._get_subdevice(device)
        insn.chanspec = _comedi.cr_pack(self.channel, self.range, self.aref)
        return insn

    def _get_subdevice(self, device):
        if self.subdevice is None:
            return _comedi.comedi_find_subdevice_by_type(
                device, _comedi.COMEDI_SUBD_AI, 0);
        return self.subdevice


def setup_insns(device, insn_setup_functions):
    n = len(insn_setup_functions)
    insns = _comedi.comedi_insnlist_struct()
    insns.n_insns = n
    array = _comedi.insn_array(n)
    array.thisown = False
    for i,setup in enumerate(insn_setup_functions):
        array[i] = setup(device, array[i])
    insns.insns = array.cast()
    return insns

def free_insns(insns):
    array = _comedi.insn_array.frompointer(insns.insns)
    array.thisown = True
    for i in range(insns.n_insns):
        insn = array[i]
        data = _comedi.lsampl_array.frompointer(insn.data)
        data.thisown = True


if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        '-f', '--filename', default='/dev/comedi0',
        help='path to comedi device file')
    parser.add_argument(
        '-s', '--subdevice', type=int, help='subdevice for analog input')
    parser.add_argument(
        '-c', '--channel', type=int, default=0,
        help='channel for analog input')
    parser.add_argument(
        '-a', '--analog-reference', dest='aref', default='ground',
        choices=['diff', 'ground', 'other', 'common'],
        help='reference for analog input')
    parser.add_argument(
        '-r', '--range', type=int, default=0, help='range for analog input')
    parser.add_argument(
        '-N', '--num-scans', type=int, default=10,
        help='number of analog input scans')
    parser.add_argument(
        '-v', '--verbose', default=0, action='count')

    args = parser.parse_args()

    if args.verbose >= 3:
        LOG.setLevel(_logging.DEBUG)
    elif args.verbose >= 2:
        LOG.setLevel(_logging.INFO)
    elif args.verbose >= 1:
        LOG.setLevel(_logging.WARN)

    if args.num_scans > MAX_SAMPLES:
        LOG.warn('requested too many samples, reducing to {}'.format(
                MAX_SAMPLES))
        args.num_scans = MAX_SAMPLES

    LOG.info(('measuring device={0.filename} subdevice={0.subdevice} '
              'channel={0.channel} range={0.range} analog reference={0.aref}'
              ).format(args))

    device = _comedi.comedi_open(args.filename)
    if not device:
        raise Exception('error opening Comedi device {}'.format(
                args.filename))

    setup_read_insn = SetupReadInsn(
        subdevice=args.subdevice, channel=args.channel,
        aref=args.aref, range=args.range, n_scan=args.num_scans)

    insns = setup_insns(
        device, [setup_gtod_insn, setup_read_insn, setup_gtod_insn])

    ret = _comedi.comedi_do_insnlist(device, insns)
    if ret != insns.n_insns:
        raise Exception('error running instructions ({})'.format(ret))

    ret = _comedi.comedi_close(device)
    if ret != 0:
        raise Exception('error closing Comedi device {} ({})'.format(
                args.filename, ret))

    array = _comedi.insn_array.frompointer(insns.insns)
    t1 = get_time_of_day(array[0])
    t2 = get_time_of_day(array[2])
    print('initial time: {}'.format(t1))
    print('final time:   {}'.format(t2))
    print('difference:   {}'.format(t2-t1))
    print('data:')
    data = _comedi.lsampl_array.frompointer(array[1].data)
    for i in range(array[1].n):
        print(data[i])

    free_insns(insns)