/*
Thermostat Node
Edward Cheung, Ph.D.

Compiled with CCS's PCM C Compiler version 2.547
Tested with TechTools' IDE and PIC Emulator.

Modification History
- 6//1/99 Created.
- 6/25/99 Flash led briefly if good network traffic
- 6/26/99 Default after n seconds to SET_TEMP mode
-         Save working vars to nv memory (for power failure robustness)
- 7/16/99 Compiled under Borland C++ with appropriate 16c77 header
-         Add hysteresis to thermostatic code
-         cumulative time stats
- 7/17/99 Command decoder
- 7/25/99 DS1820 support.  ds1_ functions
- 8/ 1/99 poll DS1820s in background
-         time delay to filter noise and reduce false starts and stops (using fewer calls to thermals())
-         poll all DS1820's in system
-         fix clicking relays on init
-         save important vars to non volatile memory (for power fail robustness)
- 8/29/99 Fixed bug with reset stats (caused PC timeout)
          Added filtering to 1wire temp measurements (max change of 1 degree per sample)

To do
- LED backlight control (see EX_PWM.C)

Non volatile (RTC-65271) Address Map
0x0 - 0xD : real-time clock
0x0E      : cool set point (start of working vars block)
0x0F      : heat set point
0x10      : hvac mode
0x20      : cool set point (start of state store block)
0x21      : heat set point
0x22      : hvac mode
0xxx      : (end of state store block)
*/
#define THERMAL_INTERVAL   10  // seconds between runs of thermal control function
#define DS1_MAX            4   // number of 1 wire temp sensors
#define CLOCK_HI           0   // if true, use 20mhz
#define MIN_DELTA          5   // minimum delta between heat and cool set point via front panel control

#include <16c77.h>
#ifndef __BORLANDC__
#if CLOCK_HI
  //#use delay(clock=19660800)
  #use delay(clock=20000000)
#else
  #use delay(clock=14318180)
#endif
#use rs232(baud=9600, bits=8, parity = N, xmit=PIN_C6, rcv=PIN_C7, enable=PIN_C5, brgh1ok,ERRORS)
#define PAGE_1 #ASM BSF    03,5 #ENDASM
#define PAGE_0 #ASM BCF    03,5 #ENDASM
#BYTE   PORTB = 6        // for switch input
#BYTE   PORTE = 9        // led test
// portd is data bus. Note hardcoded set_tris_d statements below.
// Unavoidable with byte wide I/O and PCM compiler.
#BYTE   RTC_DATA = 8     // see comment immediately above.
#use FAST_IO(A)
#use FAST_IO(B)
#use FAST_IO(C)
#use FAST_IO(D)
#use FAST_IO(E)
#endif
#define WR_PIN PIN_B4    // RTC control lines
#define RD_PIN PIN_B3
#define AD_PIN PIN_C0
#define NODE_ADDRESS 'T'
// Dallas chip
#define DS_READ  0x27  // tris data to read from DS1620
#define DS_WRITE 0x07  // tris data to write to DS1620
#define RST_PIN PIN_B7
#define CLK_PIN PIN_B6
#define DQ_PIN  PIN_B5
#define DS_1WIRE PIN_A5
// commands to dallas chips
#define DS_TEMP  0xAA
#define DS_WRTH  0x01,9
#define DS_WRTL  0x02,9
#define DS_RDTH  0xA1
#define DS_RDTL  0xA2
#define DS_START 0xEE,0,0
#define DS_STOP  0x22,0,0
#define DS_WRCFG 0x0C,8
#define DS_RDCFG 0xAC
#define DS_SRCRM 0xF0   // search ROM function
#define DS_RDRM  0x33   // read ROM function
#define DS_MTRM  0x55   // match ROM command
// LCD display
#define RS_PIN PIN_C4   // high:data, low:instruction
#define RW_PIN PIN_C1   // high:read, low:write
#define EN_PIN PIN_C3   // falling edge:latch data
// LED
#define RED   0
#define GREEN 1
#define BLUE  2
#define RED_PIN PIN_E0
#define GRN_PIN PIN_E1
#define BLU_PIN PIN_E2
// relays
#define HEAT 0
#define COOL 1
#define FAN  2
#define OFF  3
#define AUTM 4
#define HEAT_PIN PIN_A1
#define COOL_PIN PIN_A2
#define FAN_PIN  PIN_A0
// buttons
#define TOP 0x1
#define MID 0x2
#define BOT 0x4

