/*---------------------------------------------------------------------------
haakedriv.c

Driver for the Phoenix P1 HAAKE thermostat

Markus Zolliker, Nov 2006
----------------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
#include <math.h>
#include <tcl.h>
#include <fortify.h>
#include <sics.h>
#include <splitter.h>
#include <obpar.h>
#include <devexec.h>
#include <nserver.h>
#include <interrupt.h>
#include <emon.h>
#include <evcontroller.h>
#include <evcontroller.i>
#include <servlog.h>
#include <sicsvar.h>
#include <evdriver.i>
#include <rs232controller.h>
#include "fsm.h"
#include "ease.h"
#include "initializer.h"

#define HAAKE_ON 1
#define HAAKE_EXT 2
#define HAAKE_RESET 3

typedef struct {
  EaseDriv d;
  char *unit;
  float kelvin;
  float t;
  float t2;
  float set;
  int errcnt;
  int status;
  int running;
  int extcontrol;
  int relais;
  int overtemp;
  int lowlevel;
  int pumpalarm;
  int externalarm;
  int coolalarm;
  int sensor1alarm;
  int sensor2alarm;
  int with2sensors;
} Haake;


static ParClass haakeClass = { "HAAKE", sizeof(Haake) };

/*----------------------------------------------------------------------------*/
int HaakeHandler(void *object)
{
  int iret, l;
  Haake *drv = ParCast(&haakeClass, object);
  EaseBase *eab = EaseBaseCast(object);
  char *corr;

  assert(drv);
  if (eab->state < EASE_idle)
    goto quit;
  if (availableNetRS232(eab->ser) || availableRS232(eab->ser)) {
    eab->msg[0] = '\0';
    l = sizeof(eab->ans);
    iret = readRS232TillTerm(eab->ser, eab->ans, &l);
    if (eab->state != EASE_expect && eab->state != EASE_lost) {
      if (iret == 1) {
        ParPrintf(eab, eError, "unexpected answer: %s", eab->ans);
      }
      goto quit;
    }
    if (iret == 1) {
      ParPrintf(eab, -2, "ans: %s", eab->ans);

      if (eab->state == EASE_lost) {
        if (strcmp(eab->ans, "F001") == 0) {
          EaseWrite(eab, "V");
        }
        goto quit;
      }
      l = strlen(eab->ans) - 1;
      if (l >= 0 && eab->ans[l] != '$') {
        /* end char is not $ -> send again */
        ParPrintf(eab, -2, "ans: %s", eab->ans);
        if (drv->errcnt < 10) {
          eab->state = EASE_read;
          EaseWrite(eab, NULL);   /* send the same command again */
          drv->errcnt++;
          return 0;
        }
        eab->errCode = EASE_ILL_ANS;
        eab->state = EASE_idle;
        goto error;
      } else {
        drv->errcnt = 0;
      }


      if (eab->state == EASE_lost) {
        goto quit;
      } else if (eab->cmd[0] == 'V') {
        if (strcmp(eab->ans, eab->version) == 0) {
          /* we are still connected with the same device */
        } else if (*eab->version == '\0') {
          strlcpy(eab->version, eab->ans, sizeof(eab->version));
        } else {                /* version (and therefore device) changed */
          eab->errCode = EASE_DEV_CHANGED;
          eab->state = EASE_idle;
          goto error;
        }
        eab->state = EASE_idle;
        goto quit;
      } else {
        /* eab->tmo = 20; no need to change the timeout ? */
      }
    }
    if (iret != 1) {
      eab->errCode = iret;
      eab->state = EASE_idle;
      goto error;
    }
    eab->state = EASE_read;
  } else if (eab->state == EASE_expect) {
    if (time(NULL) > eab->cmdtime + eab->tmo) {
      eab->state = EASE_lost;
    }
  } else if (eab->state == EASE_lost) {
    if (time(NULL) > eab->cmdtime) {
      EaseWrite(eab, "check");
      eab->state = EASE_lost;
    }
  }
  goto quit;
error:
  /* EaseWriteError(eab); */
quit:
  return EaseHandler(eab);
}

