/***************************************************************************************************
 Project : Radioreset Interface
 Date    : 3.7.2007 (last update: 6.10.2007)
 Author  : Jan Matuska, honzamat@atlas.cz                          
 Company : www.starhill.org, FSI VUT Brno
 ---------------------------------------------------------------------------------------------------
 Chip type                : ATmega8L
 Clock frequency          : 3.579545 MHz (external clock from MT8870)
 Memory model             : Small
 Internal SRAM size       : 1024 B
 EEPROM memory            : 256 B   
 Compiler                 : avr-gcc 4.1.2 (WinAVR 20070525rc2) 
 Fuses (log.0=programmed) : BOOTSZ1, BOOTSZ2, SUT0, CKSEL3, CKSEL2, CKSEL1, CKSEL0   
***************************************************************************************************/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include "radioreset.h"
#include "messages.h"


// globalni promenne provoznich adres a parametru
unsigned char reset_output='0'; // vystupni port 1-8 v ascii
unsigned char reset_time='0';   // cas resetu 0-9 v ascii
unsigned char remote_addr[3];   // adresa vzaleneho zarizeni  012->SDJ
unsigned char rptr_addr[3];     // adresa vzaleneho zarizeni  012->SDJ
unsigned char session_key[5];   // pole na ukladani overovaciho klice 01234->DTSDJ

// globalni promenne v EEPROM
uint8_t ee_adresa[3]   EEPROM_SECTION = {'1','2','3'};   // adresa zarizeni, pole 012->SDJ
uint16_t ee_codes[200] EEPROM_SECTION = {0x6581,0x3283}; // tabulka kodu
uint8_t ee_tx_delay    EEPROM_SECTION = 15;              // delay na zaklicovani stanice v 150ms 
uint8_t ee_rx_delay    EEPROM_SECTION = 100;             // cekani na prijem paketu - prime spojeni v 150ms
uint8_t ee_rptr_delay  EEPROM_SECTION = 250;             // cekani na potvrzeni pres opakovac v 150ms
uint8_t ee_tone_time   EEPROM_SECTION = 30;              // delka dtmf tonu v 10ms
uint8_t ee_space_time  EEPROM_SECTION = 25;              // delka mezery mezi dtmf tony v 10ms
uint8_t ee_static_off  EEPROM_SECTION = 0;               // bit maska trvale seplych rele

// globalni promenne pro UART, volatile=je videt v preruseni
volatile unsigned char rs232enter = 0;              // globalni flag prijateho radku na RS232 (\n)    
volatile unsigned char nllock = 0;                  // globalni lock    
volatile unsigned char rx_buffer[RX_BUFFER_SIZE];   // buffer prijatych znaku na RS232 

// verbose
volatile unsigned char verbose=0;                   // verbose rezim   


volatile unsigned int counter = 0;    // pro delays
volatile unsigned int ledcounter = 0; // na bliknuti LEDkou
// -------------------------------------------------------------------------------------------------
// D E L A Y 
// -------------------------------------------------------------------------------------------------
// preruseni kazdou 1ms na generovani zpozdeni   3579545/1e3=3580  65536-3580=61956=0xF204 do TCNT1
// a zaroven blikani LEDky
ISR(TIMER1_OVF_vect) {
  TCNT1H=0x0F2;
  TCNT1L=0x04;
  if (counter) {
    counter--;
    CLRWDT;
  }
  if (ledcounter) {
    ledcounter--;
    CLRWDT;
  } else {
    LED_OFF;
  }
}

// -------------------------------------------------------------------------------------------------
// cekaci smycka po kroku 1ms
void delay(unsigned int cas) {
  cli();
  counter=cas;
  sei();
  while(counter) asm("nop");
}



// =================================================================================================        
// RUZNE FCE, ktere by jako makro byly moc velke  
// -------------------------------------------------------------------------------------------------
// test ASCII znaku zda je argument cislo od 0-9
unsigned char is_numeric(unsigned char arg)  {
  if ((arg>='0') && (arg<='9')) return 1; 
  return 0;
}

// -------------------------------------------------------------------------------------------------         
// test ASCII znaku zda je argument cislo od 1-8
unsigned char is_18(unsigned char arg)  {
  if ((arg>='1') && (arg<='8')) return 1; 
  return 0;
}

// -------------------------------------------------------------------------------------------------
// Funkce rozlozi int na 4-bitove slova dle argumentu pozice [0-3]
// vrati cislo jako ASCII znak
unsigned char getznak(unsigned int tmp_klic, unsigned char pozice) {
 pozice <<= 2;		      // vynasobeni 4
 tmp_klic >>= pozice;   // posuv o pozice*4 doprava
 tmp_klic &= 0x0F;      // vymaskovani hornich 12 bitu
 tmp_klic += 48;        // pricteni 48 do ASCII znaku
 return (tmp_klic);
}

// -------------------------------------------------------------------------------------------------
// umocnovani zakladu 10 a exponentem exp, tedy 10^exp  v math.h je to moc velke
unsigned int decpower(unsigned char exp) {
  unsigned int out=1;
  while(exp--) out *= 0x0A;
  return out;
}

// -------------------------------------------------------------------------------------------------   
// vezme pocet znaku v rx_bufferu od pocize start a prevede na cisleny char
// value=vystupni hodnota, error_code=1 pro OK 0 pro failed (overflow, no-numeric apod.)
unsigned char rxb2char(unsigned char *value, unsigned char start, unsigned char count) {
  unsigned char i=0;
  unsigned int tmp=0;
  while(count--) tmp += decpower(count) * (rx_buffer[start-(i++)]-48);
  if(tmp<=255) {
    *value = (unsigned char) tmp;
    return 1;
  } else return 0;
}
// =================================================================================================  