// prototypes
void lcd_put(byte data,short rs);
byte lcd_get(short rs);
void lcd_init(void);
short ds1_rst(void);
void ds1_put(byte data);
short ds1_bitin(void);
byte ds1_get(void);
void ds1_read(int id[8]);
void ds_put(byte protocol,byte length,long data);
void led_put(int select,short state);
void rly_put(int select,short state);
int rtc_get(long address,short rtc);
void rtc_put(long address,int data,short rtc);
void paint(void);
void lcd_cursor(short visible);
// global vars
short click_go;       // new switch state
short timer_go;       // timer for periodic actions
short network;        // if network alive then true
short new_cmd;        // true if new command/message from master
byte mode;            // current display mode
#define SET_TEMP 0    // adjust temp set point
#define SET_MODE 1    // change heating cooling mode
#define SET_STAT 2    // status display
byte prev_sw;         // previous switch status
byte heat_setpt;      // heating set point
byte cool_setpt;      // cooling set point
byte hvac_mode;       // heating cooling mode, one of HEAT, COOL, FAN, OFF, AUTO
byte stat_mode;       // which status/internal parameter to show/modify, one of following:
#define HOURS   0     // set hour and show time
#define MINUT   1     // set minute
#define MUNTH   2
#define DAYS    3
#define YEARS   4
#define HYSTER  5     // heating cooling hysteresis
#define SAVER   6     // save all to nv memory
#define RETRIVE 7     // retrieve from nv memory
#define WIREID  8     // get 8 byte id of one 1-wire device attached
#define COOL_T  9     // run time cooling
#define HEAT_T  10    // run time heating (MUST be last stat_mode)
byte hysteresis;      // thermostat hysteresis
byte gui_timer;       // if 1, go to default state
long cool_time;       // amount of minutes cooling
long heat_time;       // amount of minutes heating
long temp;            // current inside temp
byte cmd;             // command byte from master
byte arg;             // argument byte from master
byte thermal_cnt;     // keeps track of when to run thermal controls
signed int ds1_temps[DS1_MAX];  // arrays with temps from 1 wire sensors