/*----------------------------------------------------------------------------*/
static void HaakeParDef(void *object)
{
  Haake *drv = ParCast(&haakeClass, object);
  EaseBase *eab = object;
  int reset = 0;

  ParName("");
  ParFmt("%.2f");
  ParTail(drv->unit);
  ParFloat(&drv->t, PAR_NAN);

  ParName("unit");
  if (drv->unit == NULL) {
    ParAccess(usUser);
  }
  ParLogAs(NULL);
  ParStr(&drv->unit, NULL);

  ParName("t2");
  if (drv->with2sensors > 0) {
    ParFmt("%.2f");
    ParTail(drv->unit);
  }
  ParFloat(&drv->t2, PAR_NAN);

  ParName("set");
  ParFmt("%.2f");
  ParTail(drv->unit);
  ParFloat(&drv->set, PAR_NAN);

  ParName("running");
  EaseUpdate(HAAKE_ON);
  ParInt(&drv->running, 0);

  ParName("extcontrol");
  EaseUpdate(HAAKE_EXT);
  ParInt(&drv->extcontrol, 0);

  ParName("relais");
  ParInt(&drv->relais, 0);
  ParName("overtemp");
  ParInt(&drv->overtemp, 0);
  ParName("lowlevel");
  ParInt(&drv->lowlevel, 0);
  ParName("pumpalarm");
  ParInt(&drv->pumpalarm, 0);
  ParName("externalarm");
  ParInt(&drv->externalarm, 0);
  ParName("coolalarm");
  ParInt(&drv->coolalarm, 0);
  ParName("sensor1alarm");
  ParInt(&drv->sensor1alarm, 0);
  ParName("sensor2alarm");
  ParInt(&drv->sensor2alarm, 0);

  ParName("reset");
  ParAccess(usUser);
  ParSave(0);
  ParInt(&reset, 0);
  if (ParActionIs(PAR_SET) > 0) {
    EaseSetUpdate(drv, HAAKE_RESET, 1);
  }
  ParName("with2sensors");
  ParAccess(usUser);
  ParInt(&drv->with2sensors, 0);

  EaseBasePar(drv);
  EaseSendPar(drv);
  EaseDrivPar(drv, "%.2f", drv->unit);
  ParStdDef();
  EaseMsgPar(drv);
}

/*----------------------------------------------------------------------------*/
static long HaakeRead(long pc, void *object)
{
  Haake *drv = ParCast(&haakeClass, object);
  EaseBase *eab = object;
  char alarms[128];
  int status;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    EaseWrite(eab, "B");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (!EaseGetUpdate(drv, HAAKE_ON)) {
      drv->running = (eab->ans[0] != '0');
    }
    if (!EaseGetUpdate(drv, HAAKE_EXT)) {
      drv->extcontrol = (eab->ans[1] != '0');
    }
    drv->relais = (eab->ans[2] != '0');
    drv->overtemp = (eab->ans[3] != '0');
    drv->lowlevel = (eab->ans[4] != '0');
    drv->pumpalarm = (eab->ans[5] != '0');
    drv->externalarm = (eab->ans[6] != '0');
    drv->coolalarm = (eab->ans[7] != '0');
    drv->sensor1alarm = (eab->ans[10] != '0');
    drv->sensor2alarm = (eab->ans[11] != '0');

    EaseWrite(eab, "F1");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (eab->cmd[0] != 'F') goto fsm_quit;
    drv->t = atof(eab->ans) + drv->kelvin;
    if (drv->with2sensors < 1)
      goto nof2;
    EaseWrite(eab, "F2");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (eab->cmd[0] != 'F') goto fsm_quit;
    drv->t2 = atof(eab->ans) + drv->kelvin;
    if (drv->t2 < -222) {
      drv->t2 = PAR_NAN;
    }
  nof2:
    EaseWrite(eab, "S");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (eab->cmd[0] != 'S') goto fsm_quit;
    drv->set = atof(eab->ans) + drv->kelvin;

  skipGetSet:
    ParLog(drv);
  fsm_quit:return 0;
  }                             /* FSM END ******************************** */
}