// =================================================================================================        
//   ####  ##### #   #  #####
//   #   #   #   ## ##  #
//   #   #   #   # # #  ###
//   #   #   #   #   #  #
//   ####    #   #   #  #
// -------------------------------------------------------------------------------------------------

// vzorky funkce sinus pro generovani DTMF pomoci PWM
unsigned char sin_samples [128] PROGMEM = {
64,67,70,73,76,79,82,85,88,91,94,96,99,102,104,106,109,111,113,115,117,118,120,
121,123,124,125,126,126,127,127,127,127,127,127,127,126,126,125,124,123,121,120,
118,117,115,113,111,109,106,104,102,99,96,94,91,88,85,82,79,76,73,70,67,64,60,57,
54,51,48,45,42,39,36,33,31,28,25,23,21,18,16,14,12,10,9,7,6,4,3,2,1,1,0,0,0,0,0,
0,0,1,1,2,3,4,6,7,9,10,12,14,16,18,21,23,25,28,31,33,36,39,42,45,48,51,54,57,60};
// skoky v ramci tabulky pro jednotlive frekvence (ZAVISLE NA TAKTU MCU!!!) 
unsigned char dtmf_high_tones [4] PROGMEM = {178,195,217,240};
unsigned char dtmf_low_tones [4] PROGMEM = {102,113,125,138};  
// globalni promenne v ramci preruseni
volatile unsigned int  i_next_h = 0;             // position freq. A in LUT (extended format)
volatile unsigned int  i_sample_h;               // position freq. A in LUT (actual position)
volatile unsigned int  i_next_l = 0;             // position freq. A in LUT (extended format)
volatile unsigned int  i_sample_l;               // position freq. A in LUT (actual position)
volatile unsigned char  dtmf_l_step=0, dtmf_h_step=0;

// buffer pro prichozi a odchozi dtmf  
unsigned char dtmf_buffer[DTMF_BUFFER_SIZE]; 
unsigned char dtmf_pos=0;  // pozice v dtmf bufferu

// -------------------------------------------------------------------------------------------------  
// preruseni na Timer2 overflow ve fci PWM. Zavisle na taktu MCU!
// skace se po urcitych krocich po SIN tabulce a vypocitava se dalsi hodnota, kdy dojde 
// ke zmene stavu na PB3/OC2 (dosazeni timeru2 hodnoty reg.OCR2) - klasika PWM
ISR(TIMER2_OVF_vect) {
  unsigned char s_high,s_low;
  i_next_h += dtmf_h_step;       
  i_next_l += dtmf_l_step;       
  i_sample_h = (char)(((i_next_h) >> 4)&(0x007F)); 
  i_sample_l = (char)(((i_next_l) >> 4)&(0x007F)); 
  // vzorek je = HI + (3/4)+LO = HI + LO - LO/4
  s_high=pgm_read_byte(&sin_samples[i_sample_h]);
  s_low=pgm_read_byte(&sin_samples[i_sample_l]);
  OCR2 = s_high + (s_low-(s_low>>2));
}

// -------------------------------------------------------------------------------------------------    
// pipne DTMF znak pomoci PWM na Timeru 2, pin PB3/OC2
// je nutne tam dat nejaky filtr, aby se odrezel pilotni signal PWM, staci RC, zbytek je
// ve stanici.
void SendDTMF(unsigned char znak) {     
  unsigned char tt,st;  
  tt=eeprom_read_byte(&ee_tone_time);
  st=eeprom_read_byte(&ee_space_time);        
  dtmf_l_step=pgm_read_byte(&dtmf_low_tones[3]);  // default # pokud je v bufferu kravina
  dtmf_h_step=pgm_read_byte(&dtmf_high_tones[2]);
  if((znak=='1')||(znak=='2')||(znak=='3')||(znak=='A')) dtmf_l_step=pgm_read_byte(&dtmf_low_tones[0]); 
  if((znak=='4')||(znak=='5')||(znak=='6')||(znak=='B')) dtmf_l_step=pgm_read_byte(&dtmf_low_tones[1]); 
  if((znak=='7')||(znak=='8')||(znak=='9')||(znak=='C')) dtmf_l_step=pgm_read_byte(&dtmf_low_tones[2]); 
  if((znak=='*')||(znak=='0')||(znak=='#')||(znak=='D')) dtmf_l_step=pgm_read_byte(&dtmf_low_tones[3]); 
  if((znak=='1')||(znak=='4')||(znak=='7')||(znak=='*')) dtmf_h_step=pgm_read_byte(&dtmf_high_tones[0]);
  if((znak=='2')||(znak=='5')||(znak=='8')||(znak=='0')) dtmf_h_step=pgm_read_byte(&dtmf_high_tones[1]);
  if((znak=='3')||(znak=='6')||(znak=='9')||(znak=='#')) dtmf_h_step=pgm_read_byte(&dtmf_high_tones[2]);
  if((znak=='A')||(znak=='B')||(znak=='C')||(znak=='D')) dtmf_h_step=pgm_read_byte(&dtmf_high_tones[3]);
  PWM_RUN;
  TIMER2_OVF_ENABLE;
  while(tt--) delay(10);
  TIMER2_OVF_DISABLE;
  PWM_STOP;
  TCCR2=0x00;   // vypnuti PWM, aby to nepiskalo nosnou na mic
  while(st--) delay(10);   // zakazani preruseni od Timeru2 na overflow
}

