#define  __AVR_ATtiny85__ true  // required definition for TinyWireS
#define __AVR_ATtinyX5__  true  // required definition for ADC reference .. note reference to 2.56v did not work without it
#include<avr/io.h>              // for pin definitions
#include<EEPROM.h>              // required for EEPROM
//#define SCL PB2
//#define SDA PB0

//#define slave_i2c  true
#ifndef slave_i2c
#include "TinyWireM.h"
#include "LiquidCrystal_I2C.h"
#else
#include "TinyWireS.h"          // calling the i2c slave library
#endif

#define Debugit true
#define LCD_i2c true

#include "TinyDebug.h"
//#include "arduino.h"             // required for ADC 
#ifndef slave_i2c
#ifdef LCD_i2c
 LiquidCrystal_I2C lcd (0x27,16,2);
#endif
#endif

static float PB1_Analog = 0.0;  // global parameter to preserve the PID functions output
static float PB3_Analog = 0.0;  //
union {                         // union structure for timer to ease the send and receive over i2c
    uint32_t val32;             //
    byte bval[4];               //
} precond_time_out, charge_time_out, topoff_time_out; // 

static float EOT, EOC;          // global parameters for battery current setting .. these are functions of COC
volatile byte chargeStatus = 0; // initialize the battery state machine 
union {                         // union struction for the variables to be sent by i2c 
    float fval;
    byte bval[4];
} batteryVoltReading,batteryCurrentReading, VOC, COC ;
#define STATUS_PIN_1 PB1        // define the discharge command pin 
#define STATUS_PIN_2 PB5        // define the charge command pin 
#define ADC_PIN0 A3             // define the battery volts ADC pin 
#define ADC_PIN1 A2             // define the battery current ADC pin
enum charge_state{  initial = 0,          // self explainatory
                    precond =1,
                    charging =2,
                    timertopoff =3,
                    maintenance =4,
                    badbattery =5,
                    nobattery=6};
//  C++ function from the net for setting up timers
//char str[16];
class TimerMs { 
public:
    // (период, мс), (0 не запущен / 1 запущен), (режим: 0 период / 1 таймер)
    TimerMs(uint32_t prd = 1000, bool state = 0, bool mode = 0) {
        setTime(prd);
        if (state) start();
        _mode = mode;
    }
    void setTimerMode() {         // установить в режим таймера (остановится после срабатывания)
        _mode = 1;
    }
    void setPeriodMode() {        // установить в режим периода (перезапустится после срабатывания)
        _mode = 0;
    }
    void setTime(uint32_t prd) {      // установить время
        _prd = (prd == 0) ? 1 : prd;
    }
    void attach(void (*handler)()) {  // подключить коллбэк
        _handler = *handler;
    }
    void detach() {
        _handler = NULL;
    }

    void start() {            // запустить/перезапустить таймер
        _state = true;  
        _tmr = uptime();
    }
    void restart() {
        start();
    }
    void resume() {           // продолжить после остановки
        _state = true;
        _tmr = uptime() - _buf;
    }
    void stop() {             // остановить/приостановить таймер
        _state = false;
        _buf = uptime() - _tmr;
    }
    void force() {            // принудительно переполнить таймер
        _tmr = uptime() - _prd;
    }

    // в режиме периода однократно вернёт true при каждом периоде
    // в режиме таймера будет возвращать true при срабатывании
    bool tick() {
        if (_state) _buf = uptime() - _tmr;
        if (_state && _buf >= _prd) {
            if (!_mode) _tmr += _prd * (_buf / _prd);
            else stop();
            if (*_handler) _handler();
            _ready = 1;
            return true;
        }
        return false;
    }
    
    bool ready() {      // однократно вернёт true при срабатывании (флаг)
        if (_ready) {
            _ready = 0;
            return true;
        }
        return false;
    }
    bool elapsed() {        // всегда возвращает true при срабатывании
        return (uptime() - _tmr >= _prd);
    }
    bool active() {         // работает ли таймер (start/resume)
        return _state;
    }
    bool status() {         // elapsed+active: работает ли таймер + не сработал ли он      
        return _state && !elapsed();
    }
    
    // остаток времени
    uint32_t timeLeft() {     // остаток времени в мс
        return max(long(_prd - _buf), 0L);
    }
    uint8_t timeLeft8() {     // остаток времени в 0-255
        return max(255 - _buf * 255l / _prd, 0ul);
    }
    uint16_t timeLeft16() {   // остаток времени в 0-65535
        return max(65535 - _buf * 65535l / _prd, 0ul);
    }
    
