// Sanwa PC500, 510, 520M, 5000 との通信プログラム
// Copyright (c) 2005 Noriaki Mitsunaga
// $Id: pc5000.c,v 1.2 2005/11/15 00:08:35 jm3wud Exp jm3wud $

#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/ttycom.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>

#undef DEBUG

#ifdef __FreeBSD__
#define DEVICE "/dev/cuaa0"
#endif

#ifdef __LINUX
#define DEVICE "/dev/ttyS0"
#define NEED_NODELAY
#endif

// definitions
// 記号
#define STX 0x02
#define ETX 0x03
#define DLE 0x10

// 受信したデータ内での位置
#define IDX_CMD    2
#define IDX_BFUNC0 4
#define IDX_BFUNC1 5
#define IDX_BFUNC2 6
#define IDX_BFUNC3 7
#define IDX_PM     8
#define IDX_D1     9
#define IDX_D2     11
#define IDX_D3     12
#define IDX_D4     13
#define IDX_D5     14
#define IDX_D6     15
#define IDX_DPPM   17
#define IDX_DP     18

// 測定ファンクション
#define FUNC_AcV    0x5
#define FUNC_DcV    0x6
#define FUNC_AC_DcV 0x7   // AC + DcV
#define FUNC_Cx     0x8   // 容量測定 
#define FUNC_Dx     0x4   // ダイオードテスト PC5000
#define FUNC_Dx2    0x14  // ダイオードテスト PC500, 510, 520M
#define FUNC_TC1    0x0   // TC, TC(>1000 F, C) 温度測定数値が1000以上の場合
#define FUNC_TC     0x20  // TC
#define FUNC_TF     0x40
#define FUNC_Ohm    0x80
#define FUNC_Conti  0x180 // 導通テスト
#define FUNC_AcA    0x201
#define FUNC_DcA    0x202
#define FUNC_AC_DcA 0x203
#define FUNC_Hz     0x400
#define FUNC_Duty   0x800 // Duty%
#define FUNC_mA     0x802 // %mA (PC5000)
#define FUNC_dB     0x2000

// 受信データの種類
#define PC500_DATA_NORMAL 0
#define PC500_DATA_OVER   1

// 受信中のデータ
struct pc500_intdata {
  int idx;
  int mode;
  unsigned char data[22];
};

// 受信して整理したデータ
struct pc500_data {
  int lowbatt;
  int over;
  unsigned long function;
  int val;
  int vale;
};

// function declarations
int asy_init(int fd, speed_t speed);
static struct pc500_data* analyze(struct pc500_data *, struct pc500_intdata*, unsigned char rcv);
static struct pc500_data* analyze_recieved(struct pc500_data *, unsigned char *);
static void initialize_dmm(int asy);
static inline int myatoi(int c);
static void print_pc500_data(FILE *fp, struct pc500_data*);
static void send_request500(int asy);
static void send_request5000(int asy);

// inline functions
inline int
myatoi(int c)
{
  if (c<'0' || c>'9')
    return 0; // XXX
  else
    return c - '0';
}

// functions
void
print_pc500_data(FILE *fp, struct pc500_data* dat)
{
  switch (dat->function) {
  case FUNC_AcV:
    fprintf(fp, "AcV: ");
    break;
  case FUNC_DcV:
    fprintf(fp, "DcV: ");
    break;
  case FUNC_AC_DcV:
    fprintf(fp, "AC + DcV: ");
    break;
  case FUNC_Cx:
    fprintf(fp, "Cx: ");
    break;
  case FUNC_Dx:
  case FUNC_Dx2:
    fprintf(fp, "Dx: ");
    break;
  case FUNC_TC1:
  case FUNC_TC:
    fprintf(fp, "TC: ");
    break;
  case FUNC_TF:
    fprintf(fp, "TF: ");
    break;
  case FUNC_Ohm:
    fprintf(fp, "Ohm: ");
    break;
  case FUNC_Conti:
    fprintf(fp, "Conti: ");
    break;
  case FUNC_AcA:
    fprintf(fp, "AcA: ");
    break;
  case FUNC_DcA:
    fprintf(fp, "DcA: ");
    break;
  case FUNC_AC_DcA:
    fprintf(fp, "AC + DcA: ");
    break;
  case FUNC_Hz:
    fprintf(fp, "Hz: ");
    break;
  case FUNC_Duty:
    fprintf(fp, "Duty%: ");
    break;
  case FUNC_mA:
    fprintf(fp, "mA: ");
    break;
  case FUNC_dB:
    fprintf(fp, "dB: ");
    break;
  default:
    fprintf(fp, "Unknown: ");
    break;
  }

  if (dat->over == 1) {
    if (dat->val>0)
      fprintf(fp, "over flow +");
    else
      fprintf(fp, "over flow -");
  } else {
    fprintf(fp, "%d x 10^%d ", dat->val, dat->vale);
  }
  if (dat->lowbatt == 1) {
    fprintf(fp, "  low battery\n");
  } else {
    fprintf(fp, "\n");
  }
}

