Wire.onReceive doesn't report global variables or program freezes. I2C problems

Hi
I currently decided to make few relay output modules and drive them from Master arduino board (from Serial TTL) via I2C. I decided to use one I2C address for all slave devices, but to use different identifiers. If to drive relays only, it works perfectly, but when I added code to report relay status back, it started behaving badly. It seems it works for first command or couple sometimes, but eventually freezes. I discovered that this problem is possibly related to interrupt routine on slave device because it cannot handle global variables to send it back to the master, but I cannot figure out how to fix it. I searched the forum for the answer but couldn’t adopt any advices to my situation. Can someone have a look please and advice. My next task to have slave devices with DS18B20 on board and get temperatures back, but I am not sure is it worth trying via I2C anymore. Thank you.
There’s the code for I2C master device, what gets commands from serial com port as example: R 3 1 2 - what would mean “Header” - R, device identifier - 3, comand - 1 to turn output on, and 2 - is second relay.

#include <EEPROM.h>
#include <Wire.h>
 int who_we_ask_for;
 int functionality_ID;
 int function_details;
//-----------------------------------------------------------------------------
void setup()
{
    Serial.begin(9600);
    Wire.begin();  // Serial.println("IMaster");      
}
//-----------------------------------------------------------------------------
//________________________________________________________________________________________________________________________________ 
void loop() 
{
   check_serial_commands();
}         
//________________________________________________________________________________________________________________________________ 
//-----------------------------------------------------------------------------
 void check_serial_commands()
{
 int read_data_from_serial[4];
 int i;
   if (Serial.available())
      {                   
          if (Serial.peek() == 'R')//My device identifier
          {
              for (i = 1; i < 4; i++)
              {
                Serial.read();
                read_data_from_serial[i] = Serial.parseInt();
                who_we_ask_for               = read_data_from_serial[1];
                functionality_ID             = read_data_from_serial[2];
                function_details             = read_data_from_serial[3];
              }
              send_commands_via_I2C();  //here we send commnds via I2C
          } 
     }
 }//check_serial_commands end
//-----------------------------------------------------------------------------
void send_commands_via_I2C()
{
             Wire.beginTransmission(10); // transmit to device 10
             Wire.write('R');         //send my device identifier
             Wire.write(who_we_ask_for);
             Wire.write(functionality_ID);
             Wire.write(function_details);
             request_the_slave_for_details(2);  //request 2 bytes
             Wire.endTransmission();    // stop transmitting 
} //send_commands_via_I2C end
 //-----------------------------------------------------------------------------
 void request_the_slave_for_details(int how_many)
{
  byte buf[how_many];
  int n = Wire.requestFrom(10, how_many);   // request 2 bytes from slave device #10
  for( int i = 0; i<n; i++)
  {
    buf[i] = Wire.read();
  }
   for( int i = 0; i < how_many; i++)
      {
        Serial.println (buf[i]);
      }
}
//-----------------------------------------------------------------------------

And here the I2C slave code:

#include <EEPROM.h>
#include <Wire.h>

byte relay[8] = {9,8,7,6,5,4,3,2};
byte relay_status[8];
char received_header;
byte requested_to;
byte request_type;
byte request_details;
volatile byte Unique_ID = 3;  //Lets say this device is number 3
volatile byte result;
//-----------------------------------------------------------------------------
void setup()
{
for (int thisPin = 0; thisPin < 8; thisPin++)
  {
    pinMode(relay[thisPin], OUTPUT);
    digitalWrite(relay[thisPin], LOW);  //turn relay off
    relay_status[thisPin] = false;
  }
    Serial.begin(9600);
    Wire.begin(10);  //My (I am Slave) address is 10
    Wire.onRequest(requestEvent); //
    Wire.onReceive(receiveEvent); //
}
//-----------------------------------------------------------------------------
//________________________________________________________________________________________________________________________________ 
void loop() 
{
}         
//________________________________________________________________________________________________________________________________ 
//-----------------------------------------------------------------------------
void requestEvent()   //Reply via I2C
{
      byte I2C_data_buffer[2];
      I2C_data_buffer[0] = Unique_ID; //load the device unique ID to buffer
      I2C_data_buffer[1] = result;    //load the 8 relay status in decimal into buffer
      Wire.write( I2C_data_buffer, 2);  //send data buffer containing 2 values
}
//-----------------------------------------------------------------------------
void receiveEvent(int how_many) //Receive command via I2C
{
 while (Wire.available())
 {
    received_header = Wire.read();
    requested_to    = Wire.read();
    request_type    = Wire.read();
    request_details = Wire.read();
 }
 if (received_header == 'R')
  {
    check_commands();
  }
}
 //-----------------------------------------------------------------------------