// -------------------------------------------------------------------------------------------------     
// odvysila DTMF buffer do eteru. tx_delay je doba od zaklicovani do zacatku vysilani a 
// to kvuli PowerSave rezimu prijimacich stanic a jejich squelchi.
void dtmf_send_buffer(void){
unsigned char i;
 DTMF_RX_DISABLE; 
 // tx + delay   
 if(verbose) write(VERB_TX);
 TX_ON;
 i=eeprom_read_byte(&ee_tx_delay);
 while(i--) delay(150);
 i=0;
 while (i<dtmf_pos) {
   if(verbose) putchar1(dtmf_buffer[i]);   
   SendDTMF(dtmf_buffer[i]);
   i++;
  }   
 if(verbose) { 
   lfcr();
   write(VERB_RX);
 }
 TX_OFF;
 delay(500); 
 dtmf_clear_buffer();
 DTMF_RX_ENABLE; 
}

// -------------------------------------------------------------------------------------------------   
// smaze dtmf buffer nastavenim 0 na zacatek a vynulovanim dtmf_pos, tzn. prazdno
void dtmf_clear_buffer(void){
 dtmf_pos=0;
 dtmf_buffer[0]='\0';
}

// -------------------------------------------------------------------------------------------------
// vlozi znak do dtmf bufferu dokud neni plny
void dtmf_push(unsigned char znak){
  if(dtmf_pos<DTMF_BUFFER_SIZE){ 
    dtmf_buffer[dtmf_pos]=znak;
    dtmf_pos++;
  }
}

// -------------------------------------------------------------------------------------------------
// externi preruseni 1, reakce na vzestupnou hranu STD signalu od MT8870
// ukladani prijatych tonu do DTMF bufferu na pozici dtmf_pos
// pokud prijde *, dtmf_pos se nastavi na 0 a zacina se od zacatku
// pokud je znaku vic jak velikost bufferu, prijate znaky ignoruje     
ISR(INT1_vect) {   
  unsigned char temp;
  unsigned char table[17]="D18945#A230*67BC";  // dekodovaci tabulka zprehazenych vstupu
  temp=((PIND & 0x0E0)>>4)+(PINB & 0x01);  
  if(table[temp]=='*') dtmf_clear_buffer(); 
  dtmf_push(table[temp]);
  LED_BLIK;
  if(verbose) {
    putchar1('{');
    putchar1(table[temp]);
    putchar1('}');
  }
}
// =================================================================================================   





// =================================================================================================        
//   ####    ####       .###.  ####.   ### 
//   #   #  #           #   #      #  #   #
//   ####    ###   ###     #    ##:      #
//   # #        #        #         #   #
//   #   #  ####        #####  ####'  #####
// -------------------------------------------------------------------------------------------------  

// preruseni po prichodu noveho znaku na USART     
// novy byte zapise do pozice 0, ostatni posune o 1 vyse = FIFO fornta
// po prichodu \n nebo \r nastavi priznak enteru - je zpracovan v main()
// zaroven lock na \r\n nebo \n\r aby nedavalo enterflag 2x
ISR(USART_RXC_vect) {
  unsigned char status,data,i;
  status=UCSRA;
  data=UDR;
  if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0) {
     if( ((data=='\n')||(data=='\r')) && (!nllock) ) { rs232enter=1; nllock=1; }
     if(data>=32 && data<=126) {
        nllock=0;
       i=RX_BUFFER_SIZE-1;
       while(i>0) {
         rx_buffer[i]=rx_buffer[i-1];
         i--;
      }
     rx_buffer[0]=data;
     }
  }
}

// -------------------------------------------------------------------------------------------------         
// odeslani znaku na USART
// ceka na vyprazneni TX bufferu a zapise do nej novy znak  
void putchar1(char c) {
  while (!(UCSRA & DATA_REGISTER_EMPTY));
  UDR=c;
}       
    
// -------------------------------------------------------------------------------------------------    
// odesle na terminal znaky konce radku a navratu na zacatek
void lfcr(void) {
  putchar1('\n');    //send lf - 10
  putchar1('\r');    //send cr - 13
}

// -------------------------------------------------------------------------------------------------  
// vypsani znakoveho retezce na terminal z flash pameti  
void write(char *sss){
  char k;     
  while ((k=pgm_read_byte(sss++))>0) {
    putchar1(k);  
  } 
  lfcr();
}    

// -------------------------------------------------------------------------------------------------  
// vypsani charu na terminal  
void print_char(unsigned char arg){
  unsigned char i=3;
  while(i--) {
    putchar1(48 + (arg / decpower(i)));
    arg %= decpower(i);
  }     
}

// -------------------------------------------------------------------------------------------------  
// otestuje, zdali se cmd nachazi na count pozici bufferu
// a zda-li je za cmd pouze numericky argument
unsigned char cmdtest(unsigned char cmd, unsigned char count){
  if(cmd!=rx_buffer[count]) return 0;
  while(count--) {
    if(!is_numeric(rx_buffer[count])) {
      write(UART_NUMERROR);
      return 0;
    }
  }  
  return 1;
}

// ------------------------------------------------------------------------------------------------- 
// vypis adresy tohoto zarizeni
void rs232_showaddr(void) {  
  unsigned char i=0;
  do {
    putchar1(eeprom_read_byte(&ee_adresa[i]));
  } while(++i<3);
  lfcr();
}