    uint32_t uptime() {         // на случай использования в других фреймворках
        return millis();
    }

private:
    uint32_t _tmr = 0, _prd = 1000, _buf = 0;
    bool _state = 0, _mode = 0, _ready = 0;
    void (*_handler)() = NULL;
};
static TimerMs PreCondTimer(precond_time_out.val32, 1, 1);  // define instances of timers
static TimerMs ChargingTimer(charge_time_out.val32, 1, 1);  //
static TimerMs TopOffTimer(topoff_time_out.val32, 1, 1);    //
static TimerMs ADCTimer(5000, 1, 1);                        //
#ifdef LCD_i2c
static TimerMs lcdTimer(5000, 1, 1); 
#endif
/*void enters_sleep()                                       // sleep function not required at this stage
{  
  MCUCR&=~(1<<SM0);      // enabling sleep mode and idle sleep mode
  MCUCR&=~(1<<SM1);      // enabling sleep mode and idle sleep mode
  MCUCR|= (1<<SE);     //Enabling sleep enable bit
 #ifdef Debugit
   Debug.println("sleeping");
  #endif
  __asm__ __volatile__ ( "sleep" "\n\t" :: ); //Sleep instruction to put controller to sleep
  //controller stops executing instruction after entering sleep mode  

}*/

/*
memory map
address var               size
0       i2caddress        1 byte
1       deviceName        6 bytes
7       Version           3 bytes
10       VOC               4 bytes
14       COC               4 bytes
18      preCondTOut       4 bytes
22      ChargingTOut      4 bytes
26      TopOffTOut        4 bytes

*/

char deviceName[]="btry01";         // some initail parameters
char deviceVersion[]={1,0,0};       //
byte i2caddress;                    //
int I2C_SLAVE_ADDRESS =0;           //
void setup(){
  #ifdef Debugit
    Debug.begin();
    Debug.println(F("Hello Tiny!"));
  #endif
  analogReference(INTERNAL2V56 );   // initernal voltage reference for ADC works fine but need at least 3 V on VCC
  pinMode(ADC_PIN0, INPUT);         // setting up the pin modes
  pinMode(ADC_PIN1, INPUT);         //
  pinMode(STATUS_PIN_1, OUTPUT);    //
  pinMode(STATUS_PIN_2, OUTPUT);    //
  
  I2C_SLAVE_ADDRESS = EEPROM.read(0);                // read the eeprom and decide if the controller is configured on the bus or not
  if((I2C_SLAVE_ADDRESS==0)|(I2C_SLAVE_ADDRESS==1)){ // start for the first  time, address 0 and 1 not allowed as it is to be used by master
     I2C_SLAVE_ADDRESS =10;
     strcpy(deviceName,"btry01");
     strcpy(deviceVersion, "100");
     VOC.fval=4.3;
     COC.fval =2.2;
     precond_time_out.val32 =0x0fffffff; 
     charge_time_out.val32  =0x0fffffff; 
     topoff_time_out.val32  =0x0fffffff;     
  }else if (I2C_SLAVE_ADDRESS>127){                  // insaint .. means the eeprom is corrupt or not configured on the bus
       I2C_SLAVE_ADDRESS =36;
     strcpy(deviceName,"btry02");
     strcpy(deviceVersion, "200");
     VOC.fval=4.3;
     COC.fval =2.0;
     precond_time_out.val32 =0x0ffffffe; 
     charge_time_out.val32  =0x0ffffffe; 
     topoff_time_out.val32  =0x0ffffffe; 
  }else{                                             // possibly configured on the bus .. proceed in reading the remaining of the eeprom
  //I2C_SLAVE_ADDRESS = 0x12;
  for(int k=0;k<6;k++){
    deviceName[k]=EEPROM.read(k+1);
    delay(75);
  }
  for(int k=0;k<3;k++){
    deviceVersion[k]=EEPROM.read(k+7);
    delay(75);
  }
  for(int k=0;k<4;k++){
    VOC.bval[k]=EEPROM.read(k+10);
    delay(75);
  }
  if (!((VOC.fval>0)&(VOC.fval<15))){
      VOC.fval=4.3;
    }
    for(int k=0;k<4;k++){
    COC.bval[k]=EEPROM.read(k+14);
    delay(75);
    
  }
  if (!((COC.fval>0)&(COC.fval<10.0))){
      COC.fval=2.2;
    }
    for(int k=0;k<4;k++){
    precond_time_out.bval[k]=EEPROM.read(k+18);
    delay(75);
  }
  if (!((precond_time_out.val32>0)&(precond_time_out.val32<0x0fffffff))){
      precond_time_out.val32=0x0fffffff;
    }
      for(int k=0;k<4;k++){
    charge_time_out.bval[k]=EEPROM.read(k+22);
    delay(75);
  }
  if (!((charge_time_out.val32>0)&(charge_time_out.val32<0x0fffffff))){
      charge_time_out.val32=0x0fffffff;
    }
      for(int k=0;k<4;k++){
    topoff_time_out.bval[k]=EEPROM.read(k+26);
    delay(75);
  }
    if (!((topoff_time_out.val32>0)&(topoff_time_out.val32<0x0fffffff))){
      topoff_time_out.val32=0x0fffffff;
    }
  }
  #ifdef slave_i2c  
    TinyWireS.begin(I2C_SLAVE_ADDRESS);                 // initialize the i2c
    TinyWireS.onRequest(requestEvent);                  // set the interrupt handler of read to the requestEvent
    TinyWireS.onReceive(recieveEvent);                  // set the interrupt handler of write to the receiveEvent
  #endif
  #ifndef slave_i2c 
   #ifdef LCD_i2c
    TinyWireM.begin(); 
    lcd. init (); 
    lcd. backlight ();
    lcd. print("Battery Manager");
    lcd.setCursor(1,1);
    lcd. print  ("Welcome ");  // Print a message to the LCD.
    delay(100);
    Debug.println("LCD attached");    
  //lcd.clear();                          // display it
  //lcd.print("C: ");
     lcdTimer.setPeriodMode();                          // initialize the ADC timer
     lcdTimer.setTime(500);                             // tick is 100 millis
     lcdTimer.start();                                  // start

   #endif
  #endif

  
     ADCTimer.setPeriodMode();                          // initialize the ADC timer
     ADCTimer.setTime(100);                             // tick is 100 millis
     ADCTimer.start();                                  // start
// enters_sleep(); // disabled as not needed at the current time .. will be required for ultra low power consumption

}