void check_commands()
{
  if (requested_to == Unique_ID)      //is this info for me???
    {
     switch (request_type)
     {
       case 1:       
          digitalWrite(relay[request_details], HIGH); //requested relay must go on
          relay_status[request_details] = true;       //mark flag for reading output status
          calculate_the_answer();
          break;
       case 2:
          digitalWrite(relay[request_details], LOW);    //requested relay must go off
          relay_status[request_details] = false;        //mark flag for reading output status
          calculate_the_answer();
          break;
       case 3:
          calculate_the_answer(); //calculate the 8 relay status in decimal and report when requested
          break;
       default: 
       break;
    } //switch close
  } //if close
} //check_commands close
  //-----------------------------------------------------------------------------
void calculate_the_answer() //calculate the 8 relay status in decimal
{
          result = 0;
          if (relay_status[0] == true) result = result + 128;
          if (relay_status[1] == true) result = result + 64;
          if (relay_status[2] == true) result = result + 32;
          if (relay_status[3] == true) result = result + 16;
          if (relay_status[4] == true) result = result + 8;
          if (relay_status[5] == true) result = result + 4;
          if (relay_status[6] == true) result = result + 2;
          if (relay_status[7] == true) result = result + 1; 
}
//-----------------------------------------------------------------------------

I discovered that this problem is possibly related to interrupt routine on slave device because it cannot handle global variables to send it back to the master

This statement is nonsense. Whether the data to send back is held in a local variable, a global variable, a register, or on an SD card is irrelevant.

   if (Serial.available())
      {                   
          if (Serial.peek() == 'R')//My device identifier
          {
              for (i = 1; i < 4; i++)
              {
                Serial.read();

When there is one byte of serial data to read, and the byte is an ‘R’, it is NOT OK to read 4 bytes.

Why is it necessary to assign values to who_we_ask_for, functionality_ID, and function_details more than once?

             request_the_slave_for_details(2);  //request 2 bytes

In the middle of a transmission? I don’t think so.

  int n = Wire.requestFrom(10, how_many);   // request 2 bytes from slave device #10
  for( int i = 0; i<n; i++)
  {
    buf[i] = Wire.read();
  }

Request 2 bytes. Expect an immediate response. Hasn’t worked for anyone else. What makes you so special?

volatile byte Unique_ID = 3;  //Lets say this device is number 3
volatile byte result;

Why are these volatile?

 while (Wire.available())
 {
    received_header = Wire.read();
    requested_to    = Wire.read();
    request_type    = Wire.read();
    request_details = Wire.read();
 }

More nonsense. When there is one byte to read, it is NOT OK to read more than one. Get over it.

It does not make sense for the slaves to figure out if they are actually supposed to respond. Use different addresses for each slave, and have the master be smart enough to ask ONE device for information.

The Serial read always works and echoes back when tested.

Because you spend time doing other things. That does NOT make the code right. It simply means that you haven't been burned yet.

Four times Wire.read works as well.

You've been lucky.

I can see that I'm wasting my time here. Bye.

Lobobo, when writing a sketch, you should do it right, not deliberately wrong.
On the forum and in many libraries, the use of the Wire library is wrong. Even the examples in the Arduino reference are confusing.

Writing to a Slave :

  • Wire.beginTransmission
  • Wire.write (up to 32 times or even not at all)
  • Wire.endTransmission (an error is returned)
    The I2C transmission is done by Wire.endTransmission. The Wire.write only writes data in a transmit-buffer.

Reading from a Slave:

  • Wire.requestFrom (the number of read bytes is returned)
  • Wire.readBytes (or Wire.read or perhaps Wire.available)
    The Wire.requestFrom does the complete I2C transmission. It returns after that has finished and returns the number of received bytes that are waiting in the receive-buffer. Wire.available() can also be used to determine the number of bytes in the receive-buffer.
    Use Wire.readBytes to read everything in your own buffer, or one byte at a time with Wire.read.

You may not mix writing and reading.