/*----------------------------------------------------------------------------*/
static long HaakeStart(long pc, void *object)
{
  Haake *drv = ParCast(&haakeClass, object);
  EaseBase *eab = object;
  char unitcmd[8] = "W TE K";
  char msg[256];
  
  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    EaseWrite(eab, "V");
    return __LINE__;
  case __LINE__:                 /**********************************/
    if (0 != strncmp(eab->version, "1P/H", 4)) {
      snprintf(msg, sizeof msg,
               "unknown temperature controller version: %s", eab->version);
      EaseStop(eab, msg);
      goto quit;
    }
    ParPrintf(drv, eLog, "connected to haake thermostat %s",
              eab->version);
    if (drv->unit == NULL) {
      drv->unit = strdup("K");
    }
    if (*drv->unit == 'C') {
      EaseWrite(eab, "W TE C");
      drv->kelvin = 0;
    } else {
      EaseWrite(eab, "W TE K");
      drv->kelvin = 273.15;
    }
    return __LINE__;
  case __LINE__:                 /**********************************/
    FsmCall(HaakeRead);
    return __LINE__;
  case __LINE__:                 /**********************************/

  quit:
    return 0;
  }                             /* FSM END ******************************************* */
}

/*----------------------------------------------------------------------------*/
static long HaakeSet(long pc, void *object)
{
  Haake *drv = ParCast(&haakeClass, object);
  EaseBase *eab = object;
  char cmd[32];
  int upd;

  switch (pc) {
  default:                     /* FSM BEGIN ****************************** */
    EasePchk(drv);
    upd = EaseNextUpdate(drv);
    switch (upd) {
    case EASE_RUN:
      snprintf(cmd, sizeof cmd, "w sw %.5g",
               drv->d.targetValue - drv->kelvin);
      break;
    case HAAKE_ON:
      snprintf(cmd, sizeof cmd, "w ts%d", drv->running);
      break;
    case HAAKE_EXT:
      if (drv->extcontrol) {
        snprintf(cmd, sizeof cmd, "w ex");
      } else {
        snprintf(cmd, sizeof cmd, "w in");
      }
      break;
    case HAAKE_RESET:
      snprintf(cmd, sizeof cmd, "er");
      break;
    default:
      goto quit;
    }
    if (!drv->running) {
      ParPrintf(eab, eWarning, "WARNING: Haake is not running");
    }
    if (drv->relais || drv->overtemp || drv->lowlevel
        || drv->pumpalarm || drv->externalarm || drv->coolalarm
        || drv->sensor1alarm || drv->sensor2alarm) {
      ParPrintf(eab, eWarning, "WARNING: Haake alarm");
    }
    EaseWrite(eab, cmd);
  quit:
    return 0;
  }                             /* FSM END ******************************************* */
}

/*----------------------------------------------------------------------------*/
static int HaakeInit(SConnection * con, int argc, char *argv[],
                     int dynamic)
{
  /* args:
     MakeObject objectname haake <rs232>
     MakeObject objectname haake <host> <port>
   */
  Haake *drv;

  drv = EaseMakeDriv(con, &haakeClass, argc, argv, dynamic, 7,
                     HaakeParDef, HaakeHandler, HaakeStart, NULL,
                     HaakeRead, HaakeSet);
  if (drv == NULL)
    return 0;
  setRS232ReplyTerminator(drv->d.b.ser, "\r");
  setRS232SendTerminator(drv->d.b.ser, "\r");
  drv->d.b.tmo = 5;
  return 1;
}

/*----------------------------------------------------------------------------*/
void HaakeStartup(void)
{
  ParMakeClass(&haakeClass, EaseDrivClass());
  MakeDriver("HAAKE", HaakeInit, 0, "HAAKE Phoenix P1 thermostat");
}