int order=0;                      // parametr to organize the read of volt and current with gap of 100 millis between each
float rawvolts,rawamps  = 0 ;     // raw volts and amps initialization as global 
void loop(){


#ifdef slave_i2c
  TinyWireS_stop_check();         // to clear the i2c interrupt 
#endif
 if (ADCTimer.tick()){            // timer schedule on ADC added to allow conversion. and permit CPU to response on i2c requests
    EOC = 0.1* COC.fval;
    EOT = 0.025 * COC.fval;
     if (order == 0){ 
     for (int r=0;r<5;r++){       // multiple reading and average... loop structure available.. i removed the average as the reading is accurate
      rawvolts = analogRead(ADC_PIN0);   
     }
     order++;
     }else if (order==1){
     for ( int r=0;r<5;r++) // multiple reading and average... loop structure available.. i removed the average as the reading is accurate
     {
      rawamps = analogRead(ADC_PIN1);     
     }
     order=0;
     }
    batteryVoltReading.fval = ((rawvolts*5.12)-(rawamps*2.56))/1023;  // after trial the battery voltage is lower than the charging voltage since current on bypass is accomulated
                                                                      // by subtract the voltage read on the bypass the result is exact to battery voltage
                                                                      // consider correction .. as voltage divider shall allow high voltage charger. the divider shall be passed as parameter in future
    batteryCurrentReading.fval = rawamps*0.005;                       // the current value depends on the bypass shunt resistor.. consider correction here.. and allow replace the resistor
 }                                                                    // for future shall make this equation parametrical

 chargeStatus = chargeState(chargeStatus,batteryVoltReading.fval ,batteryCurrentReading.fval);    // main state machine for battery charging called here. shall be at loop speed.
//enters_sleep();
#ifdef LCD_i2c
if (lcdTimer.tick()){ 
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print ("V:");
    lcd.setCursor(2, 0);
    lcd.print(batteryVoltReading.fval);
    lcd.setCursor(6, 0);
    lcd.print("/");
    lcd.setCursor(7, 0);
    lcd.print(VOC.fval);
    lcd.setCursor(12, 0);
    switch (chargeStatus){
      case 0: lcd.print("int"); break;
      case 1: lcd.print("pre"); break;
      case 2: lcd.print("chrg"); break;
      case 3: lcd.print("full"); break;
      case 4: lcd.print("main"); break;
      case 5: lcd.print("bad"); break;
      case 6: lcd.print("no"); break;
    }
    
    lcd.setCursor(0, 1);
    lcd.print("A:");
    lcd.setCursor(2, 1);
    lcd.print(batteryCurrentReading.fval);
    lcd.setCursor(6, 1);
    lcd.print("/");
    lcd.setCursor(7, 1);
    lcd.print(COC.fval);
    lcd.setCursor(12, 1);
    switch (chargeStatus){
      case 5: lcd.print("b:bad"); break;
      case 6: lcd.print("b:no"); break;
      default: lcd.print("b:ok"); break;
    }
}
#endif
}