void init(void) {
  int x;
  output_low(HEAT_PIN);
  output_low(COOL_PIN);
  output_low(FAN_PIN);
  set_tris_a(0xF8);     // relay control
  set_tris_b(DS_WRITE); // pushbuttons, rtc ctl, DS1620
  set_tris_c(0x80);     // other controls and serial port
  set_tris_d(0xff);     // data bus
  set_tris_e(0x00);      // leds
  output_low(PIN_C5);   // serial port listen
  // RTC
  output_low(AD_PIN);
  output_high(WR_PIN);
  output_high(RD_PIN);
  rtc_put(0xA,0x20,TRUE);  // start clock
  rtc_put(0xB,0x7,TRUE);   // resume updates
  // DS1620
  output_low(RST_PIN);
  output_high(CLK_PIN);
  ds_put(DS_START);
  PAGE_1 // page 1
  output_high(DS_1WIRE);  // configure as input to release 1-wire I/F
  PAGE_0 // page 0
  // LCD
  output_low(EN_PIN);
  output_low(RS_PIN);
  output_low(RW_PIN);
  lcd_init();
  // led and relays
  led_put(RED,FALSE);
  led_put(GREEN,FALSE);
  led_put(BLUE,FALSE);
  rly_put(HEAT,FALSE);
  rly_put(COOL,FALSE);
  rly_put(FAN,FALSE);
  // serial ISR
  enable_interrupts(global);
  enable_interrupts(int_rda);
  restart_wdt();
  click_go = FALSE;
  new_cmd = FALSE;
  mode = SET_TEMP;
  prev_sw = 0;
  stat_mode = HOURS;
  network = 0;
  hysteresis = 2;
  gui_timer = 0;
  cool_time = 0;
  temp = 0;
  thermal_cnt = 0;
  heat_time = 0;
  setup_counters( RTCC_INTERNAL, RTCC_DIV_256);
  enable_interrupts(RTCC_ZERO);
  restart_wdt();
  for (x=0;x<DS1_MAX;x++)
    ds1_temps[x] = 0;
  cool_setpt = rtc_get(0x0E,TRUE);
  heat_setpt = rtc_get(0x0F,TRUE);
  hvac_mode  = rtc_get(0x10,TRUE);
  paint();
}
byte lcd_get(short rs) {
  byte data;
  set_tris_d(0xff);
  delay_us(6);
  output_bit(RS_PIN,rs);
  delay_us(6);
  output_high(RW_PIN);
  delay_us(6);
  output_high(EN_PIN);
  delay_us(6);
  data = RTC_DATA;
  delay_us(6);
  output_low(EN_PIN);
  delay_us(6);
  return data;
}
void lcd_put(byte data,short rs) {
  restart_wdt();
  output_bit(RS_PIN,rs);
  delay_us(4);
  output_low(RW_PIN);
  delay_us(5);
  output_high(EN_PIN);
  delay_us(6);
  set_tris_d(0x00);
  delay_us(6);
  RTC_DATA = data;
  delay_us(6);
  output_low(EN_PIN);
  delay_us(6);
  set_tris_d(0xff);
  delay_us(6);
}
void lcd_init(void) {
  int x;
  delay_ms(2);
  lcd_put(0x30,FALSE);
  delay_ms(5);
  lcd_put(0x30,FALSE);
  delay_ms(1);
  lcd_put(0x30,FALSE);
  lcd_put(0x3C,FALSE);  // number of lines and font
  lcd_put(0x0C,FALSE);  // display, cursor and blink on/off (0xF for blink cursor)
  lcd_put(0x01,FALSE);  // clear display
  lcd_put(0x04,FALSE);  // direction and shift on/off
  delay_ms(2);
  //lcd_put(' ',TRUE);
  //lcd_put('B',TRUE);
  //for (x=0;x<78;x++)
  //  lcd_put('0'+x,TRUE);
  lcd_cursor(FALSE);
}
#ifdef __BORLANDC__
char lcd_print[40];
#define printf sprintf
#else
void lcd_print(char text) {
  lcd_put(text,TRUE);
}
#endif
void lcd_cursor(short visible) {
  if (visible)
    lcd_put(0x0F,FALSE);  // display, cursor and blink on/off (0xF for blink cursor)
  else
    lcd_put(0x0C,FALSE);  // display, cursor and blink on/off (0xF for blink cursor)
}
void lcd_pos(int x,int y) {
// 1,1 is upper left
  int pos;
  pos = 0;
  if (y==1) {
    //if (x==1) {
    //  lcd_put(0x2,FALSE);
    //  delay_us(200);
    //  return;
    //}
    pos = x-1;
  } else if (y==2)
    pos = x + 0x3E;
  else if (y==3)
    pos = x + 19;
  else if (y==4)
    pos = x + 0x53;
  lcd_put(pos|0x80,FALSE);
}
// Dallas Semiconductor.  ds_xx are for 3-wire, ds1_xx are for 1-wire
short ds1_rst(void) {
// issue reset pulse for 1 wire interface.  Returns true if presence detected
  short presence;
  restart_wdt();
  // queue up low on 1-wire interface
  output_low(DS_1WIRE);
  // set port as output
  PAGE_1
  output_low(DS_1WIRE);
  PAGE_0
  // delay for reset duration
  delay_us(500);
  // release clamp to ground by making pin an input
  PAGE_1
  output_high(DS_1WIRE);
  PAGE_0
  // wait for presence pulse.  Can start as quickly as 60us, and end at 75us.
  delay_us(65);
  // sample presence
  presence = !input(DS_1WIRE);
  // wait till presence pulse clears
  delay_us(275);
  return presence;
}
void ds1_put(byte data) {
// send a byte.  LSB is sent first
  int x;
  for (x=0;x<8;x++) {
    if (data&0x1) {
      // set port as output to pull I/F low
      PAGE_1
      output_low(DS_1WIRE);
      PAGE_0
      // minimum low time
      delay_us(5);
      // release interface
      PAGE_1
      output_high(DS_1WIRE);
      PAGE_0
      delay_us(60);           // wait for time slot
    } else {
      // set port as output to pull I/F low
      PAGE_1
      output_low(DS_1WIRE);
      PAGE_0
      delay_us(60);           // wait for time slot
      // release interface
      PAGE_1
      output_high(DS_1WIRE);
      PAGE_0
    }
    // shift data right one bit
    rotate_right(&data,1);
    delay_us(5);  // between bit delay
  }
}
short ds1_bitin(void) {
  short data;
  // set port as output to pull I/F low
  PAGE_1
  output_low(DS_1WIRE);
  PAGE_0
  // minimum low time
  delay_us(4);  // reduce this and/or the other delay if '0' are read as '1's
  // release interface
  PAGE_1
  output_high(DS_1WIRE);
  PAGE_0
  delay_us(5);  // wait to ensure bus is stable
  // shift data in, one bit
  data = input(DS_1WIRE);
  delay_us(50);  // between bit delay
  return data;
}
byte ds1_get(void) {
// sample a byte, lsb first
  int x;
  int data;
  data = 0;
  for (x=0;x<8;x++) {
    shift_right(&data,1,ds1_bitin());
  }
  return data;
}
void ds1_read(int id[8]) {
// read the ID of the attached unit
  int data,x;
  ds1_rst();
  ds1_put(DS_RDRM);   // read ROM command
  for (x=0;x<8;x++)
    id[x] = ds1_get();
}
void ds1_address(int unit) {
// send address of unit given its id
  switch (unit) {
  case 0:
    ds1_put(0x10);  // family
    ds1_put(0x41);  // s.n.
    ds1_put(0xbb);  // s.n.
    ds1_put(0x30);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.  if 'z', enter 0x00
    ds1_put(0xce);  // CRC
    break;
  case 1:
    ds1_put(0x10);  // family
    ds1_put(0x7C);  // s.n.
    ds1_put(0xF5);  // s.n.
    ds1_put(0x30);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.  if 'z', enter 0x00
    ds1_put(0x22);  // CRC
    break;
  case 2:
    ds1_put(0x10);  // family
    ds1_put(0x9D);  // s.n.
    ds1_put(0xC2);  // s.n.
    ds1_put(0x30);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.  if 'z', enter 0x00
    ds1_put(0xE9);  // CRC
    break;
  case 3:
    ds1_put(0x10);  // family
    ds1_put(0x2E);  // s.n.
    ds1_put(0xD9);  // s.n.
    ds1_put(0x30);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.  if 'z', enter 0x00
    ds1_put(0xBB);  // CRC
    break;
  case 4:
    ds1_put(0x10);  // family
    ds1_put(0x75);  // s.n.
    ds1_put(0xEC);  // s.n.
    ds1_put(0x30);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.  if 'z', enter 0x00
    ds1_put(0x3B);  // CRC
    break;
  case 5:
    ds1_put(0x10);  // family
    ds1_put(0xA0);  // s.n.
    ds1_put(0xB7);  // s.n.
    ds1_put(0x30);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.
    ds1_put(0x00);  // s.n.  if 'z', enter 0x00
    ds1_put(0xF1);  // CRC
    break;
  default:
    // UNKNOWN ID
    break;
  }
}
void ds1_trigger(int unit){
// trigger sample for temperature at the DS1820 given its unit id
  ds1_rst();
  ds1_put(0x55);   // match ROM command
  // send serial number
  ds1_address(unit);
  ds1_put(0x44);   // start conversion
  // stiff high to supply power
  output_high(DS_1WIRE);
  PAGE_1
  output_high(DS_1WIRE);
  PAGE_0
}
long ds1_data(int unit) {
  long data;
  int temp1,temp2;
  int x;
  // send reset
  ds1_rst();
  ds1_put(0x55);   // match ROM command
  // send serial number
  ds1_address(unit);
  ds1_put(0xBE);   // read scratch pad
  temp1 = ds1_get();
  temp2 = ds1_get();
  data = temp2;
  data = data<<8;
  data = data|temp1;
  // reset units
  ds1_rst();
  return data;
}
void ds1_run(void) {
  float fahr;
  static int ds1_state=0;  // 1 Wire unit to sample
  signed int tempo;
  // get data from previous acquisition
  tempo = ds1_data(ds1_state);
  fahr = 32.0 + (9.0*(float)tempo)/10.0;
  tempo = fahr + 0.5;   // new temperature in integer Fahrenheit
  // Ramp to new reading to reject noise
  if (tempo < ds1_temps[ds1_state])
    ds1_temps[ds1_state]--;
  else
    ds1_temps[ds1_state]++;
  // increment state
  ds1_state++;
  if (ds1_state>=DS1_MAX)
    ds1_state = 0;
  // sample next unit
  ds1_trigger(ds1_state);  // sample next temperature
}
void ds_put(byte protocol,byte length,long data) {
  int bit;
  output_high(RST_PIN);
  // issue protocol byte LSB first
  for (bit=0;bit<8;bit++) {
    output_low(CLK_PIN);
    if (protocol&0x01 == 0x01)
      output_high(DQ_PIN);
    else
      output_low(DQ_PIN);
    rotate_right(&protocol,1);
    output_high(CLK_PIN);
  }
  // write data bits
  for (bit=0;bit<length;bit++) {
    output_low(CLK_PIN);
    if (data&0x01 == 0x01)
      output_high(DQ_PIN);
    else
      output_low(DQ_PIN);
    rotate_right(&data,1);
    output_high(CLK_PIN);
  }
  output_low(RST_PIN);
}
long ds_get(byte protocol) {
  int bit;
  long data;
  output_high(RST_PIN);
  // issue protocol byte LSB first
  for (bit=0;bit<8;bit++) {
    output_low(CLK_PIN);
    if (protocol&0x01 == 0x01)
      output_high(DQ_PIN);
    else
      output_low(DQ_PIN);
    rotate_right(&protocol,1);
    output_high(CLK_PIN);
  }
  //output_high(DQ_PIN);
  // read data from DS
  set_tris_b(DS_READ);
  data = 0;
  for (bit=0;bit<9;bit++) {
    output_low(CLK_PIN);
    if (input(DQ_PIN))
      data = data|0x0200;
    rotate_right(&data,2);
    output_high(CLK_PIN);
  }
  set_tris_b(DS_WRITE);
  output_low(RST_PIN);
  return data;
}
void rtc_put_lo(int address,int data,short rtc) {
  set_tris_d(0x00);
  address = address & 0x3F; // ensure upper two bits low
  RTC_DATA = address|0xC0;  // set upper bits high
  output_high(AD_PIN);
  output_low(AD_PIN);
  if (rtc) {
    RTC_DATA = address|0x80;  // set bit 6 low
    output_high(AD_PIN);
    output_low(AD_PIN);
  } else {
    RTC_DATA = address|0x40;  // set bit 7 low
    output_high(AD_PIN);
    output_low(AD_PIN);
  }
  RTC_DATA = data;
  output_low(WR_PIN);
  output_high(WR_PIN);
  RTC_DATA = address|0xC0;  // set upper bits high
  output_high(AD_PIN);
  output_low(AD_PIN);
  set_tris_d(0xff);
}
int rtc_get_lo(long address,short rtc) {
  int data;
  set_tris_d(0x00);
  address = address & 0x3F; // ensure upper two bits low
  RTC_DATA = address|0xC0;  // set upper bits high
  output_high(AD_PIN);
  output_low(AD_PIN);
  if (rtc) {
    RTC_DATA = address|0x80;  // set bit 6 low
    output_high(AD_PIN);
    output_low(AD_PIN);
  } else {
    RTC_DATA = address|0x40;  // set bit 7 low
    output_high(AD_PIN);
    output_low(AD_PIN);
  }
  set_tris_d(0xff);
  output_low(RD_PIN);
  data = RTC_DATA;
  output_high(RD_PIN);
  set_tris_d(0x00);
  address = address & 0x3F; // ensure upper two bits low
  RTC_DATA = address|0xC0;  // set upper bits high
  output_high(AD_PIN);
  output_low(AD_PIN);
  set_tris_d(0xff);
  return data;
}
void rtc_put(long address,int data,short rtc) {
  if (rtc) {
    // set address register
    rtc_put_lo(0x00,(int)address,rtc); // indirect address register
    // set data register
    rtc_put_lo(0x01,data,rtc); // rtc data register
  } else {
    // set bank register
    rtc_put_lo(0x20,(address>>9),rtc);
    // set data
    rtc_put_lo((address&0x1f),data,rtc);
  }
}
int rtc_get(long address,short rtc) {
  int data;
  if (rtc) {
    // set address register
    rtc_put_lo(0x00,(int)address,rtc); // indirect address register
    // get data register
    data = rtc_get_lo(0x01,rtc); // rtc data register
  } else {
    // set bank register
    rtc_put_lo(0x20,(address>>9),rtc);
    // get data
    data = rtc_get_lo((address&0x1f),rtc);
  }
  return data;
}
int rtc_test(void) {
  int data,x,errors;
  errors = 0;
  // low level write of xtended RAM, not battery backed
  for (x=0;x<10;x++) {
    rtc_put_lo(x,x,FALSE);
    rtc_put_lo(12,0xaa,FALSE);
    data = rtc_get_lo(x,FALSE);
    if (data != x) {
      errors++;
    }
  }
  // high level write of test data into RTC bank
  rtc_put_lo(0x00,0x00,TRUE); // indirect address register
  rtc_put_lo(0x01,0x00,TRUE); // rtc data register
  rtc_put_lo(0x00,0x01,TRUE);
  rtc_put_lo(0x01,0x01,TRUE);
  rtc_put_lo(0x00,0x02,TRUE);
  rtc_put_lo(0x01,0x02,TRUE);

  // get data from RTC bank
  rtc_put_lo(0x00,0x00,TRUE);
  data = rtc_get_lo(0x01,TRUE);
  if (data != 0x00)
    errors++;
  rtc_put_lo(0x00,0x01,TRUE);
  data = rtc_get_lo(0x01,TRUE);
  if (data != 0x01)
    errors++;
  rtc_put_lo(0x00,0x02,TRUE);
  data = rtc_get_lo(0x01,TRUE);
  if (data != 0x02)
    errors++;

  // high level test of XRAM bank
  for (x=0;x<200;x++) {
    rtc_put(x,x,FALSE);
    data = rtc_get(x,FALSE);
    if (data != x)
      errors++;
  }

  // high level test of RTC bank
  for (x=0;x<0x40;x++) {
    rtc_put(x,x,TRUE);
    data = rtc_get(x,TRUE);
    if ((data != x)&&(x!=0xC)&&(x!=0xd))
      errors++;
  }
  return errors;
}
void rtc_save(void) {
  // save state into nonvolatile RAM
  rtc_put(0x20,cool_setpt,TRUE);
  rtc_put(0x21,heat_setpt,TRUE);
  rtc_put(0x22,hvac_mode,TRUE);
}
void rtc_retrieve(void) {
  // retrieve state from nonvolatile RAM
  cool_setpt = rtc_get(0x20,TRUE);
  heat_setpt = rtc_get(0x21,TRUE);
  hvac_mode  = rtc_get(0x22,TRUE);
}
void led_put(int select,short state) {
  if (select==RED)
    output_bit(RED_PIN,state);
  else if (select==GREEN)
    output_bit(GRN_PIN,state);
  else if (select==BLUE)
    output_bit(BLU_PIN,state);
}
short rly_get(int select) {
  if (select==HEAT)
    return input(HEAT_PIN);
  else if (select==COOL)
    return input(COOL_PIN);
  else
    return input(FAN_PIN);
}
void rly_put(int select,short state) {
  if (select==HEAT)
    output_bit(HEAT_PIN,state);
  else if (select==COOL)
    output_bit(COOL_PIN,state);
  else if (select==FAN)
    output_bit(FAN_PIN,state);
}
byte sw_get(void) {
  int data;
  port_b_pullups(TRUE);
  delay_us(1);
  data = PORTB&0x7;
  port_b_pullups(FALSE);
  return data^0x7;
}