// Send data request command for PC500, 510, 520M
void
send_request500(int asy)
{
  static unsigned char req[] = {DLE, STX, 0x42, 0x00, 0x00, 0x00, DLE, ETX};
  
  write(asy, req, sizeof(req));
}

// Send data request command for PC5000
void
send_request5000(int asy)
{
  static unsigned char req[] = {DLE, STX, 0x00, 0x00, 0x00, 0x00, DLE, ETX};
  write(asy, req, sizeof(req));
}

// Analyze one char
struct pc500_data*
analyze(struct pc500_data *res, struct pc500_intdata *dat, unsigned char rcv)
{
  unsigned char *data = dat->data;

  switch (dat->idx) {
  case 0:
    if (rcv == DLE) {
      data[dat->idx ++] = rcv;
    }
    break;
  case 1:
    if (rcv == STX) {
      data[dat->idx ++] = rcv;
    } else {
      dat->idx = 0;
    }
    break;
  case IDX_CMD: // 2
    if (rcv == 0x00) {
      data[dat->idx ++] = rcv;
      dat->mode = PC500_DATA_NORMAL;
    } else if (rcv == 0x01) {
      data[dat->idx ++] = rcv;
      dat->mode = PC500_DATA_OVER;
    } else {
      dat->idx = 0;
    }
    break;
  case 3:
    if (dat->mode == PC500_DATA_NORMAL && rcv == 0x0f) {
      data[dat->idx ++] = rcv;
    } else if (dat->mode == PC500_DATA_OVER && rcv == 0x07) {
      data[dat->idx ++] = rcv;
    } else {
      dat->idx = 0;
    }
    break;
  case 12:
    if (dat->mode == PC500_DATA_NORMAL) {
      data[dat->idx ++] = rcv;
    } else if (dat->mode == PC500_DATA_OVER && rcv == DLE) {
      data[dat->idx ++] = rcv;
    } else {
      dat->idx = 0;
    }
    break;
  case 13:
    if (dat->mode == PC500_DATA_NORMAL) {
      data[dat->idx ++] = rcv;
    } else if (dat->mode == PC500_DATA_OVER && rcv == ETX) {
      data[dat->idx ++] = rcv;
      return analyze_recieved(res, data);
    } else {
      dat->idx = 0;
    }
    break;
  case 20:
    if (rcv == DLE) {
      data[dat->idx ++] = rcv;
    } else {
      dat->idx = 0;
    }
    break;
  case 21:
    if (rcv == ETX) {
      data[dat->idx] = rcv;
      dat->idx = 0;
      return analyze_recieved(res, data);
    } else {
      dat->idx = 0;
    }
    break;
  default:
    data[dat->idx ++] = rcv;
  }

  return NULL;
}

