// Monitor shared memory and make control decisions

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "../io/io.h"

#define NOMODE 0
#define OILMODE 1
#define WOODMODE 2
#define TANKMODE 3

#define TANK_BOTTOM shm->ad[0].value
#define TANK_MIDDLE shm->ad[1].value
#define TANK_TOP shm->ad[2].value
#define SOLAR_IN shm->ad[3].value

#define WOOD_OUT shm->ad[4].value
#define WOOD_IN shm->ad[5].value
#define OIL_OUT shm->ad[6].value
#define HOT_WATER shm->ad[7].value

main(){
  float limit1, limit2;
  int mode;
  int i,j,demand, bbdemand;
  byte htwarm, dhwwarm, htcomfort, oil_wood_sw, last_oil_wood_sw, testbit;
  byte stage, laststage, dio0, dio1, dio2, dio3, rmask, newbit, oldbit;
  byte bfwarm, mfwarm, tfwarm;
  byte stages[20];
    
  struct timeval now;
  long start_usec;
  long now_usec;
  long wood_end_time;
  //time_t timestamp;

  int shmid;
  key_t key;
  char *shmat();
  //short *shm, *s;
  shmstruct *shm;
   
  // Key for our shared memory is 9700
  
  key = 9700;
  
  // Create our shared memory
  if ((shmid = shmget(key, sizeof(shmstruct), 0666)) < 0) {
    perror("shmget");
    exit(1);
  }
  
  // Link to our shared memory
  if ((shm=shmat(shmid, NULL, 0)) == (char *) -1) {
    perror("shmat");
    exit(1);
  }
  
  gettimeofday(&now,NULL);
  start_usec = now.tv_sec * 1000000 + now.tv_usec;

  // Set direction bytes
  
  // dio 0 is input
  shm->dio[0].dir = 0;
     
  // dio 1 is output
  shm->dio[1].dir = 255;
     
  // dio 2 is input
  shm->dio[2].dir = 0;
     
  // dio 3 is output
  shm->dio[3].dir = 255;
     
  // Intialaize mode to nomode
  
  mode = NOMODE;
  last_oil_wood_sw = 0;
  
  gettimeofday(&now,NULL);
  wood_end_time = now.tv_sec -1;
      
  // Do forever
  while (1){
    //timestamp = time(NULL);   
    gettimeofday(&now,NULL);

    // Read input byte(s)
    dio0 = shm->dio[0].value;
    
    // Strip out nodemand bit
    demand = dio0 & 0x01;
    // Strip out hot tub demand bit
    htwarm = dio0 & 0x02;
    // Strip out hot tub comfort (not econ)  bit
    htcomfort = dio0 & 0x04;
    // Strip out oil/wood switch  bit
    oil_wood_sw = dio0 & 0x08;
    // Strip out DHW Warm bit
    dhwwarm = dio0 & 0x10;
    // Strip out top floor Warm bit
    tfwarm = dio0 & 0x20;
    // Strip out main floor Warm bit
    mfwarm = dio0 & 0x40;
    // Strip out bottom floor Warm bit
    bfwarm = dio0 & 0x80;
    
    // Strip out testbit
    testbit = dio0 & 0x80;
    
    // Read output byte(s) to get current state
    dio1 = shm->dio[1].value;
    
    // Previous state(s)
    laststage = stage;
    
    // ************************************************************************************************ 
    // Figure out mode   
    // If wood boiler outlet is hot, wait an hour before giving up on wood
    // Get oil+wood switch and compare to previous state
    // If oil/wood is closed and was not closed on previous cycle, reset end time.
    // ************************************************************************************************ 
    
    if ((WOOD_OUT > 55) || ((last_oil_wood_sw==0) && (oil_wood_sw != 0))){
      wood_end_time = now.tv_sec + 3600;
    }
    last_oil_wood_sw = oil_wood_sw;
   
    // Hysteresis
    if (mode == TANKMODE){
      limit2 = 49;
    }else{
      limit2 = 51;
    }
    
    // If woodmode timer has expired, check if tank is hot enough to be usefull...
    if (wood_end_time < now.tv_sec){
      if (TANK_TOP > limit2){
        mode = TANKMODE;
      }else{
        mode = OILMODE;
      }
    }else{
      mode = WOODMODE;
    }
    
    // ************************************************************************************************ 
    // Rule details are in documentation for each bit.
    // In general, there may be different rules depending on the heat source.
    // In addition to detailed rules, there is a set of general priorities:
    //
    // In wood mode:
    // A) Keep output temp within bounds. Open recirc and limit active zones to raise temp. Open more
    //    zones to lower temp.
    // B) Heat Domestic Hot Water if it's cold enough to ask for heat.
    // C) Heat the hot tub if it's cold and 'comfort' is selected
    // D) Heat the living space
    // E) Heat the hot tub
    // F) Heat DHW to 60 degrees C
    // G) Heat the storage tank
    //
    // In tank mode:
    // A) Heat Domestic Hot Water if it's cold enough to ask for heat and the tank is hot enough to help.
    // B) Heat the hot tub if it's cold and 'comfort' is selected and the tank is hot enough to help.
    // C) Heat the living space.
    //
    // Based on demand, determine which stages are valid.
    // Based on output temp, pick a stage
    // ************************************************************************************************ 
    
    stage = 0;    // Stage 0 is startup
    stage[0] = 1; // Stage 0 is always possible
    
    // Intialize other stages to 'not valid'
    for (i=1;i<20;i++){
      stages[i] = 0;
    }
    
    // Baseboard demand means main and/or top floor need heat
    bbdemand = !mfwarm || !tfwarm; 
    
    // *********************** If we have enough heat to be useful, figure out stage
    if(oldstage > 0){
      limit1=61;
    }else{
      limit1=60;
    }
      
    // *********************** Stage 1: DHW is really cold - we need to heat the hot water first
    if (!dhwwarm){
      stage[1] = 1;
    }
      
    // *********************** Stage 2: We need to heat the hot tub before the other zones
    if (dhwwarm && !htwarm && htcomfort){
      stage[2] = 1;
    }

    // *********************** Stage 3: Main floor heat
    if (!mfwarm){
      stage[3] = 1;
    }
      
    // *********************** Stage 4: Top floor heat
    if (!tfwarm){
      stage[4] = 1;
    }
      
    // *********************** Stage 5: Bottom floor heat
    if (!bfwarm){
      stage[5] = 1;
    }
      
    // *********************** Stage 6: Hot tub
    if (!htwarm){
      stage[6] = 1;
    }
      
    // *********************** Stage 7: Superheat DHW
    if (HOT_WATER < ){
      stage[6] = 1;
    }
      
      // Set limits for 
      if(oldbit){
        limit1=61;
        limit2=66;
      }else{
        limit1=60;
        limit2=65;
      }
      // If done with stage 1 and 2 and baseboard demand and hot enough, open another zone
      if ((stage == 0) && bbdemand &&  (WOOD_OUT > limit2)){
        switch (laststage) {
          case (<3) : stage = 3; break;    // Main floor
          case (3) : stage = 4; break;     // Top floor
          case (4) : stage = 5; break;     // Bottom floor
        }   
      }
    }    
    
        
    // ************************************************************************************************ 
    // DIO 3 bit 0: Wood Mode 
    // 
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 0;
    oldbit = dio3 & rmask;
    
    if (mode == WOODMODE){
      newbit = rmask;
    }else{
      newbit = 0;
    }
    
    // Insert new bit value
    dio3 = dio3 & (~rmask);
    dio3 = dio3 | newbit;
    
    // ************************************************************************************************ 
    // DIO 3 bit 1: Oil Mode 
    // 
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 1;
    oldbit = dio3 & rmask;
    
    if (mode == OILMODE){
      newbit = rmask;
    }else{
      newbit = 0;
    }
    
    // Insert new bit value
    dio3 = dio3 & (~rmask);
    dio3 = dio3 | newbit;
    
    // ************************************************************************************************ 
    // DIO 3 bit 2: Tank Mode 
    // 
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 2;
    oldbit = dio3 & rmask;
    
    if (mode == TANKMODE){
      newbit = rmask;
    }else{
      newbit = 0;
    }
    
    // Insert new bit value
    dio3 = dio3 & (~rmask);
    dio3 = dio3 | newbit;
    
    // ************************************************************************************************ 
    // DIO 1 bit 0: Hot water force
    // Assert demand for hot water: open hot water zone valve.
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 0;
    oldbit = dio1 & rmask;
    
    // Hysteresis
    if(oldbit){
      limit1=61;
      limit2=73;
    }else{
      limit2=72;
      limit1=60;
    }
    
    newbit = 0;
    
    // First rule: If there's demand for hot water, satisfy it first.
    if ((mode == WOODMODE) && !dhwwarm && (WOOD_OUT > 65)){
      newbit = rmask;
    }
  
    if ((mode == WOODMODE) && (HOT_WATER < limit1)){
      if (htwarm && (WOOD_OUT > (HOT_WATER + 2))){
      //if (!demand && htwarm && (WOOD_OUT > HOT_WATER)){
        newbit = rmask;
      }
    }
    
    // Insert new bit value
    dio1 = dio1 & (~rmask);
    dio1 = dio1 | newbit;
    
    // ************************************************************************************************ 
    // DIO 1 bit 1: Hot Tub Control
    // Asserts TS7260 control over hot tub zone valve
    // Always asserted
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 1;
    oldbit = dio1 & rmask;
    
    newbit = rmask;
    
    // Insert new bit value
    dio1 = dio1 & (~rmask);
    dio1 = dio1 | newbit;
    
    // ************************************************************************************************ 
    // DIO 1 bit 2: Wood Mode force
    // This is actually the relay that disables oil demand - should be called 'oil disable'
    // 
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 2;
    oldbit = dio1 & rmask;
    
    if ((mode == WOODMODE) || (mode == TANKMODE)){
      newbit = rmask;
    }else{
      newbit = 0;
    }
    
    // Insert new bit value
    dio1 = dio1 & (~rmask);
    dio1 = dio1 | newbit;
    
    // ************************************************************************************************ 
    // DIO 1 bit 3: Wood recirc 
    // Never if not woodmode
    // Never if wood is cold (<55)
    // On if either wood outlet or inlet is below target operating temps.
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 3;
    oldbit = dio1 & rmask;

    // Hysteresis
    if(oldbit){
      limit2=67;
      limit1=63;
    }else{
      limit2=65;
      limit1=58;
    }
              
    if ((mode == WOODMODE) && ((WOOD_OUT < limit2) || (WOOD_IN < limit1)) && (WOOD_OUT > 55)){
      newbit = rmask;
    }else{
      newbit = 0;
    }
    
    // Insert new bit value
    dio1 = dio1 & (~rmask);
    dio1 = dio1 | newbit;
        
    // ************************************************************************************************ 
    // DIO 1 bit 4: Heat storage tank circulator pump 
    // Test: If testbit is zero, turn on circ
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 4;
    oldbit = dio1 & rmask;
    // Hysteresis
    if(oldbit){
      limit2=2;
    }else{
      limit2=3;
    }
    
    if ((mode == TANKMODE) && demand){
      newbit = rmask;
    }else{
      newbit = 0;
    }
        
    // Insert new bit value
    dio1 = dio1 & (~rmask);
    dio1 = dio1 | newbit;
    
    // ************************************************************************************************ 
    // DIO 1 bit 5: Heat storage tank zone valve relay 
    // If boiler outlet is more than 10 degrees C above the tank middle and there is no demand and the hot tub is warm, turn on ht zv relay
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 5;
    oldbit = dio1 & rmask;
    // Hysteresis
    if(oldbit){
      limit2=77;
    }else{
      limit2=75.5;
    }
              
    newbit = 0;
    
    if(mode == WOODMODE){
      
      // If overheating, dump to tank.
      if (WOOD_OUT > limit2){
        newbit = rmask;
      }
      
      // If there's no other demand, dump to tank conditionally
      if ((WOOD_OUT > 55) && htwarm && !demand) {
         
        // If there's enough temperature difference to be useful
        if (WOOD_OUT > (TANK_TOP+2)) {
        
          // If there's no hot water demand
          //if((HOT_WATER >= 60) || (HOT_WATER > (TANK_BOTTOM + 2))) {
          if(HOT_WATER >= 60) {
            newbit = rmask;
          }
        }
      }
    }
    
    if(mode == OILMODE){
      // oil mode
      if ((OIL_OUT > 40) && (OIL_OUT > (TANK_TOP)) && !demand && (htwarm || (OIL_OUT <= 45))){
        newbit = rmask;
      }else{
        newbit = 0;
      }
    }
    
    // Insert new bit value
    dio1 = dio1 & (~rmask);
    dio1 = dio1 | newbit;
    
    // ************************************************************************************************ 
    // DIO 1 bit 6: hot tub zone valve relay 
    // If boiler outlet is above 45 degrees C and there is no demand and the hot tub is cold, turn on ht zv relay
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 6;
    oldbit = dio1 & rmask;
    
    // Hysteresis
    if(oldbit){
      limit2=73;
    }else{
      limit2=74;
    }
    
    newbit = 0;
    
    if (mode == OILMODE && !htwarm){
      if ((OIL_OUT > 45) && !demand){
        newbit = rmask;
      }
    }
    
    if ((mode == WOODMODE) && !htwarm){
      if ((WOOD_OUT > limit2) || (!demand && (WOOD_OUT > 60))){
        newbit = rmask;
      }
    }

    // Not from tank for now
    if (mode == TANKMODE){
      newbit = 0;
    }
    
    // Hack for hot tub 'normal' mode
    
    if (!htwarm && htcomfort){
      newbit = rmask;
    }
    
    // Insert new bit value
    dio1 = dio1 & (~rmask);
    dio1 = dio1 | newbit;
    
    // ************************************************************************************************ 
    // DIO 1 bit 7: oil circ force enable - allows oil circ force if tank or hot tub zv limits are closed 
    // If boiler outlet is above 40 degrees C and there is no demand, turn on circ force enable relay
    // ************************************************************************************************ 
    
    // Create mask, capture old bit value
    rmask = 1 << 7;
    oldbit = dio1 & rmask;
    
    if (mode == OILMODE && (OIL_OUT > 40) && !demand){
      newbit = rmask;
    }else{
      newbit = 0;
    }
    
    // Insert new bit value
    dio1 = dio1 & (~rmask);
    dio1 = dio1 | newbit;
    
    // ************************************************************************************************ 
    // write results to shared memory
    // sleep until next 35 second interval
    // ************************************************************************************************ 
    
    shm->dio[1].value = dio1;
    shm->dio[3].value = dio3;
        
    // Sleep for 35 second interval
    start_usec += 35000000;
    gettimeofday(&now,NULL);
    now_usec = now.tv_sec * 1000000 + now.tv_usec;
    
    usleep(start_usec - now_usec);
  }
}