// ------------------------------------------------------------------------------------------------- 
// ulozi novou adresu tohoto zarizeni
void rs232_newaddr(void) {
  unsigned char i=3;
  while(i--) eeprom_write_byte(&ee_adresa[2-i], rx_buffer[i]);
  write(UART_ADDRSAVED);
  return;
}

// ------------------------------------------------------------------------------------------------- 
// vypise kod z tabulky codes
void rs232_readcode(void) {   
  unsigned char i=4,value;
  unsigned int data;   
  if(!rxb2char(&value,2,3)) {
    write(UART_CHAROVERF);
    return;
  }
  if((value>=1)&&(value<=200)) {
    value--;
    data=eeprom_read_word(&ee_codes[value]);
    while(i--) putchar1(getznak(data,i));
    lfcr();        
  } else {
    write(UART_OVERFLOW);
  }
}

// ------------------------------------------------------------------------------------------------- 
// zapise kod do tabulky codes
void rs232_writecode(void) {  
  unsigned char i=4,value;
  unsigned int data=0;   
  if(!rxb2char(&value,6,3)) {
    write(UART_CHAROVERF);
    return;
  }
  if((value>=1)&&(value<=200)) {
    value--;  
    while(i--) data=(data<<4)|(rx_buffer[i]-48);
    eeprom_write_word(&ee_codes[value], data);
    write(UART_KEYSTORED);    
  } else {
    write(UART_OVERFLOW);
  }  
}

// ------------------------------------------------------------------------------------------------- 
// vypise nastaveni konstant casovych prodlev
void rs232_showdelays(void) {
  print_char(eeprom_read_byte(&ee_tx_delay));
  putchar1(' ');
  print_char(eeprom_read_byte(&ee_rx_delay));
  putchar1(' ');
  print_char(eeprom_read_byte(&ee_rptr_delay));
  putchar1(' ');
  print_char(eeprom_read_byte(&ee_tone_time));
  putchar1(' ');
  print_char(eeprom_read_byte(&ee_space_time));
  lfcr();
}

// ------------------------------------------------------------------------------------------------- 
// nastaveni konstant casovych prodlev
void rs232_setdelays(void) {  
  unsigned char temp[5], error=0;
  if(!rxb2char(&temp[0],14,3)) error++;
  if(!rxb2char(&temp[1],11,3)) error++;
  if(!rxb2char(&temp[2],8,3)) error++;
  if(!rxb2char(&temp[3],5,3)) error++;
  if(!rxb2char(&temp[4],2,3)) error++;
  if(error) {
    write(UART_CHAROVERF);
    return;
  }
  eeprom_write_byte(&ee_tx_delay,temp[0]); 
  eeprom_write_byte(&ee_rx_delay,temp[1]);   
  eeprom_write_byte(&ee_rptr_delay,temp[2]);
  eeprom_write_byte(&ee_tone_time,temp[3]);
  eeprom_write_byte(&ee_space_time,temp[4]);
  write(UART_DLSAVED);
  
}

// -------------------------------------------------------------------------------------------------
// provedeni primeho sepnuti rele na aktualnim boardu
void rs232_direct(void) {
  if(!is_18(rx_buffer[1])) {     // 1-8 jsou vystupy, 
     write(UART_NUMERROR);
     return;
  }
  write(UART_DIRECTOK);
  reset_output=rx_buffer[1];
  reset_time=rx_buffer[0];     
  set_output();
}

//--------------------------------------------------------------------------------------------------
// vybagrovani uart bufferu a priprava na exekuci master rezimu
// direct: CCCVS   rptr: DDDCCCVS
void rs232_reset(unsigned char lmode) {
  unsigned char i=3,err[3]={3,3,3}; 
  if (lmode==REPEATER) while(i--) rptr_addr[i]=rx_buffer[7-i];
  i=3;
  while(i--) remote_addr[i]=rx_buffer[4-i];
  reset_output=rx_buffer[1];
  reset_time=rx_buffer[0];
  i=3;
  while(i--) {
    if(remote_addr[i]==eeprom_read_byte(&ee_adresa[i])) err[0]--;
    if((rptr_addr[i]==eeprom_read_byte(&ee_adresa[i]))&&(lmode==REPEATER)) err[1]--;
    if((rptr_addr[i]==remote_addr[i])&&(lmode==REPEATER)) err[2]--;
  }
  if (!err[0]) { write(UART_ADDRERR1); return; }  // kdyz je local stejna jako remote
  if (!err[1]) { write(UART_ADDRERR1); return; }  // kdyz je local stejne jako remote za opakovacem
  if (!err[2]) { write(UART_ADDRERR2); return; }  // kdyz je remote stejna jako opakovac 
  master_execute(lmode);
}
  
// -------------------------------------------------------------------------------------------------
// spusteni live testu na dostupnost zarizeni
void rs232_livetest(void) {
  unsigned char i=3,err=3;
  while(i--) {
    remote_addr[i]=rx_buffer[2-i];
    if(remote_addr[i]==eeprom_read_byte(&ee_adresa[i])) err--;
  }
  if(!err) { write(UART_ADDRERR1); return; }
  livetest();
}   

// -------------------------------------------------------------------------------------------------
// nastaveni priznaku na verbose rezim - RS232 je vic ukecana
void rs232_verbose(void) {
  if(verbose) {
   write(UART_VERBOFF);
   verbose=0; 
  } else {
   write(UART_VERBON);
   verbose=1; 
  }
}   
   