#if CLOCK_HI
  #define INTS_PER_FIRE  76
#else
  #define INTS_PER_FIRE 54  // 76.3 for one second interval at 20Mhz
#endif
#ifndef __BORLANDC__
#int_rtcc
#endif
void clock_timer(){
  static int test=INTS_PER_FIRE;
  // network traffic indicator
  if (test==10 && network) {
    network = FALSE;
    // flash LED to indicate network present
    if (PORTE==0)
      // no LEDs on, turn on white
      PORTE = 0x7;
    else
      // LED off briefly
      PORTE = 0x0;
  }
  // periodic interrupt
  if (test<=0) {
    // wrap around var to test if period elapsed
    test=INTS_PER_FIRE;
    timer_go = TRUE;
  }
  test--;
  // sample switches
  if (prev_sw!=sw_get()) {
    // change of switch state
    prev_sw = sw_get();
    click_go = TRUE;
  }
}

#ifndef __BORLANDC__
#int_rda
#endif
void serial_isr() {
  byte byte_in;
  static byte com_state=0;
  // process new received character
  byte_in=getc();
  network = TRUE;  // mark network alive
  switch (com_state) {
  case 0:
    if (byte_in==NODE_ADDRESS)
      // message for thermostat node
      com_state++;
    break;
  case 1:
    // check if from computer
    if (byte_in=='C')
      com_state++;
    else
      com_state=0;
    break;
  case 2:
    // check delimiter
    if (byte_in=='_')
      com_state++;
    else
      com_state=0;
    break;
  case 3:
    cmd = byte_in;
    com_state++;
    break;
  case 4:
    arg = byte_in;
    com_state++;
    break;
  case 5:
    // absorb <cr>
    com_state = 0;
    new_cmd = TRUE;
    break;
  default:
    com_state=0;
  }
}
// temperature regulation functions
void thermals(long temp,int second) {
  if (mode == SET_TEMP) {
    // regulate temp if not setting any parameters
    if (temp>=(cool_setpt+(hysteresis+1)/2) && (hvac_mode==COOL||hvac_mode==AUTM)) {
      // cool
      rly_put(COOL,TRUE);
      led_put(BLUE,TRUE);
    } else if (temp<=(cool_setpt-hysteresis/2) && (hvac_mode==COOL||hvac_mode==AUTM)) {
      // not cooling
      rly_put(COOL,FALSE);
      led_put(BLUE,FALSE);
    } else {
      // deadband zone, keep previous status
      if (rly_get(COOL))
        led_put(BLUE,TRUE);
      else
        led_put(BLUE,FALSE);
    }
    if (temp<=(heat_setpt-(hysteresis+1)/2) && (hvac_mode==HEAT||hvac_mode==AUTM)) {
      // heat
      rly_put(HEAT,TRUE);
      led_put(RED,TRUE);
    } else if (temp>=(heat_setpt+hysteresis/2) && (hvac_mode==HEAT||hvac_mode==AUTM)) {
      // not heating
      rly_put(HEAT,FALSE);
      led_put(RED,FALSE);
    } else {
      // deadband zone, keep previous status
      if (rly_get(HEAT))
        led_put(RED,TRUE);
      else
        led_put(RED,FALSE);
    }
    if (hvac_mode==FAN) {
      // fan only
      rly_put(COOL,FALSE);
      rly_put(HEAT,FALSE);
      rly_put(FAN,TRUE);
      led_put(GREEN,TRUE);
    } else {
      // no fan
      rly_put(FAN,FALSE);
      led_put(GREEN,FALSE);
    }
  }
}
void send_byte(int data){
  putchar('C');
  putchar(NODE_ADDRESS);
  putchar('_');
  restart_wdt();
  putchar(data);
  putchar(13);
  set_tris_c(0x80);     // other controls and serial port
}
void send_long(long data){
  putchar('C');
  putchar(NODE_ADDRESS);
  putchar('_');
  restart_wdt();
  putchar((byte)(data>>12)+'0');
  putchar((byte)(data>>8)+'0');
  putchar((byte)(data>>4)+'0');
  restart_wdt();
  putchar((byte)(data)+'0');
  putchar(13);
  set_tris_c(0x80);     // other controls and serial port
}
void show_id(void) {
  int id[8],x;
  ds1_read(id);
  lcd_pos(1,4);
  if (id[6]==0)
    // show compressed id, substitute 'z' for id[6]
    printf(lcd_print,"%2X%2X%2X%2X%2X%2Xz%2X",id[0],id[1],id[2],id[3],id[4],id[5],id[7]);
  else
    // show id without family id
    printf(lcd_print,"?%2X%2X%2X%2X%2X%2X%2X",id[1],id[2],id[3],id[4],id[5],id[6],id[7]);
}
void main(void) {
  int month,day,year,hour,minute,second,x;
  float fahr;

  // extra long delay for power up
  for (x=0;x<75;x++) {
    // extra delay
    restart_wdt();
    delay_ms(5);
  }
  // do all inits
  init();

  /*
  //x = lcd_get(TRUE);

  //lcd_put(0x01,FALSE);
  //rtc_test();
  // rtc_put(0xB,0x81,TRUE); // suspend updates
  rtc_put(0xB,0x83,TRUE);  // suspend updates
  rtc_put(0x08,6,TRUE);    // month
  rtc_put(0x07,18,TRUE);   // day
  rtc_put(0x09,99,TRUE);   // year
  rtc_put(0x04,22,TRUE);   // hour
  rtc_put(0x02,00,TRUE);   // minute
  rtc_put(0x00,9,TRUE);    // second
  rtc_put(0xA,0x20,TRUE);  // start clock
  rtc_put(0xB,0x7,TRUE);   // resume updates

  // Hardware limits in DS1620.  For test make one count higher than ambient, then heat with finger
  // set low temp emergency level.
  ds_put(DS_WRTH,0x10);  // 0x10 = 50F
  delay_ms(100);         // writes to EEPROM can take 10msec
  // set high temp emergency level
  ds_put(DS_WRTL,0x40);  // 0x40 = 90F

  x = sw_get();
  */

  // main executive loop
  while (1) {
    // stroke watchdog
    restart_wdt();
    // new command
    if (new_cmd) {
      new_cmd = FALSE;
      switch (cmd) {
      case 'A':
        // get 1 wire temp, 0=outside
        send_byte(ds1_temps[arg-'0']+'0');
        break;
      case 'T':
        // get temp
        send_byte(temp+'0');
        break;
      case 'S':
        // heating cooling status
        if (rly_get(COOL))
          send_byte('0'+COOL);
        else if (rly_get(HEAT))
          send_byte('0'+HEAT);
        else if (rly_get(FAN))
          send_byte('0'+FAN);
        else
          send_byte('0'+OFF);
        break;
      case 'H':
        // set heat set point
        heat_setpt = arg-'0';
        send_byte(temp+'0');
        thermal_cnt = 10;
        paint();
        break;
      case 'I':
        // get heat set point
        send_byte(heat_setpt+'0');
        break;
      case 'C':
        // set cool set point
        cool_setpt = arg-'0';
        send_byte(temp+'0');
        thermal_cnt = 10;
        paint();
        break;
      case 'D':
        // get cool set point
        send_byte(cool_setpt+'0');
        break;
      case 'U':
        // cume cool time
        send_long(cool_time);
        break;
      case 'V':
        // cume heat time
        send_long(heat_time);
        break;
      case 'R':
        // restore from nv memory
        rtc_retrieve();
        send_byte(temp+'0');
        break;
      case 'Z':
        cool_time = 0;
        heat_time = 0;
        send_byte(temp+'0');
        break;
      case 'M':
        // set mode
        hvac_mode = arg-'0';
        if (hvac_mode>AUTM)
          hvac_mode = OFF;
        rtc_put(0x10,hvac_mode,TRUE);
        send_byte(temp+'0');
        paint();
        break;
      case 'N':
        // get mode
        send_byte(hvac_mode+'0');
        break;
      default:
        break;
      }
    }
    // periodic actions
    if (timer_go) {
      #ifndef __BORLANDC__
      timer_go = FALSE;
      #endif
      if (gui_timer == 1) {
        gui_timer = 0;
        if (mode != SET_TEMP) {
          mode = SET_TEMP;
          paint();
        }
      } else if (gui_timer>0)
        gui_timer--;

      // get time
      month = rtc_get(0x08,TRUE);
      day = rtc_get(0x07,TRUE);
      year = rtc_get(0x09,TRUE);
      hour = rtc_get(0x04,TRUE);
      minute = rtc_get(0x02,TRUE);
      second = rtc_get(0x00,TRUE);

      // get temperature
      temp = ds_get(DS_TEMP);
      fahr = 30.0 + (9.0*(float)temp)/10.0;  // note built in offset due to bias
      temp = fahr + 0.5;   // temperature in integer Fahrenheit

      // get temperature and filter
      ds1_run();

      /*
      // check for new day
      if (hour==23 && minute==59) {
        // zero cume heating and cooling times
        heat_time = 0;
        cool_time = 0;
      }
      */

      // keep track of run minutes
      if (second==0) {
        if (rly_get(COOL))
          cool_time++;
        if (rly_get(HEAT))
          heat_time++;
      }

      // regulate temperature if not setting parameters
      if (thermal_cnt <= 0) {
        thermals(temp,second);
        thermal_cnt = THERMAL_INTERVAL;
      } else {
        thermal_cnt--;
        // restore LEDs
        led_put(BLUE,rly_get(COOL));
        led_put(RED,rly_get(HEAT));
        if (hvac_mode == FAN)
          led_put(GREEN,TRUE);
        else
          led_put(GREEN,FALSE);
      }

      // display temps
      lcd_pos(14,2);
      printf(lcd_print,"%2ldF",temp);
      lcd_pos(12,3);
      printf(lcd_print,"%3dF",ds1_temps[0]);

      // status/parameter line
      lcd_pos(1,4);
      switch (stat_mode) {
      case HOURS:
        if (mode==SET_STAT) {
          printf(lcd_print,"Hour = %02U      ",hour);
        } else {
          if (second!=0x81)
            printf(lcd_print,"%02U/%02U  %02U:%02U:%02U",month,day,hour,minute,second);
        }
        break;
      case MINUT:
        if (mode==SET_STAT) {
          printf(lcd_print,"Minute = %02U    ",minute);
        } else {
          if (second!=0x81)
            printf(lcd_print,"%02U/%02U  %02U:%02U:%02U",month,day,hour,minute,second);
        }
        break;
      case MUNTH:
        if (mode==SET_STAT) {
          printf(lcd_print,"Month = %02U     ",month);
        } else {
          if (second!=0x81)
            printf(lcd_print,"%02U/%02U  %02U:%02U:%02U",month,day,hour,minute,second);
        }
        break;
      case YEARS:
        if (mode==SET_STAT) {
          printf(lcd_print,"Year = %02U     ",year);
        } else {
          if (second!=0x81)
            printf(lcd_print,"%02U/%02U  %02U:%02U:%02U",month,day,hour,minute,second);
        }
        break;
      case DAYS:
        if (mode==SET_STAT) {
          printf(lcd_print,"Day = %02U       ",day);
        } else {
          if (second!=0x81)
            printf(lcd_print,"%02U/%02U  %02U:%02U:%02U",month,day,hour,minute,second);
        }
        break;
      case HYSTER:
        printf(lcd_print,"Hyst=%d degrees",hysteresis);
        break;
      case SAVER:
        printf(lcd_print,"Save all data  ");
        break;
      case RETRIVE:
        printf(lcd_print,"Retrieve data  ");
        break;
      case WIREID:
        printf(lcd_print,""); // ID:
        break;
      case HEAT_T:
        printf(lcd_print,"Heating %2ld:%02ld ",heat_time/60,heat_time%60);
        break;
      case COOL_T:
        printf(lcd_print,"Cooling %2ld:%02ld ",cool_time/60,cool_time%60);
        break;
      default:
        break;
      }
    }
    // check pushbuttons
    if (click_go) {
      // new button state
      click_go = FALSE;
      gui_timer = 5;

      if (sw_get() == TOP) {
        // top switch depressed
        mode++;
        if (mode > SET_STAT)
          mode = SET_TEMP;
      }
      if (sw_get()==MID) {
        // middle switch depressed
        switch (mode) {
        case SET_TEMP:
          if ((hvac_mode==HEAT || hvac_mode==AUTM) && heat_setpt<90) {
            heat_setpt++;
            rtc_put(0xF,heat_setpt,TRUE);
            if (heat_setpt > (cool_setpt-MIN_DELTA)) {
              cool_setpt = heat_setpt+MIN_DELTA;
              rtc_put(0xE,cool_setpt,TRUE);
            }
            thermal_cnt = 10;
          }
          if ((hvac_mode==COOL || hvac_mode==AUTM) && cool_setpt<90) {
            cool_setpt++;
            rtc_put(0xE,cool_setpt,TRUE);
            thermal_cnt = 10;
          }
          break;
        case SET_MODE:
          hvac_mode++;
          if (hvac_mode>AUTM)
            hvac_mode = HEAT;
          rtc_put(0x10,hvac_mode,TRUE);
          break;
        case SET_STAT:
          stat_mode++;
          if (stat_mode == WIREID) {
            lcd_pos(1,4);
            printf(lcd_print,"Hit MOD for ID ");
          }
          if (stat_mode>HEAT_T)
            stat_mode = HOURS;
          break;
        default:
          break;
        }
      }
      if (sw_get()==BOT) {
        // botom switch depressed
        switch (mode) {
        case SET_TEMP:
          if ((hvac_mode==HEAT || hvac_mode==AUTM) && heat_setpt>60) {
            heat_setpt--;
            rtc_put(0xF,heat_setpt,TRUE);
            thermal_cnt = 10;
          }
          if ((hvac_mode==COOL || hvac_mode==AUTM) && cool_setpt>60) {
            cool_setpt--;
            rtc_put(0xE,cool_setpt,TRUE);
            if (cool_setpt < (heat_setpt+MIN_DELTA)) {
              heat_setpt = cool_setpt-MIN_DELTA;
              rtc_put(0xF,heat_setpt,TRUE);
            }
            thermal_cnt = 10;
          }
          break;
        case SET_MODE:
          // reset to default mode;
          mode = SET_TEMP;
          break;
        case SET_STAT:
          // change parameter chosen
          switch (stat_mode) {
          case HOURS:
            // set hour
            hour = rtc_get(0x04,TRUE);
            hour++;
            if (hour>23)
              hour = 0;
            rtc_put(0x04,hour,TRUE);
            break;
          case MINUT:
            // set minute, reset seconds
            minute = rtc_get(0x02,TRUE);
            minute++;
            if (minute>59)
              minute = 0;
            rtc_put(0x00,0x00,TRUE);
            rtc_put(0x02,minute,TRUE);
            break;
          case MUNTH:
            // set month
            month = rtc_get(0x08,TRUE);
            month++;
            if (month>12)
              month = 1;
            rtc_put(0x08,month,TRUE);
            break;
          case DAYS:
            // set day
            day = rtc_get(0x07,TRUE);
            day++;
            if (day>31)
              day = 0;
            rtc_put(0x07,day,TRUE);
            break;
          case YEARS:
            // set year
            year = rtc_get(0x09,TRUE);
            if (year==99) {
              year=00;
            } else {
              year++;
              if (year>10)
                year=99;
            }
            rtc_put(0x09,year,TRUE);
            break;
          case SAVER:
            rtc_save();
            mode = SET_TEMP;
            stat_mode = HOURS;
            break;
          case RETRIVE:
            rtc_retrieve();
            mode = SET_TEMP;
            stat_mode = HOURS;
            break;
          case WIREID:
            show_id();
            break;
          case HYSTER:
            // change thermostat hysteresis
            hysteresis++;
            if (hysteresis>4)
              hysteresis = 1;
            break;
          case COOL_T:
            // zero cooling run time
            cool_time = 0;
            mode = SET_TEMP;
            stat_mode = HOURS;
            break;
          case HEAT_T:
            // zero heating run time
            heat_time = 0;
            mode = SET_TEMP;
            stat_mode = HOURS;
            break;
          default:
            break;
          }
          break;
        default:
          break;
        }
      }
      // redraw lcd screen
      paint();
    }
  }
}
void paint(void) {
  // paint screen
  // top line
  lcd_pos(1,1);
  switch(hvac_mode) {
  case HEAT:
    printf(lcd_print,"Heat Set Pt %UF|",heat_setpt);
    break;
  case COOL:
    printf(lcd_print,"Cool Set Pt %UF|",cool_setpt);
    break;
  case FAN:
    printf(lcd_print,"  - Fan Only - |");
    break;
  case OFF:
    printf(lcd_print,"     - Off -   |");
    break;
  default:
    printf(lcd_print,"Auto  %UF<->%UF|",heat_setpt,cool_setpt);
    break;
  }
  // second and third lines
  switch(mode) {
  case SET_TEMP:
    lcd_pos(17,1);
    printf(lcd_print,"TSet");
    lcd_pos(1,2);
    printf(lcd_print,"Inside temp    |    ");
    lcd_pos(1,3);
    printf(lcd_print,"Outside tmp    |Up  ");
    lcd_pos(16,4);
    printf(lcd_print,"|Down");
    break;
  case SET_MODE:
    printf(lcd_print,"Mode");
    lcd_pos(1,2);
    printf(lcd_print,"Inside temp    |    ");
    lcd_pos(1,3);
    printf(lcd_print,"Outside tmp    |Next");
    lcd_pos(16,4);
    printf(lcd_print,"|OK  ");
    break;
  default:
    printf(lcd_print,"Stat");
    lcd_pos(1,2);
    printf(lcd_print,"Inside temp    |    ");
    lcd_pos(1,3);
    printf(lcd_print,"Outside tmp    |Next");
    lcd_pos(16,4);
    printf(lcd_print,"|Mod ");
    break;
  }
}