byte chargeState(byte charge_state, float batteryVolt, float batteryCurrent) {


  switch (charge_state)
  {
    case initial:
    
    if (batteryVolt >= 4.5)                           // these values are specific for Li ion batteries
    {
      charge_state = nobattery;                       // select the proper state 
     /*#ifdef Debugit
        Debug.println("from initial to no battery");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);                 // make sure not to charge or discharge accedently ..
     digitalWrite(STATUS_PIN_1,LOW);                  // shall turn off both commands
    }else if(batteryVolt < 3)
    {
       PreCondTimer.setPeriodMode();                  // initiate the precodition timer
       PreCondTimer.setTime(precond_time_out.val32);  // set the timer value note the value is in milli seconds .. often the precondition take 2 hours or more.
       PreCondTimer.start();                          // starts the timer counting
       charge_state = precond;                        // select the proper state
      /*#ifdef Debugit
        Debug.println("from initial to Preconditioning");
       #endif*/
       digitalWrite(STATUS_PIN_2,LOW);                // make sure not to charge or discharge accedently ..
     digitalWrite(STATUS_PIN_1,LOW);                  // shall turn off both commands
    }else 
    {
      charge_state = maintenance;                     // select the proper state
     /*#ifdef Debugit
       Debug.println("from initial to Maintenance");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);                 // make sure not to charge or discharge accedently ..
     digitalWrite(STATUS_PIN_1,LOW);                  // shall turn off both commands
    }
    return charge_state;
    break;
    case precond:
    if (batteryVolt > 3.0)                            // if voltage reaches 3V then the battery is allowed to charge in charging mode and enables current to araise
    {
     ChargingTimer.setPeriodMode();                   // initiate the charging timer
     ChargingTimer.setTime(charge_time_out.val32);    // set time from the eeprom .. note charging take about 3-4 hours and value is in milli seconds
     ChargingTimer.start();                           //
     charge_state = charging;                         //
    /*#ifdef Debugit
      Debug.println("from Preconditioning to Charging");
     #endif*/
     digitalWrite(STATUS_PIN_2,LOW);                  //
     digitalWrite(STATUS_PIN_1,LOW);                  //
    }else if (PreCondTimer.tick())                    // check if precondition time is out.. if out with out reaching 3volts the battery consider bad.. 
    {
     charge_state = badbattery ;                      // select the proper next state
    /*#ifdef Debugit
      Debug.println("from Preconditioning to bad Battery");
     #endif*/
     digitalWrite(STATUS_PIN_2,LOW);                  //
     digitalWrite(STATUS_PIN_1,LOW);                  //
    }else 
    {
      charge_state = precond;                              // stay in precoditioning
     /*#ifdef Debugit
       Debug.println("stay in preconditioning");
       Debug.println(EOC);
      #endif*/
      PB3_Analog=PID1(batteryCurrent,EOC,0.1,0,0,0,100);   // PID loop to control charging voltage based on consumed current
     /*#ifdef Debugit
       Debug.println(PB3_Analog);
      #endif*/
      digitalWrite(STATUS_PIN_2,pwmDuty(PB3_Analog));      // convert the PID anaolg out put to pulse width modulation.. for future may be removed from the function and placed in the main loop
      digitalWrite(STATUS_PIN_1,LOW);                      // make sure charging and discharging not occuring the the same time
    }
    return charge_state;
    break;
    case charging:
    if ((batteryVolt > 4.3) and (batteryCurrent < EOC))  // condition to exit the charging state.. once battery volts is more than 4.3 and its current drops below the specific capacity charge.
    {                                                    // move to fully charged mode or simply .. TOP OFF
      TopOffTimer.setPeriodMode();                       // initiate the timer for fully charged
      TopOffTimer.setTime(topoff_time_out.val32);        // set period in milli second
      TopOffTimer.start();                               // start timer
      charge_state = timertopoff;                        // move to next proper state
     /*#ifdef Debugit
       Debug.println("from Charging to TopOff");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);                    // turn off charging output
      digitalWrite(STATUS_PIN_1,LOW);                    // turn off discharging output
    }else if (batteryVolt < 3.0)
    {
      PreCondTimer.setPeriodMode();                  // initiate the precodition timer
       PreCondTimer.setTime(precond_time_out.val32);  // set the timer value note the value is in milli seconds .. often the precondition take 2 hours or more.
       PreCondTimer.start();                          // starts the timer counting
       charge_state = precond;                        // select the proper state
      /*#ifdef Debugit
        Debug.println("from initial to Preconditioning");
       #endif*/
       digitalWrite(STATUS_PIN_2,LOW);                // make sure not to charge or discharge accedently ..
     digitalWrite(STATUS_PIN_1,LOW);                  // shall turn off both commands
    }else
    {
      if (ChargingTimer.tick())                          // if charging time out
      {
        charge_state = badbattery ;                      // the battery consider bad.. just to mention battery could timer out is the charger voltage is low and current is high.. always check schematic and measure both
       /*#ifdef Debugit
         Debug.println("from Charging to bad battery");
        #endif*/
        digitalWrite(STATUS_PIN_2,LOW);                  // turn off  charging output
        digitalWrite(STATUS_PIN_1,LOW);                  // turn off discharing output
      }else
      {
        charge_state = charging ;                        // stay in charging mode
       /*#ifdef Debugit
         Debug.println("Stay in Charging ");
        #endif*/
        PB3_Analog=PID1(batteryVolt,VOC.fval,200,0.01,0.00001,0.0,100.0);   // PID controller to control charging voltage vs charging setpoint 
        digitalWrite(STATUS_PIN_2,pwmDuty(PB3_Analog));                     // convert PID percentage output to pulse width modulation
        digitalWrite(STATUS_PIN_1,LOW);                                     // turn off unnecessary output
      }
    }
    return charge_state;
    break;
    case timertopoff:
    if (batteryVolt > 4.5)                              // while battery being fully charged
    {
      charge_state = nobattery;
     /*#ifdef Debugit
       Debug.println("from TopOff to bad battery");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);
      digitalWrite(STATUS_PIN_1,LOW);
    }else
    {

     if (batteryCurrent < EOT)                          // if battery current droped below end of charge current.. its a function of fully charged battery current parameter 
     {
      charge_state = maintenance;                       // battery current required to be maintained by charging
     /*#ifdef Debugit
       Debug.println("from TopOff to maintenance");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);
      digitalWrite(STATUS_PIN_1,LOW);

     }else{
     if (TopOffTimer.tick())                            // if top off timer ..times out
     {
      charge_state = maintenance ;
     /*#ifdef Debugit
        Debug.println(TopOffTimer.elapsed());
        Debug.println("from TopOff to maintenance time out");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);
      digitalWrite(STATUS_PIN_1,LOW);
     }else
     {
      charge_state = timertopoff;
     /*#ifdef Debugit
       Debug.println("stay in TopOff");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);
      digitalWrite(STATUS_PIN_1,LOW);
     }
     }
     return charge_state;
    break;
    case maintenance:                                   //  in maintenance
    if (batteryVolt < 4.0)                              // check if battery self discharged
    {
      ChargingTimer.setPeriodMode();                    // charge again
      ChargingTimer.setTime(charge_time_out.val32);
      ChargingTimer.start();     
      charge_state = charging;
     /*#ifdef Debugit
       Debug.println("from maintenance to Charging");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);
      digitalWrite(STATUS_PIN_1,LOW);
    }else
    {
      if (batteryVolt > 4.5)                            // if battery voltage reaches the charger voltage means no battery available
      {
        charge_state = nobattery;
       /*#ifdef Debugit
          Debug.println("from maintenance to no battery");
        #endif*/
        digitalWrite(STATUS_PIN_2,LOW);
        digitalWrite(STATUS_PIN_1,LOW);
      }else{
        if (batteryVolt > 4.3)                                              // if over charged
      {
        charge_state = maintenance;
       /*#ifdef Debugit
          Debug.println("stay in maintenance");
        #endif*/
        PB1_Analog= PID1(batteryVolt,VOC.fval,200,0.01,0.0001,-100,100.0);  // PID loop to control discharge to maintain the voltage reading and the battery specific voltage setting
        digitalWrite(STATUS_PIN_1,pwmDuty(abs(PB1_Analog)));                // convert the PID output pulse width modulation
        digitalWrite(STATUS_PIN_2,LOW);                                     // disable the charging output.
      }else{
       ChargingTimer.setPeriodMode();                                       // this state added for not to stuck in the maintenance state
       ChargingTimer.setTime(charge_time_out.val32);                        // as maintenance state is trasitional to determind what to do and has no timer
       ChargingTimer.start();                                               // after chacking all conditions and found nothing .. we send it back to charging
       charge_state = charging;                                             // the charging state then charge or topoff
      /*#ifdef Debugit
        Debug.println("from maint to TopOff");
       #endif*/
      digitalWrite(STATUS_PIN_2,LOW);
      digitalWrite(STATUS_PIN_1,LOW);
      }
    }
    }
    return charge_state;
    break;
    case badbattery:
   /*#ifdef Debugit
      Debug.println("bad battery");                                         // if bad battery nothig is moving.. fix .. replace or disconnect and connect back
    #endif*/
    digitalWrite(STATUS_PIN_2,LOW);                                         // here these is a risk to stuck in this state and require human introvention
    digitalWrite(STATUS_PIN_1,LOW);
    return charge_state;
    break;
    case nobattery:                                                         // no battery state
    if (batteryVolt < 4.5)                                                  // checks if battery hawked up again
    {
      PreCondTimer.start();                                                 // start from first state
      charge_state = precond;
     /*#ifdef Debugit
        Debug.println("from no battery to preconditioning");
      #endif*/
      digitalWrite(STATUS_PIN_2,LOW);
      digitalWrite(STATUS_PIN_1,LOW);
    }
    return charge_state;
    break;
    default:                                                              // default state is initiallizaiton
    charge_state = initial;
    digitalWrite(STATUS_PIN_2,LOW);
    digitalWrite(STATUS_PIN_1,LOW);
    return charge_state;
    break;   
  }
   /*#ifdef Debugit
      Debug.println(charge_state);
    #endif*/
    return charge_state;
    break;
}
}