// -------------------------------------------------------------------------------------------------
// rozdeleni akci jednotlivym prikazum
void rs232_command(void) {   
  unsigned char i=9;
  LED_BLIK;
  if(cmdtest('P',0)) write(UART_ALIVE); 
  if(cmdtest('H',0)) write(HELP); 
  if(cmdtest('A',0)) rs232_showaddr();
  if(cmdtest('I',0)) rs232_showdelays();
  if(cmdtest('V',0)) rs232_verbose();
  if(cmdtest('X',2)) rs232_direct();
  if(cmdtest('N',3)) rs232_newaddr();
  if(cmdtest('R',3)) rs232_readcode();
  if(cmdtest('L',3)) rs232_livetest(); 
  if(cmdtest('C',5)) rs232_reset(DIRECT);
  if(cmdtest('W',7)) rs232_writecode();
  if(cmdtest('D',8)) rs232_reset(REPEATER);
  if(cmdtest('K',15)) rs232_setdelays();
  while (i--) rx_buffer[i]=0;
  rs232enter=0;     
  dtmf_clear_buffer();	// preventivne proti akci po zmene adresy, kdy by v dtmf bufferu neco bylo
}
// ================================================================================================= 






// =================================================================================================        
//   ####   ####  #      ###   #   #   ####
//   #   #  #     #     #   #   # #   #
//   ####   ###   #     #####    #     ###
//   # #    #     #     #   #    #        #
//   #   #  ####  ####  #   #    #    ####
// -------------------------------------------------------------------------------------------------  

// je to napsane trosku neoptimalne, ale zase umoznuje menit relatka jak chci..
void move_to_relays(char vystupy){     
  if((vystupy & 0x01)!=0) RELE1_ON; else RELE1_OFF;
  if((vystupy & 0x02)!=0) RELE2_ON; else RELE2_OFF;
  if((vystupy & 0x04)!=0) RELE3_ON; else RELE3_OFF;
  if((vystupy & 0x08)!=0) RELE4_ON; else RELE4_OFF;
  if((vystupy & 0x10)!=0) RELE5_ON; else RELE5_OFF;
  if((vystupy & 0x20)!=0) RELE6_ON; else RELE6_OFF;
  if((vystupy & 0x40)!=0) RELE7_ON; else RELE7_OFF;
  if((vystupy & 0x80)!=0) RELE8_ON; else RELE8_OFF;
}
                               
// -------------------------------------------------------------------------------------------------                                                      
// odpoji a pripoji vystupy dle zadanych hodnot
// bere v uvahu globalni promenne znak reset_output, znak reset_time a z eeprom static_off
// reset_output 1-8 , pokud je 0,tak jen nastavi vystupy dle static_off
void set_output(void){
 unsigned char tmp_time=0, tmp_output=0, static_off=eeprom_read_byte(&ee_static_off); 
 if(is_numeric(reset_time)) tmp_time=(reset_time-48)*10;    
 if(is_18(reset_output)) tmp_output=1<<(reset_output-48-1);
 if(tmp_output!=0) {
     // pokud je cas nula, nastavi priznak trvaleho off vystupu
   if(tmp_time==0) static_off|=tmp_output;
    // pokud neni cas nula, smaze priznak trvaleho off vystupu      
   if((tmp_time!=0) && ((static_off & tmp_output)!=0)) static_off&=~(tmp_output);    
 }   
 if(static_off!=eeprom_read_byte(&ee_static_off)) eeprom_write_byte(&ee_static_off,static_off);
 tmp_output|=static_off;   // to tmp_output ulozi skutecny stav vystupu      
 move_to_relays(tmp_output);
 while(tmp_time--) delay(1000);
 tmp_output=static_off;     // vychozi nastaveni vystupu
 move_to_relays(static_off);   
}           
// ================================================================================================= 








// =================================================================================================        
//    ###   ###   #   #  #   #
//   #     #   #  ## ##  ## ##
//   #     #   #  # # #  # # #
//   #     #   #  #   #  #   #
//    ###   ###   #   #  #   #
// -------------------------------------------------------------------------------------------------     

// alive test vzdaleneho slave prvku
void livetest(void) { 
  unsigned char i;
  write(L_SENDPCKT);
  create_packet_header();
  dtmf_push('C'); 
  dtmf_send_buffer();  
  i=eeprom_read_byte(&ee_rx_delay);
  i=100; // XXXXXXXXXXXXXXXXXXXXXX
  DTMF_RX_ENABLE;
  while((i--)&&!((rxtest_addr())&&(dtmf_pos==8))) delay(150);
  DTMF_RX_DISABLE;
  i=3;
  while(i--) if(dtmf_buffer[4+i]!=remote_addr[i]) { write(L_ERR); return; }
  if(dtmf_buffer[7]=='1') { write(L_OK); return; }  
  write(L_ERR);
}  

