/* Main program loop */
/*         $Id: thermo.c,v 1.7 2001/09/24 22:44:40 leon Exp $         */
/* BUGS: startup sensor read generates line fault. Subsequent are OK */

#pragma SYMBOLS

#include <reg515.h>
#include <intrins.h>

#include "display.h"
#include "serial.h"
#include "ds1307.h"
#include "setup.h"


/* DS1820 device number selection:
   0 = boiler
   1 = hot-water container
   2..4 = circuit temp
*/

#undef TEST_SETUP
#ifdef TEST_SETUP 
  const char code thermometer[] = {9, 10, 11, 11, 11, 11, 6, 13, 14}; 
#else
  const char code thermometer[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; 
#endif


#define MIXING_VALVE_HOLD_TIME 120 /* time between motor movements in sec */



extern void ds1820_start(unsigned char device_number);
extern void ds1820_init();
extern unsigned char data touch_byte;
extern unsigned int data convert_time;
extern bit ds1820error;


sbit APD = 0xF8;   /* P5.0 DS1820 line  */

sbit SER = 0x94;   /* P1.4 Output Module Serial Input */
sbit RCK = 0x96;   /* P1.6 Output Module Reload Clock */
sbit SCK = 0x97;   /* P1.7 Output Module Serial Clock */


sbit BURNER_OPERATING = 0xB5; /* P3.5 negated! */
sbit BURNER_FAULT     = 0xB4; /* P3.4 negated! */


static signed char mixing_valve_timer[4]; /* > 0 rotating, < 0 holding */
static bit output_update_hint;

static unsigned char bdata pump_status;
static unsigned char bdata motor_status;
sbit burner           = pump_status ^ 0;
sbit hot_water_pump   = pump_status ^ 1;
sbit underfloor_pump0 = pump_status ^ 2;
sbit underfloor_pump1 = pump_status ^ 3; 
sbit underfloor_pump2 = pump_status ^ 4;
sbit underfloor_pump3 = pump_status ^ 4; /* Same relais for both circuits!*/
sbit circulator_pump  = pump_status ^ 5;
sbit solar_pump       = pump_status ^ 6;
sbit inter_tank_pump  = pump_status ^ 7;


/* disabled until CMOS RAM will be expanded  */
#if 0
unsigned long idata pump_operating_time[8];
unsigned long idata pump_start_time[8];
#endif


static unsigned char seconds_elapsed; /* for rare checks such as pump timers */

static void 
external0_init()
{
  IP0 = IP0 & 0xFC; /* lowest interrupt priority (0) */
  IT0 = 1;
  EX0 = 1; /* enable external interrupt 0*/
}

static void
external0 (void) interrupt 0 using 1
{
  const unsigned char code mixing_valve_motor_stop_mask[4] = 
  {0xFC, 0xF3, 0xCF, 0x3F};
  unsigned char valve;
  
  if(!BURNER_OPERATING)
    burner_seconds++;

  seconds_elapsed ++;
    
  for(valve = 0; valve < 4; valve++)
    {
      if (mixing_valve_timer[valve]) 
                {
                  if(mixing_valve_timer[valve] > 0) /* motor is rotating ?*/
                        {
                          if(--mixing_valve_timer[valve] == 0)
                                {
                                  motor_status &= mixing_valve_motor_stop_mask[valve];
                                  mixing_valve_timer[valve] = -MIXING_VALVE_HOLD_TIME;
                                  output_update_hint = 1;
                                }
                        }
                  else /* motor is holding ?*/
                        ++mixing_valve_timer[valve];
                }
    }          
}

void
output_update()
{
  unsigned char i, sbuf;
  GATE = 0; 
  SER = 0;
  SCK = 0;
  RCK = 0;
  sbuf = motor_status;
  set_leds(pump_status);
  for(i = 0; i < 8; i++)
    {
      SER = sbuf & 0x80 ? 1 : 0;
      SCK = 0;
      sbuf <<= 1;
      SCK = 1;
    }
  
  sbuf = pump_status;
  for(i = 0; i < 8; i++)
    {
      SER = sbuf & 0x80 ? 1 : 0;
      SCK = 0;
      sbuf <<= 1;
      SCK = 1;
    }
  
  RCK = 1;
  SCK = 0;
}

void 
mixing_valve_motor_open(int valve_number, unsigned char burner_seconds)
{
  motor_status |= 1 << 2*valve_number;
  mixing_valve_timer[valve_number] = burner_seconds;
  output_update();
}

void 
mixing_valve_motor_close(int valve_number, unsigned char burner_seconds)
{
  motor_status |= 2 << 2*valve_number;
  mixing_valve_timer[valve_number] = burner_seconds;
  output_update();
}

    

int 
get_temperature()
{
  struct SCRATCHPAD {
    unsigned char temp_lsb;
    unsigned char temp_msb;
    unsigned char user_1;
    unsigned char user_2; /* positive calibration */
    int reserved;
    unsigned char count_remain;
    unsigned char count_per_c;
    unsigned char crc;
  };
  extern struct SCRATCHPAD scratchpad;

  int d;

  d = (scratchpad.count_per_c - scratchpad.count_remain) * 100;
  d /= scratchpad.count_per_c;

  return 50*(int)(scratchpad.temp_msb << 8 | scratchpad.temp_lsb & 0xFE)
         - 25 + d  + scratchpad.user_2 ; 
}



void 
main()
{

  unsigned char crc_error_counter = 0;
  unsigned char ds1820_device_error_counter = 0;
  unsigned char device = 5; /* Always start with outdoor temp device */
  int underfloor_temp_correction = 0;
  int temp;
  bit night_time = 0; /* Are we in the night or day? */
  bit blanking;   /* blanking of display enabled? */
  bit force_inter_tank_pump = 0; /* force pumping once a day */
  bit need_for_heat = 0;
  

  APD = 0; /* Power-ON the one-wire line */

  set_defaults();

  motor_status = 0x00;
  pump_status = 0x00;
  

  
  mixing_valve_timer[0] =   mixing_valve_timer[1] = 
    mixing_valve_timer[2] =   mixing_valve_timer[3] = 0;

  mixing_valve_motor_close(0, 120); /* close all valves */
  mixing_valve_motor_close(1, 120);
  mixing_valve_motor_close(2, 120);
  mixing_valve_motor_close(3, 120);
  
  output_update_hint = 0;
  
  delay(60000);
  
  lcd_init();
  lcd_print("PROM | " __DATE__ "\nDATE | " __TIME__);

  /*  serial_init(); */

  delay(60000); /* give time to power DS1820 line */
  delay(60000);
  delay(60000);
  delay(60000);
  


  ds1307_init();      
  burner_seconds =  ds1307_get_ulong(BURNER_SECONDS_NVRAM_ADDR);
  solar_pump_minutes = ds1307_get_int(BURNER_SECONDS_NVRAM_ADDR+4);
  lcd_print("Initializing\nthermometers");
  ds1820_init();    


  seconds_elapsed = ds1307_get_bcd_seconds();
  BCD_TO_DECIMAL(seconds_elapsed);   /* sync with the RTC clock */
  external0_init();                  /* run the 1 second interrupt */
  ds1820_start(thermometer[device]); /* bulky start */
  
  lcd_print("Loading default\nparameters");
  load_parameters();

  lcd_print("Starting pumps");
  underfloor_pump0 = underfloor_pump0_enabled;
  underfloor_pump1 = underfloor_pump1_enabled;
  underfloor_pump2 = underfloor_pump2_enabled;
  underfloor_pump3 = underfloor_pump3_enabled;

  circulator_pump = 0;
  blanking = 0;
  if(!KEY_YES) /* at startup disable reading of the solar sensors */
        {
          solar_pump_enabled = 0;
          display_solar_temperatures = 0;
        }

  if(!KEY_NO) /* at startup reset to factory defaults! but not permanent*/
        {
          set_defaults();
        }

  if(!KEY_UP) /* clear solar pump counter */
        solar_pump_minutes = 0;
  
  output_update();

  lcd_send(0,1);/* clear LCD */
  show_time();
  
  while (1) 
    {
      if(output_update_hint)
                {
                  output_update();
                  output_update_hint = 0;
                }


      if(!KEY_UP | !KEY_DOWN)
                {
                  setup();
                  
                  if (!hot_water_pump) /* Imediate change pump status after setup */
                        {
                          if (underfloor_pump0 &&  !underfloor_pump0_enabled)
                                mixing_valve_motor_close(0, 120);
                          underfloor_pump0 = underfloor_pump0_enabled;
                          if (underfloor_pump1 &&  !underfloor_pump1_enabled)
                                mixing_valve_motor_close(1, 120);
                          underfloor_pump1 = underfloor_pump1_enabled;
                          if (underfloor_pump2 &&  !underfloor_pump2_enabled)
                                mixing_valve_motor_close(2, 120);
                          underfloor_pump2 = underfloor_pump2_enabled;
                          if (underfloor_pump3 &&  !underfloor_pump3_enabled)
                                mixing_valve_motor_close(3, 120);
                          underfloor_pump3 = underfloor_pump3_enabled;
                        }
                  
                  if (!solar_pump_enabled)
                        solar_pump = 0;
                  if (!circulator_pump_enabled)
                        circulator_pump = 0;
                  if (!inter_tank_pump_enabled)
                        inter_tank_pump_enabled = 0;
                  blanking = 0; /* do not blank when exiting setup */
                  output_update_hint = 1;
                  show_time();
                }

          if (!KEY_YES | !KEY_NO)
                {
                  blanking = 0;
                }
          

      if(!ET1)
                {
                  extern bit ds1820crc_ok(void);
                  extern bit ds1820error;
                  extern unsigned char ds1820crc;
                  extern unsigned char errornum;
                  
                  if (ds1820error)
                        switch(errornum)
                          {
                          case 1: halt(device + 1, "1-wire line GND\nshort circuit!");
                          case 2:
                                if(ds1820_device_error_counter++ > 100)
                                  halt(device + 1, "1-wire fault or\nDS1820 missing!");

                                ds1820_start(thermometer[device]);
                                continue; 
                          default:halt(device + 1, "ds1820 Unknown\nerror");
                          }
                  if (!ds1820crc_ok())
                        {
                          crc_error_counter++;
                          if (crc_error_counter > 99)
                                halt(device + 1, "To many ds1820\nCRC errors (100)");
                          lcd_print_uchar(0x0F, crc_error_counter);
                          ds1820_start(thermometer[device]);
                          continue;
                        }
                  
                  temp = get_temperature();
                  
                  switch (device)
                        {
                          unsigned char circuit;
                          int boiler_temperature;
                          int hot_water_tank_temp;
                          int collector_temperature;
                          
                        case 0: /* boiler temperature */
                          boiler_temperature = temp;
                          if (blanking)
                                icm_blank();
                          else
                                icm1(boiler_temperature);
                          if(need_for_heat && burner_enabled && temp < boiler_min_temp)
                                {
                                  burner = 1;
                                  output_update();
                                }
                          if(temp > boiler_max_temp)
                                {
                                  burner = 0;
                                  need_for_heat = 0;
                                  output_update();
                                }
                          break;

                        case 1: /* hot water container temperature */
                          if (blanking)
                                icm_blank();
                          else
                                icm2(temp);
                          hot_water_tank_temp = temp;
                          if (hot_water_pump_enabled
                                  && !hot_water_pump
                                  && temp < hot_water_min_temp 
                                  && burner_enabled
                                  && !night_time /* we don't assure correct temp at night */
                                  && boiler_temperature > temp + 500)
                                {
                                  underfloor_pump0 = underfloor_pump1 = /* stop the pumps */
                                        underfloor_pump2 = underfloor_pump3 = 0; 
                                  output_update();
                                  delay(1000);
                                  hot_water_pump = 1;
                                  need_for_heat = 1;
                                  output_update();
                                }
                          else if (hot_water_pump)
                                if ( (temp > hot_water_max_temp)
                                         || (boiler_temperature - temp < 300))
                                  {
                                        hot_water_pump = 0;
                                        need_for_heat = 0;
                                        output_update();
                                        delay(1000);
                                        underfloor_pump0 = underfloor_pump0_enabled; /* restart */
                                        underfloor_pump1 = underfloor_pump1_enabled;
                                        underfloor_pump2 = underfloor_pump2_enabled;
                                        underfloor_pump3 = underfloor_pump3_enabled;
                                        output_update();
                                  }
                          
                          break;
                        case 5: /* environment temperature */
                          set_serial_display(3, temp);
                          underfloor_temp_correction = 
                                - ((long)temp * (long)temperature_slope)/100;
                          if (night_time)
                                underfloor_temp_correction -= night_temp_offset;
                          
                          update_serial_display(blanking);
                          break;
                          /* Circuit #3 is disabled and used as outdoor temperature */
                          
                        case 2: /* underfloor sensors */
                          if(!display_solar_temperatures) /* display temp anyway */
                                {
                                  set_serial_display(0, temp);
                                  update_serial_display(blanking);
                                }
                          if (temp > 4000)
                                halt(66, "Underfloor temp.\nto high! (>40" "\xdf" "C)");
                          if (!underfloor_pump0 || !underfloor_pump0_enabled) 
                                break; /* do not move the motor if pump off */
                          goto valve_motor_check;
                        case 3:
                          if(!display_solar_temperatures)
                                {
                                  set_serial_display(1, temp);
                                  update_serial_display(blanking);
                                }
                          if (temp > 4000)
                                halt(67, "Underfloor temp.\nto high! (>40" "\xdf" "C)");
                          if (!underfloor_pump1 || !underfloor_pump1_enabled)
                                break;
                          goto valve_motor_check;
                        case 4:
                          if(!display_solar_temperatures )
                                {
                                  set_serial_display(2, temp);
                                  update_serial_display(blanking);
                                }
                          if (temp > 4000)
                                halt(69, "Underfloor temp.\nto high! (>40" "\xdf" "C)");
                          if (!underfloor_pump2 || !underfloor_pump2_enabled)
                                break;
                          
                        valve_motor_check:
                          circuit = device - 2;
                          need_for_heat = 1;
                          

                          if (!hot_water_pump) /* do not move if heating DHW tank */
                                {
                                  if(mixing_valve_timer[circuit] == 0) /* can move valve */
                                        {
                                          int dt; /* delta time: correction time for the motor */
#define CTC (underfloor_temp[circuit] + underfloor_temp_correction)
                                          if(temp >= CTC)
                                                {
                                                  dt = (temp - CTC)*3; /* 3 sec for 1K */
                                                  dt /= 100;
                                                  if (dt > 12)
                                                        dt = 12; /* limit the time */
                                                  if (dt > 0)
                                                        mixing_valve_motor_close(circuit, dt);
                                                }
                                          else
                                                {
                                                  dt = (CTC - temp)*2; /* 2s for 1K*/
                                                  dt /= 100;
                                                  if (dt > 10)
                                                        dt = 10; /* limit the time to 8% movement */
                                                  if (dt > 0)
                                                        mixing_valve_motor_open(circuit, dt);
                                                }
                                        }
                                }
                          break;
                        case 6:/* solar collectors temperature */

                          if (display_solar_temperatures)
                                {
                                  set_serial_display(0, temp);
                                  update_serial_display(blanking);
                                }
                          collector_temperature = temp;
                          break;
                        case 7: /* solar tank temperature */
                          if (display_solar_temperatures)
                                {
                                  set_serial_display(1, temp);
                                  update_serial_display(blanking);
                                }
                          
                          if (inter_tank_pump_enabled)
                                {
                                  if (!inter_tank_pump
                                          && temp > inter_tank_trigger_temperature
                                          && temp - hot_water_tank_temp
                                          > inter_tank_temp_difference + 200) /* +hysteresis */
                                        {
                                          inter_tank_pump = 1;
                                          output_update_hint = 1;
                                        }
                                  if (!inter_tank_pump
                                          && temp > max_solar_temperature - (int)500)
                                        {
                                          inter_tank_pump = 1;
                                          output_update_hint = 1;
                                        }
                                  if (inter_tank_pump && !force_inter_tank_pump
                                          && temp - hot_water_tank_temp
                                           <  inter_tank_temp_difference)
                                        {
                                          inter_tank_pump = 0;
                                          output_update_hint = 1;
                                        }
                                }
                          break;
                        case 8: /* heat exchanger in solar tank temperature */ 
                          if (display_solar_temperatures)
                                {
                                  set_serial_display(2, temp);
                                  update_serial_display(blanking);
                                }

                          /* protect collectors against freezing */
                          if (!solar_pump && temp < -1800)
                                {
                                  solar_pump = 1;
                                  output_update_hint = 1;
                                  break;
                                }

                          if (solar_pump_enabled)
                                {
                                  if (!solar_pump && temp < max_solar_temperature
                                          && temp + collector_temp_difference + 200
                                          < collector_temperature)
                                        {
                                          solar_pump = 1;
                                          output_update_hint = 1;
                                        }
                                  else
                                        if (solar_pump && temp + collector_temp_difference
                                                > collector_temperature)
                                          {
                                                solar_pump = 0;
                                                output_update_hint = 1;
                                          }
                                  
                                }
                          break;
                        } /* switch(device) */


                  ++ device;
                  
                  if ((device == 9
                           && (solar_pump_enabled || display_solar_temperatures ))
                          || (device == 6 &&
                                  (!solar_pump_enabled && !display_solar_temperatures)))
                        {
                          device = 0;

                          lcd_print_hms(0x4F, burner_seconds);
                          ds1307_set_ulong(BURNER_SECONDS_NVRAM_ADDR, burner_seconds);
                          if (seconds_elapsed > 61) /* check for minute events */
                                {
                                  unsigned char i;
                                  unsigned char hours;
                                  int minutes; /* elapesed after midnight */
                                  /* sync internal seconds with external RTC */
                                  seconds_elapsed = ds1307_get_bcd_seconds();
                                  BCD_TO_DECIMAL(seconds_elapsed);

                                  show_time();
                                  
                                  /* convert current clock to time_of_day notation */
                                  hours = ds1307_get_bcd_hours();
                                  BCD_TO_DECIMAL(hours);
                                  minutes = ds1307_get_bcd_minutes();
                                  BCD_TO_DECIMAL(minutes);
                                  minutes += 60*hours; 

                                  if (circulator_pump_enabled)/* hot water circulation check */
                                        {
                                          bit circulator_on = 0;
                                          for ( i = 0; i < 8; i++)
                                                {
                                                  int start_minutes;
                                                  start_minutes = circulator_start_time[i];
                                                  start_minutes *= 6;
                                                  
                                                  if (circulator_start_time[i] < 240
                                                          && minutes >= start_minutes
                                                          && minutes < start_minutes + circulator_duration)
                                                        circulator_on = 1;
                                                }
                                          if (circulator_pump != circulator_on)
                                                {
                                                  circulator_pump = circulator_on;
                                                  output_update_hint = 1;
                                                }
                                        }

                                  if (solar_pump)
                                        {
                                          solar_pump_minutes ++;
                                          ds1307_set_int(0x0c, solar_pump_minutes);
                                        }
                                  
                                  
                                  /* Decide if we are in the night to reduce underfloor
                                         driving reference temperature */
                                  if (minutes > (int)night_end*6
                                          && minutes < (int)night_begin*6)
                                        {
                                          night_time = 0; /* or day time = 1 */
                                          blanking = display_blanking_enabled;
                                        }
                                  else
                                        {
                                          night_time = 1;
                                          blanking = 1;
                                        }

                                  if (minutes == 181 || minutes == 182) /* once a night */
                                        {
                                          if (!force_inter_tank_pump)
                                                output_update_hint = 1;
                                          inter_tank_pump = 1; /*forced run for rarely used pump */
                                          force_inter_tank_pump = 1;
                                        }
                                  else
                                        {
                                          if (force_inter_tank_pump)
                                                output_update_hint = 1;        
                                          force_inter_tank_pump = 0;
                                          inter_tank_pump = 0;
                                        }
                                }
                        }
                  ds1820_start(thermometer[device]);
                }
      
      WDT = 1; /* reset the watch dog timer */
      SWDT = 1;
    }
  halt(99, "SYSTEM HALTED\nend of main()");  
}