#ifdef slave_i2c
volatile uint8_t i2c_rx_buff[16];                                        // setup the i2c buffers
volatile uint8_t i2c_tx_buff[] =                                         //
{
    0, // i2c address                                                    // the communication bytes map
    0, // i2c register
    0, // first 
    0, // second
    0, // third
    0, // fourth   
    0, // fifth   
    0, // Sixth   
    0  // seventh  
};

volatile byte pos,reg_position, tx_reg_position, request_register = 0;  // global variables needed in i2c functions
const byte reg_size = sizeof(i2c_rx_buff);                              //
const byte tx_reg_size = sizeof(i2c_tx_buff);                           //
int receiveCount;                                                       // 
void requestEvent()                                                     // the request event function executed after the receive event function
{                                                                       // processing the read requests
  
  if(reg_position==0)                                                   // register position is 0 only in read requests .. this is to make sure not to conflect between read and write
{
 switch (request_register)                                              // check what data to read
 {
    case 0:// reg address equals i2c slave device address               // reads the own i2c address
      {
      i2c_tx_buff[0] = I2C_SLAVE_ADDRESS;                               // filling buffer
      tx_reg_position = 1;                                              // filling the buffer size
      
      for (int i = 0;i<tx_reg_position;i++){                            // loop to send the data .. note data is sent byte by byte
      TinyWireS.send (i2c_tx_buff[i]);
       }
       }
       break;
  case 1:// reg address equals i2c slave device name                    // reads the i2c device name .. 6 bytes
       {
       for(int i=0;i<strlen(deviceName);i++){                           // filling buffer
       i2c_tx_buff[i]=deviceName[i];
       }
        tx_reg_position = 6;                                            // filling buffer length
      for (int i = 0;i<tx_reg_position;i++){                            // sending data byte by byte
      TinyWireS.send (i2c_tx_buff[i]);
       }  
        }
        break;   
  case 2:// reg address equals version do nothing version is flashed once deviceVersion
       {
       for(int i=0;i<strlen(deviceVersion);i++){                        // device version
       i2c_tx_buff[i]=deviceVersion[i];
       }
        tx_reg_position = strlen(deviceVersion);
      for (int i = 0;i<tx_reg_position;i++){                            // sending data byte by byte
      TinyWireS.send (i2c_tx_buff[i]);
       }      
        }
        break;
  case 3:// reg address equals battery status do nothing                // battery status 
       {
       byte batteryStatus =0;                                     
       if (chargeStatus == nobattery) {                                // filling data depend on battery state 
        batteryStatus =  0;                                            // 0.. for no battery
       }else if (chargeStatus == badbattery)                           // 1.. for bad battery
       {
        batteryStatus =  1;                                           
       }else
       {
        batteryStatus =  2;                                            // 2.. for good battery
       }
       
       i2c_tx_buff[0] = batteryStatus;
      tx_reg_position = 1;
      
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }
        }
        break;
  case 4:// reg address equals charging status do nothing             // charging state machine status
       {
       i2c_tx_buff[0] = chargeStatus;
       tx_reg_position = 1;
      
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }       
        }
        break;
  case 5:// reg address equals charge VOC  which is battery VOC setting // volt of charge setting float type takes 4 bytes
       {
        
       for(int i=0;i<4;i++){
       i2c_tx_buff[i]=VOC.bval[i];
       }
        tx_reg_position = 4;
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }
        }
        break;
  case 6:// reg address equals charge COC  which is battery COC setting // current of charge setting .. float type takes 4 bytes
       {
       for(int i=0;i<4;i++){
       i2c_tx_buff[i]=COC.bval[i];
       }
        tx_reg_position = 4;
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }       
        }
        break;  
  case 7:// reg address equals precodintion time out precond_time_out  in milli seconds unsiged 32 takes 4 bytes
       {
       for(int i=0;i<4;i++){
       i2c_tx_buff[i]=precond_time_out.bval[i];
       }
        tx_reg_position = 4;
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }       
        }
        break;   
  case 8:// reg address equals charge time out charge_time_out  in milli seconds unsiged 32 takes 4 bytes
       {
       for(int i=0;i<4;i++){
       i2c_tx_buff[i]=charge_time_out.bval[i];
       }
        tx_reg_position = 4;
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }       
        }
        break;     
  case 9:// reg address equals topoff time out topoff_time_out  in milli seconds unsiged 32 takes 4 bytes
       {
        for(int i=0;i<4;i++){
       i2c_tx_buff[i]=topoff_time_out.bval[i];
       }
        tx_reg_position = 4;
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }       
        }
        break; 
  case 10: // actual battery volts batteryVoltReading float type takes 4 bytes
       {
        for(int i=0;i<4;i++){
       i2c_tx_buff[i]=batteryVoltReading.bval[i];
       }
        tx_reg_position = 4;
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }       
      }
       break; 
    case 11: // actual battery Current batteryCurrentReading float type takes 4 bytes
       {
       for(int i=0;i<4;i++){
       i2c_tx_buff[i]=batteryCurrentReading.bval[i];
       }
        tx_reg_position = 4;
      for (int i = 0;i<tx_reg_position;i++){
      TinyWireS.send (i2c_tx_buff[i]);
       }       
       }
     break; 
   default:
       break;
  }
    reg_position = 0;
   // receiveCount = 0; 
 }
}