// -------------------------------------------------------------------------------------------------  
// vytvori session key a ulozi do pole sesion_key[4-0]
// Argumenty k=BCD klic z tabulky bran jako int, rezim=0slave, 1master
// Pri vypoctu a bere rady adres opacne j*100+d*10+s
void create_session_key(unsigned int k, char rezim){
  unsigned int a=0,tmpsk;   
  unsigned char i=3;
  switch(rezim) {
   case SLAVE: while(i--) a+=(eeprom_read_byte(&ee_adresa[i])-48)*decpower(i);
        break;
   case MASTER: while(i--) a+=((remote_addr[i])-48)*decpower(i);
        break;
  }                      
  tmpsk=SESSION_KEY_FC;
  i=5;
  if(verbose) putchar1('[');
  while(i--) {
    session_key[4-i]= 48 + (tmpsk / decpower(i));
    if(verbose) putchar1(session_key[4-i]);
    tmpsk %= decpower(i);
  }
  if(verbose) {
    putchar1(']');
    lfcr();
  }
}
 
// -------------------------------------------------------------------------------------------------  
// otestuje jestli je v dtmf bufferu hlavicka s adresou lokalniho zarizeni
// a aktualniho vzdaleneho zarizeni vcetne pozadovaneho poctu dtmf znaku
// vrati 1 pokud jsou v bufferu spravne udaje
unsigned char rxtest(unsigned char pocet) {
  unsigned char i=3;    
  if(dtmf_pos!=pocet) return 0;
  if(!rxtest_addr()) return 0;  
  while(i--) if(dtmf_buffer[4+i]!=remote_addr[i]) return 0;
  return 1;
}
// ================================================================================================= 

 
 
 
 
 
 
// =================================================================================================        
//   #   #   ###    #### #####  #####  ####
//   ## ##  #   #  #       #    #      #   #
//   # # #  #####   ###    #    ###    ####
//   #   #  #   #      #   #    #      #  #
//   #   #  #   #  ####    #    #####  #   #
// -------------------------------------------------------------------------------------------------    
   
// Vytvori a odesle prvni paket na vzdaleny reset a odesle ho do eteru
void master_initpacket(unsigned char lmode) {  
  write(M_SENDINIT);
  create_packet_header();
  if(lmode==REPEATER){                  
    dtmf_push('B');
  } else {
    dtmf_push('A');
  }
  dtmf_push(reset_output);
  dtmf_push(reset_time);
  if(lmode==REPEATER){    
    dtmf_push(rptr_addr[0]);
    dtmf_push(rptr_addr[1]);
    dtmf_push(rptr_addr[2]);
  }    
  dtmf_send_buffer();  
}  

// -------------------------------------------------------------------------------------------------
// Prijme zadost od Slave na zaslani klice. Slave posle cislo radku tabulky (index)
// Navratova hodnota:  0 - timeout, Slave neodpovedel v pozadovanem casovem ramci nebo chyba v eteru
//                     1-200 prijaty radek tabulky
unsigned char master_rx_table_index(void) {
  unsigned int tmp=0;
  unsigned char i;
  write(M_WAITINDEX);              
  i=eeprom_read_byte(&ee_rx_delay);
  DTMF_RX_ENABLE;
  while((i--)&&!(rxtest(10))) delay(150);
  DTMF_RX_DISABLE;
  i=3;
  while(i--) {
    if(!is_numeric(dtmf_buffer[9-i])) return 0;
    tmp+=decpower(i)*(dtmf_buffer[9-i]-48);
  }
  if((tmp<1)||(tmp>200)||(!rxtest(10))) return 0;
  return (unsigned char) tmp;
}
 
// -------------------------------------------------------------------------------------------------
// Vytvori a odesle odpovedni paket s klicem ziskanym v tabulce
// Globalni promenne: table_index, ccc, adresa, klice
void master_klic(void) {
  unsigned char i=0;     
  write(M_SENDKEY);              
  create_packet_header();
  do dtmf_push(session_key[i]); while(++i<5);
  dtmf_send_buffer();  
}

// -------------------------------------------------------------------------------------------------
// Prijme potvrzeni od Slave o provedeni operace.
// Navratova hodnota:  0 - timeout, Slave neodpovedel v pozadovanem casovem ramci nebo chyba v eteru
//                     1 - prijem byl proveden a vzdaleny reset taktez
unsigned char master_potvrzeni(unsigned char lmode) {   
  unsigned char i;
  write(M_WAITACK);
  if(lmode==DIRECT) i=eeprom_read_byte(&ee_rx_delay); else i=eeprom_read_byte(&ee_rptr_delay);
  DTMF_RX_ENABLE;     
  while((i--)&&!(rxtest(8))) delay(150);
  DTMF_RX_DISABLE;
  if((dtmf_buffer[7]=='1')&&(rxtest(8))) return 1;
  return 0;
} 

 
// Spusteni sekvence pro cinnost zarizeni jako Master
// Navratova hodnota:  0 - timeout, Slave neodpovedel v limitnim case, zrejme chyba v komunikaci
//                     0 - reset pres opakovac se neprovedl
//                     1 - vzdaleny reset byl proveden, vse je v poradku     
// lmode je typ bud primo(=0 DIRECT) nebo pres opakovac(=1 REPEATER)
unsigned char master_execute(unsigned char lmode) {       
  unsigned char table_index;
  master_initpacket(lmode);	            // odvysila init packet  
  if((table_index=master_rx_table_index())==0) {
    write(M_TITIMEOUT); 
    return 0;                               // je master rezim ukoncen s errcode 0
  } 
  create_session_key(eeprom_read_word(&ee_codes[table_index-1]), MASTER);  
  master_klic(); 
  if(master_potvrzeni(lmode)==0) {
    if(verbose) lfcr();
    write(M_ACKTIMEOUT);  
    return 0;          
  }
  if(verbose) lfcr();
  write(M_OK);   
  return 1;
}
// =================================================================================================        





  
// =================================================================================================        
//    ####  #      ###   #   #  #####
//   #      #     #   #  #   #  #
//    ###   #     #####  #   #  ###
//       #  #     #   #   # #   #
//   ####   ####  #   #    #    #####
// -------------------------------------------------------------------------------------------------    
  
