/* 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()");  
}