void recieveEvent(int howMany)          // receive event triggered by interrupt once receiving any data in i2c .. not the how many in tiny85 not working but works on arduino
{    
  
  receiveCount = TinyWireS.available(); // number of byte available on i2c to receive
  int p = receiveCount;                 // initialize the receving counter
      if (p   < 1)                      // less than 1 means to check if device is alive and usually it is used to check the addresses on the bus
    {
        // Sanity-check
        return;                         // exit
    }
    if (p > sizeof(i2c_rx_buff))        // recived data bigger than the buffer size .. will not be able to read it.
    {
        // Also insane number
        return;                         // exit
    }

    request_register = TinyWireS.receive();   // receive the first byte .. i used the first byte to be the requested register.. not like modbus or bacnet
    // Serial.print("request_register..");
    // Serial.println(request_register);
    p--;                                      // reduce the counter by one
    if (!p)                                   // if the count become zero this means read request 
    {
        // This write was only to set the buffer for next read
        return;                               // exit to allow read interrupt calling request Event function
    }
    while(p--)                                // if reached this point means we have qualified write command to process
    {
       i2c_rx_buff[reg_position] = TinyWireS.receive(); // filling the read buffer
        reg_position++;                                 // increment the receive counter
    }
  
  if(reg_position>0){                        // double check the number of received bytes

    // Serial.println("Processing Update request");
           
   switch (request_register){                             // processing the write command based on the first received byte
    //digitalWrite(STATUS_PIN_1,~STATUS_PIN_1);
    case 0:// reg address equals i2c slave device address
       {
       I2C_SLAVE_ADDRESS = i2c_rx_buff[0];                //  the receieved is update to i2c slave address
       // write i2caddress to eeprom
       
       EEPROM.update(0,I2C_SLAVE_ADDRESS);                // save the recieved address to eeprom 
       }
       break;
  case 1:// reg address equals i2c slave device name
        {
        for (int i=0;i<6;i++)
        {
        deviceName[i]=i2c_rx_buff[i]; 
        }    
       // write deviceName to eeprom
       //cli();
       for(int k=0;k<6;k++){
         EEPROM.update(1+k,deviceName[k]);
         //delay(45);
       }
       // optional send confirmation
       // reboot
       }
       break;   
  case 2:// reg address equals version do nothing version is flashed once
       break;
  case 3:// reg address equals battery status do nothing 
       break;
  case 4:// reg address equals charging status do nothing 
       break;
  case 5:// reg address equals charge VOC  which is battery VOC setting
        {
        for (int i=0;i<4;i++)
        {
        VOC.bval[i]=i2c_rx_buff[i]; 
        }
       // write VOC to eeprom
              for(int k=0;k<4;k++){
         EEPROM.update(10+k,VOC.bval[k]);         
       }
      // reboot
       }
       break;
  case 6:// reg address equals charge COC  which is battery COC setting
        {
        for (int i=0;i<4;i++)
        {
        COC.bval[i]=i2c_rx_buff[i]; 
        }
       // write COC to eeprom
         for(int k=0;k<4;k++){
         EEPROM.update(14+k,COC.bval[k]);
       }
       
       // optional send confirmation
       // reboot
       }
       break;    
  case 7:// reg address equals precodintion time out
       {
        for (int i=0;i<4;i++)
        {
        precond_time_out.bval[i]=i2c_rx_buff[i]; 
        }
       // write precond_time_out to eeprom
         for(int k=0;k<4;k++){
         EEPROM.update(18+k,precond_time_out.bval[k]);
       }
       
       // optional send confirmation
       // reboot
       }
       break;   
  case 8:// reg address equals charge time out
       {
        for (int i=0;i<4;i++)
        {
        charge_time_out.bval[i]=i2c_rx_buff[i]; 
        }
       // write charge_time_out to eeprom
       for(int k=0;k<4;k++){
         EEPROM.update(22+k,charge_time_out.bval[k]);
       }
       
       // optional send confirmation
       // reboot
       }
       break;   
  case 9:// reg address equals topoff time out
       {
        for (int i=0;i<4;i++)
        {
        topoff_time_out.bval[i]=i2c_rx_buff[i]; 
        }
       // write topoff_time_out to eeprom
       for(int k=0;k<4;k++){
         EEPROM.update(26+k,topoff_time_out.bval[k]);
       }
       // optional send confirmation
       // reboot
        }
       break;
  default:
       break;
  
 }
 reg_position = 0;
}
}
#endif