// Vygeneruje "nahodny" radek tabulky a ulozi do table_index
// Predpoklada beh timeru0 a nahodny cas pozadavku na reset
unsigned char slave_gen_index(void){
  unsigned int table_index=TCNT0+1;
  if(table_index>200) table_index -= (table_index >> 1);   // opraveno z <<
  return (unsigned char) table_index; 
}  
  
// -------------------------------------------------------------------------------------------------    
// Vygeneruje a odesle paket pro master se zadosti o klic s cislem radku code tabulky (indexem)
void slave_key_request(unsigned char table_index){
  write(S_SENDINDEX);
  create_packet_header(); 
  dtmf_push(48+table_index/100);
  table_index %= 100;
  dtmf_push(48+table_index/10);
  table_index %= 10;
  dtmf_push(48+table_index);
  dtmf_send_buffer();   
}

// -------------------------------------------------------------------------------------------------    
// Prijme paket od Mastera s klicem na zaklade zadosti s cislem radku (indexem)
// Navratova hodnota:  0 - timeout, Master neodpovedel v pozadovanem casovem ramci
//                     1 - prijem byl proveden a klic je ok
//                     2 - byl prijat chybny klic
unsigned char slave_receive_key(unsigned char table_index){   
  unsigned char i;
  write(S_WAITKEY);
  create_session_key(eeprom_read_word(&ee_codes[table_index-1]), SLAVE);  
  DTMF_RX_ENABLE;
  i=eeprom_read_byte(&ee_rx_delay);
  while((i--)&&!(rxtest(12))) delay(150);
  DTMF_RX_DISABLE;
  if(!rxtest(12)) return 0;
  i=5;
  while(i--) if(dtmf_buffer[7+i]!=session_key[i]) return 2; 
  write(S_KEYRX);
  return 1;
}
    
// -------------------------------------------------------------------------------------------------    
// Spusteni sekvence pro cinnost zarizeni jako Slave
unsigned char slave_auth(void){
  unsigned char stav, table_index;
  write(S_BEGAUTH);
  table_index=slave_gen_index();
  slave_key_request(table_index);       // odvysila zadost MASTERovi o zaslani vygenerovaneho klice
  stav=slave_receive_key(table_index); 	// rutina, co prijme paket od mastera s klicem, porovna, pripadne osetri timeout
  switch(stav) {
   case 0: write(S_KEYTIMEOUT);
           return 0; 
   case 2: write(S_KEYFAILED);
           return 0;
  }         
  write(S_AUTHOK);
  return 1;  
} 
// =================================================================================================  
  
  
  
  
  
// =================================================================================================        
//   ####   ###    ###  #  #  ##### #####  ####
//   #   # #   #  #     # #   #       #   #
//   ####  #####  #     ##    ###     #    ###
//   #     #   #  #     # #   #       #       #
//   #     #   #   ###  #  #  #####   #   #### 
// -------------------------------------------------------------------------------------------------    
    
// Zablokuje slave rezim po chybne autorizaci na 1 minutu
// Snizuje riziko pruniku hrubou silou a dava cas na pripadne odhaleni
void anti_intrusion_timeout(void) {
  unsigned char cas=60;
  write(S_AITOUT);
  while(cas--) {
    delay(1000); 
    if(verbose) {
      putchar1(0x8);            // srandicka s countdownem na terminalu
      putchar1(0x8);
      putchar1(0x8);
      print_char(cas);
    }      
  }  
  if(verbose) lfcr();
}  

// -------------------------------------------------------------------------------------------------
// vytvori hlavicku paketu ve formatu *CCCMMM kde
// CCC je adresa vzdaleneho zarizeni z remote_addr
// MMM je adresa lokalniho zarizeni z ee_adresa
void create_packet_header(void) {
  unsigned char i=0;
  dtmf_clear_buffer();
  dtmf_push('*');
  do {
    dtmf_push(remote_addr[i]);
  } while (++i<3);
  i=0;
  do {
    dtmf_push(eeprom_read_byte(&ee_adresa[i]));
  } while (++i<3);  
}

// -------------------------------------------------------------------------------------------------
// odeslne protistrane status
// napriklad potvrzeni o provedene operaci
void slave_send_status(unsigned char status){
  write(S_SENDACK);
  create_packet_header();
  dtmf_push(status);
  dtmf_send_buffer();
}

// -------------------------------------------------------------------------------------------------
// otestuje jestli je v dtmf bufferu adresa tohoto zarizeni s hvezdickou
// vrati 1 pokud je v bufferu spravna adresa
unsigned char rxtest_addr(void) {
  unsigned char i=3;      
  if(dtmf_buffer[0]!='*') return 0;    
  while(i--) if(eeprom_read_byte(&ee_adresa[i])!=dtmf_buffer[i+1]) return 0;    
  return 1;
}