struct pc500_data* 
analyze_recieved(struct pc500_data *res, unsigned char *data)
{
  res->function = (data[IDX_BFUNC3] << 24) | (data[IDX_BFUNC2] << 16)
    | (data[IDX_BFUNC1] << 8) | data[IDX_BFUNC0];
  if (res->function & 0x8000)
    res->lowbatt = 1;
  else
    res->lowbatt = 0;
  res->function &= 0x7fff;

  if (data[IDX_CMD] == 0x00) {
    int val;
    int keta;

    res->over = 0;
    res->vale = data[IDX_DP] - '0';
    if (data[IDX_DPPM] == '-')
      res->vale = - res->vale;

    val = myatoi(data[IDX_D1]);
    val = val * 10 + myatoi(data[IDX_D2]);
    val = val * 10 + myatoi(data[IDX_D3]);
    val = val * 10 + myatoi(data[IDX_D4]);
    if (data[IDX_D5] != ' ') {
      // PC5000
      val = val * 10 + myatoi(data[IDX_D5]);
      if (data[IDX_D6] != ' ') {
	val = val * 10 + myatoi(data[IDX_D6]);
	res->vale -= 5;
      } else {
	res->vale -= 4;
      }
    } else {
      // PC500, 510,520M
      res->vale -= 3;
    }

    if (data[IDX_PM] == '-')
      res->val = -val;
    else
      res->val = val;
  } else {
    res->over = 1;
    if (data[IDX_PM] == '-') {
      res->val = -1;
    } else
      res->val = 1;
  }

  return res;
}

void
initialize_dmm(int asy)
{
  int status;

  /* get device status */
  if (ioctl(asy, TIOCMGET, &status) < 0) {
    fprintf(stderr, "ioctl: %s\n", sys_errlist[errno]);
    exit(1);
  }
  // DTR ON
  status |= TIOCM_DTR;
  // RTS OFF
  status &= ~TIOCM_RTS;
  if (ioctl(asy, TIOCMSET, &status) < 0) {
    fprintf(stderr, "ioctl: %s\n", sys_errlist[errno]);
    exit(1);
  }
  // wait 1[s]
  sleep(1);
  // RTS ON
  status |= TIOCM_RTS;
  if (ioctl(asy, TIOCMSET, &status) < 0) {
    fprintf(stderr, "ioctl: %s\n", sys_errlist[errno]);
    exit(1);
  }
  // wait 1[s]
  sleep(2);
}

int
asy_init(int fd, speed_t speed)
{
  // 通信仕様 9600bps, 8bit, non parity, stop bit 1, フロー制御なし
  // DTR ON, RTS ON
  struct termios  t;

  tcgetattr(fd, &t);

  t.c_iflag = (IGNBRK | IGNPAR);
  t.c_oflag = 0;
  t.c_lflag = 0;
  t.c_cflag = (CS8 | CREAD | CSTOPB | CLOCAL) & ~CRTSCTS;

  cfsetspeed(&t, speed);
  cfmakeraw(&t);

  t.c_cc[VTIME] = 1; /* 0.1 sec */
  t.c_cc[VMIN] = 0;
  tcsetattr(fd, TCSAFLUSH, &t);

  return 0;
}


int
main(int argc, char **argv)
{
  int asy, failcnt;
  struct pc500_intdata idata;
  struct pc500_data dat, *res;

  idata.idx = 0;

#ifdef NEED_NODELAY
  if((asy = open(DEVICE, O_RDWR | O_NDELAY))<0) {
    fprintf(stderr, "Colud not open serial port.\n");
    exit(1);
  }
#else
  if((asy = open(DEVICE, O_RDWR))<0) {
    fprintf(stderr, "Colud not open serial port.\n");
    exit(1);
  }
#endif

  asy_init(asy, B9600);
  initialize_dmm(asy);

  for(;;) {
    send_request5000(asy);

    failcnt = 0;
    for (;;) {
      unsigned char c;
      int sz = read(asy, &c, 1);
#ifdef DEBUG
      if (sz == 1) {
	fprintf(stderr, "%x %d\n", c, idata.idx);
      }
#endif

      if (sz == 1 && (res = analyze(&dat, &idata, c)) != NULL)
	break;
      if (sz == 1)
	failcnt = 0;
      else 
	failcnt ++;

      if (failcnt > 30) {
	// 3秒以上たってもデータが揃わない時
	res = NULL;
	fprintf(stderr, "Failed to get data\n");
	break;
      }
    }
    if (res != NULL)
      print_pc500_data(stdout, &dat);

    usleep(200*1000);
  }
}