int lastOutput = 0;                 // initialize global parameters for PID function
float err= 0;                       //
double lastMillis = 0;              //
double _integral = 0;               //
double _dt = 0;                     //
double _pre_error = 0;              //
float PID1(float PV, float _SP, float kp, float ki, float kd,float _min, float _max ) // fisrt PID instance function
{

    _dt = millis() - lastMillis;
    // Calculate error
    float err = _SP - PV;

    // Proportional term
    float Pout = kp * err;

    // Integral term
    _integral += err * _dt;
    double Iout = ki * _integral;

    // Derivative term
    double derivative = (err - _pre_error) / _dt;
    double Dout = kd * derivative;

    // Calculate total output
    double output = Pout + Iout + Dout;

    // Restrict to max/min
    if( output > _max )
        output = _max;
    else if( output < _min )
        output = _min;

    // Save error to previous error
    _pre_error = err;
    lastMillis = millis();
    lastOutput = output;
  

return lastOutput;
}

bool lstOutput = false;             // initialize the global parameters for the pwm function
int dcount= 0;                      // 
bool pwmDuty(int duty)              //  pwm convert the pid output from percentage to train of pulses
{
 /*#ifdef Debugit
   Debug.println(duty);
  #endif*/
if (duty == 0 ){
  lstOutput = false;
  dcount = 0;
 /*#ifdef Debugit
   Debug.println("duty 0");
  #endif*/
}else if (duty==100){
  lstOutput = true;
  dcount = 0;
 /*#ifdef Debugit
   Debug.println("duty 100");
  #endif*/
}else if ((dcount > 100 - duty) & (lstOutput == false))
{
lstOutput = true;
dcount = 0;
/*#ifdef Debugit
 Debug.println("on");
#endif*/
} else if ((dcount > duty) & (lstOutput == true)){
lstOutput = false;
dcount = 0;
/*#ifdef Debugit
 Debug.println("off");
#endif*/
} else{
 /*#ifdef Debugit
    Debug.println("count up");
  #endif*/
  dcount++;
}
return lstOutput;

}
ATTINY8520PU