// -------------------------------------------------------------------------------------------------
// overi, zda-li je v dtmf_bufferu obsazen paket
// otestuje numericke hodnoty, delku apod apod.. 
unsigned char is_packet(unsigned char name, unsigned char pktlen) {
  if(dtmf_pos!=pktlen) return 0;
  if(!rxtest_addr()) return 0;
  if(dtmf_buffer[7]!=name) return 0;
  while(--pktlen>3) {
    if(pktlen==7) pktlen--;
    if(!is_numeric(dtmf_buffer[pktlen])) return 0;
    if(pktlen<7) remote_addr[pktlen-4]=dtmf_buffer[pktlen]; // ulozeni remote adresy
  }
  write(S_REMADDROK);
  dtmf_buffer[0]='\0';     // zabraneni opetovne detekce paketu
  return 1;
}

// -------------------------------------------------------------------------------------------------
// overi a nacte hodnotu vystupniho portu a cas vypnuti
// reset_output pusti v internvalu 1-8
// reset_time pusti v intervalo 0-9, to je uz overeno z is_packet()
unsigned char read_reset_arguments(void) {
  if(!is_18(dtmf_buffer[8])) return 0;   
  reset_output=dtmf_buffer[8];
  reset_time=dtmf_buffer[9];
  write(S_ARGOK);
  return 1;
}      

// -------------------------------------------------------------------------------------------------
// handler na A packet
void packet_a_handler(void){ 
  DTMF_RX_DISABLE;
  write(S_PACDETA);
  if(read_reset_arguments()==0) return;   // nacteni a test v a s hodnot
  if(slave_auth()==0) {
    anti_intrusion_timeout();
    return;
  } 
  slave_send_status('1'); 
  write(S_HWAPPLY);
  set_output();   
}

// -------------------------------------------------------------------------------------------------
// handler na B packet
void packet_b_handler(void){  
  unsigned char i=0, temp[3], stav;
  DTMF_RX_DISABLE;
  write(S_PACDETB);
  if(read_reset_arguments()==0) return;   // nacteni a test v a s hodnot
  do rptr_addr[i]=dtmf_buffer[10+i]; while(++i<3);
  if(slave_auth()==0) { 
    anti_intrusion_timeout();
    return;
  }   
  write(S_BEG_PM);  
  i=3;
  while(i--) {
    temp[i]=remote_addr[i];
    remote_addr[i]=rptr_addr[i];
  }
  stav=master_execute(DIRECT);           // ted ze sebe udelej master
  i=3; 
  while(i--) remote_addr[i]=temp[i];     // obnova puvodni remote adresy
  write(S_END_PM);  
  if(stav) slave_send_status(stav+48);   // posle status puvodnimu masterovi, pokud je 1
}  
  
// -------------------------------------------------------------------------------------------------
// zpracovani paketu C
void packet_c_handler(void){ 
  DTMF_RX_DISABLE;
  write(S_PACDETC);   
  slave_send_status('1');
}
// =================================================================================================        







// -------------------------------------------------------------------------------------------------
// The Story begins here .....  
// -------------------------------------------------------------------------------------------------
int main(void) {
  // MCU setup
  // port B
  PORTB=0x00;
  DDRB=0x0FE;  // 1111 1110
  // port C
  PORTC=0x00;
  DDRC=0x3F;   // 0011 1111
  // port D
  PORTD=0x00;
  DDRD=0x17;   // 0001 0111
  // Timer 0 - slouzi jako generator "nahodnych" cisel pro autorizaci
  // Nastaveni: inkrementace systemovym taktem
  TCCR0=0x01;
  TCNT0=0x00;
  // Timer/Counter 1 initialization - generovani casovych prodlev a LED
  TCCR1A=0x00;
  TCCR1B=0x01;  // bez preddelicky, vstup systemovy takt
  TCNT1H=0x0F2; // startovaci hodnota timeru
  TCNT1L=0x04;
  OCR1AH=0x00;  // komparacni registry nevyuzivane
  OCR1AL=0x00;
  OCR1BH=0x00;
  OCR1BL=0x00;
  // Timer/Counter 2 initialization - pouziva se jako PWM pro DTMF, zapina se v programu
  ASSR=0x00;
  TCCR2=0x00;
  TCNT2=0x00;
  OCR2=0x00;
  // Externi preruseni
  // Reakce na vzestupnou hranu INT1 - signal STD od CM8870
  GICR=0x00;   // povoleni preruseni INT1 az v programu dle potreby
  MCUCR=0x0C;  // nastaveni vzestupne hrany pro interrupt INT1 
  // Timers interrupt enable - povoluje se v programu dle potreby
  TIMSK=0x00;
  TIMER1_OVF_ENABLE; // povoleni preruseni pro delay a blik LED
  // USART  RX: on, TX: on, 9600 baud, 8bit, 1 stop bit, no parity
  UCSRA=0x00;
  UCSRB=0x98; 
  UCSRC=0x86;
  UBRRL=23;  // speed 9600
  UBRRH=0x00;
  // Analog Comparator initialization - nepouzivan
  ACSR=0x80;
  SFIOR=0x00;
  //  Zapnuti HW watchdogu
  CLRWDT;
  WDTCR=0x18;
  WDTCR=0x0F;
  // zhasnuta LED
  LED_OFF;
  // Global enable interrupts
  sei();
  // zacatek
  LED_BLIK;
  delay(200);
  LED_BLIK;
  write(WELCOME);
  set_output();
  while(1) { 
    CLRWDT;
    DTMF_RX_ENABLE; 
    if(rs232enter==1) rs232_command(); 
    if(is_packet('A',PACKET_A_LENGTH)) packet_a_handler();     
    if(is_packet('B',PACKET_B_LENGTH)) packet_b_handler();     
    if(is_packet('C',PACKET_C_LENGTH)) packet_c_handler();
  }
}


