Мини-теплица, управляемая Arduino Mega

Тема в разделе "Глядите, что я сделал", создана пользователем Vir, 11 дек 2012.

  1. Vir

    Vir Гик

    Всем привет.

    Я нахожусь на стадии разработки проекта, на Arduino Mega, по управлению мини-теплицей.

    Цели:
    • Возможность обмениваться с Arduino, беспроводными способами, для передачи различных команд и получения ответов на них (Xbee слишком много сыпет, XBee-USB адаптер + USB 2.0);
    • Автоматическое снятие показаний с датчиков, следящими за внешними условиями:
      1. Температура (внутренняя) (подключение двух модулей одновременно);
      2. Температура (внешняя);
      3. Влажность (внутренняя);
      4. Влажность (внутренняя);
      5. Влажность (почвы);
      6. Освещенность;
      7. Наличие дыма (стабильность датчика газа);
    • Автоматическое реагирование на внешние условия:
      1. Повышенная температура:
        • Включение вентиляции;
        • Отключение обогрева;
        • В случае, если все принятые меры не привели к нормализации температуры - оповещать меня по SMS, и дико пищать пьезо-пищалкой;
    • Ручное управление включением/выключением аппаратуры (свет, вентиляция, полив);
      1. Управление с тач-панели (видео);
    • Автоматическое управление включением/выключением аппаратуры (свет, полив, вентиляция):
      1. В определенное время включать/выключать освещение;
      2. Через определенные промежутки времени осуществлять полив;
    • Составление статистики по температуре и влажности;
    • Отображать данные на LCD дисплее:
      1. По-умолчанию отображать данные с датчиков;
      2. При считывании RFID-сканнером карты, отображать результат считывания (верная/не верная карта);
    • Открытие/закрытие дверного замка:
      1. Считывать и проверять данные с RFID-карты, RFID-сканнером (RFID Reader. Корректное чтение данных);
      2. Открывать/закрывать замок с помощью сервопривода;
    В наличии:
    Буду рад любой помощи, и советам. По ходу проекта буду здесь отписываться и задавать вопросы.

    P.S. Зеленым цветом помечаются реализованные задачи, красным - задачи реализация, которых, на данный момент, столкнулась с проблемой (по возможности буду указывать ссылку на форум, где я прошу помощи у вас).

    P.P.S. Данный топик является, своего рода, дневником по созданию проекта на Arduino.
     
    Fenny и sycew1 нравится это.
  2. Vir

    Vir Гик

    Весь скетч, для удобочитаемости, я разбил на несколько файлов. Каждый файл отвечает за обработку какой-то конкретной задачи.

    Основной файл:
    Код (Text):
    #include <dht.h>
     
    unsigned long workTime; // Время работы Arduino с начала старта
    int DHTSensorSelector = 1; // Флаг обозначающий, с какого сенсора DHT будут сниматься данные
     
    void setup()
    {
      DHTSensorFirstInit();
      DHTSensorSecondInit();
      dynamicInit();
      gasSensorInit();
      grayscaleSensorInit();
      RFIDReaderInit();
      soilHumiditySensorInit();
      XBeeModuleInit();
     
      Serial.begin(9600);
    }
     
    void loop()
    {
      workTime = millis();
     
      if ((workTime % 1000) == 0)
      {
        if (DHTSensorSelector == 1)
        {
          DHTSensorFirstHandler();
          DHTSensorSelector = 2;
        }
        else if (DHTSensorSelector == 2)
        {
          DHTSensorSecondHandler();
          DHTSensorSelector = 1;
        }
      }
     
      gasSensorHandler();
      grayscaleSensorHandler();
      RFIDReaderHandler();
      soilHumiditySensorHandler();
      XBeeCommandListener();
    }
    DHTSensorFirst:
    Код (Text):
    #define DHT_SENSOR_FIRST_PIN 41
    DHT DHTLibFirst;
    char DHTLibFirstDbgMsg[128];
    int temperatureFirst;
    int humidityFirst;
     
    void DHTSensorFirstInit()
    {
      DHTLibFirst.attach(DHT_SENSOR_FIRST_PIN);
    }
     
    void DHTSensorFirstHandler()
    {
      DHTLibFirst.update();
     
      if (DHTLibFirst.getLastError() == DHT_ERROR_OK)
      {
        temperatureFirst = constrain(DHTLibFirst.getTemperatureInt(), 0, 40);
        humidityFirst  = constrain(DHTLibFirst.getHumidityInt(), 0, 100);
       
        if (temperatureFirst > 30 || temperatureFirst < 22)
        {
          //dynamicPlayWarning();
        }
      }
    }
     
    int getTemperatureFirst()
    {
      return temperatureFirst;
    }
     
    int getHumidityFirst()
    {
      return humidityFirst;
    }
    Аналогично выглядит и DHTSensorSecond, его приводить не буду, дабы не захламлять пост.

    Dynamic:
    Код (Text):
    #define DYNAMIC_PIN 8
     
    #define NOTE_B0  31
    #define NOTE_C1  33
    #define NOTE_CS1 35
    #define NOTE_D1  37
    #define NOTE_DS1 39
    #define NOTE_E1  41
    #define NOTE_F1  44
    #define NOTE_FS1 46
    #define NOTE_G1  49
    #define NOTE_GS1 52
    #define NOTE_A1  55
    #define NOTE_AS1 58
    #define NOTE_B1  62
    #define NOTE_C2  65
    #define NOTE_CS2 69
    #define NOTE_D2  73
    #define NOTE_DS2 78
    #define NOTE_E2  82
    #define NOTE_F2  87
    #define NOTE_FS2 93
    #define NOTE_G2  98
    #define NOTE_GS2 104
    #define NOTE_A2  110
    #define NOTE_AS2 117
    #define NOTE_B2  123
    #define NOTE_C3  131
    #define NOTE_CS3 139
    #define NOTE_D3  147
    #define NOTE_DS3 156
    #define NOTE_E3  165
    #define NOTE_F3  175
    #define NOTE_FS3 185
    #define NOTE_G3  196
    #define NOTE_GS3 208
    #define NOTE_A3  220
    #define NOTE_AS3 233
    #define NOTE_B3  247
    #define NOTE_C4  262
    #define NOTE_CS4 277
    #define NOTE_D4  294
    #define NOTE_DS4 311
    #define NOTE_E4  330
    #define NOTE_F4  349
    #define NOTE_FS4 370
    #define NOTE_G4  392
    #define NOTE_GS4 415
    #define NOTE_A4  440
    #define NOTE_AS4 466
    #define NOTE_B4  494
    #define NOTE_C5  523
    #define NOTE_CS5 554
    #define NOTE_D5  587
    #define NOTE_DS5 622
    #define NOTE_E5  659
    #define NOTE_F5  698
    #define NOTE_FS5 740
    #define NOTE_G5  784
    #define NOTE_GS5 831
    #define NOTE_A5  880
    #define NOTE_AS5 932
    #define NOTE_B5  988
    #define NOTE_C6  1047
    #define NOTE_CS6 1109
    #define NOTE_D6  1175
    #define NOTE_DS6 1245
    #define NOTE_E6  1319
    #define NOTE_F6  1397
    #define NOTE_FS6 1480
    #define NOTE_G6  1568
    #define NOTE_GS6 1661
    #define NOTE_A6  1760
    #define NOTE_AS6 1865
    #define NOTE_B6  1976
    #define NOTE_C7  2093
    #define NOTE_CS7 2217
    #define NOTE_D7  2349
    #define NOTE_DS7 2489
    #define NOTE_E7  2637
    #define NOTE_F7  2794
    #define NOTE_FS7 2960
    #define NOTE_G7  3136
    #define NOTE_GS7 3322
    #define NOTE_A7  3520
    #define NOTE_AS7 3729
    #define NOTE_B7  3951
    #define NOTE_C8  4186
    #define NOTE_CS8 4435
    #define NOTE_D8  4699
    #define NOTE_DS8 4978
     
    void dynamicInit()
    {
      pinMode(DYNAMIC_PIN, OUTPUT);
    }
     
    void dynamicPlayRFIDAccessGranted()
    {
      int RFIDAccessGrantedMelody[] = {NOTE_C4, NOTE_G3,NOTE_G3, NOTE_A3, NOTE_G3,0, NOTE_B3, NOTE_C4};
      int RFIDAccessGrantedMelodyNoteDurations[] = {4, 8, 8, 4, 4, 4, 4, 4};
     
      dynamicPlayMelody(RFIDAccessGrantedMelody, RFIDAccessGrantedMelodyNoteDurations, 8);
    }
     
    void dynamicPlayRFIDAccessDenied()
    {
      int RFIDAccessDeniedMelody[] = {NOTE_FS4, NOTE_B3,NOTE_FS4, NOTE_B3};
      int RFIDAccessDeniedMelodyNoteDurations[] = {4, 4, 4, 4};
     
      dynamicPlayMelody(RFIDAccessDeniedMelody, RFIDAccessDeniedMelodyNoteDurations, 4);
    }
     
    void dynamicPlayWarning()
    {
      int warningMelody[] = {NOTE_D8, NOTE_D8, NOTE_D8, NOTE_D8};
      int warningMelodyNoteDurations[] = {4, 8, 4, 8};
     
      dynamicPlayMelody(warningMelody, warningMelodyNoteDurations, 4);
    }
     
    void dynamicPlayMelody(int melodyNotes[], int noteDurations[], int notesCount)
    {
      for (int thisNote = 0; thisNote < notesCount; thisNote++) {
        int noteDuration = 1000/noteDurations[thisNote];
        tone(DYNAMIC_PIN, melodyNotes[thisNote], noteDuration);
        int pauseBetweenNotes = noteDuration * 1.30;
        delay(pauseBetweenNotes);
        noTone(DYNAMIC_PIN);
      }
    }
     
    GasSensor:
    Код (Text):
    #define GAS_SENSOR_PIN 8
     
    int gasValue = 0;
     
    void gasSensorInit()
    {
      pinMode(GAS_SENSOR_PIN, INPUT);
    }
     
    void gasSensorHandler()
    {
      gasValue = analogRead(GAS_SENSOR_PIN);
    }
     
    int getGas()
    {
      return gasValue;
    }
    GrayscaleSensor:
    Код (Text):
    #define GRAYSCALE_SENSOR_PIN 6
     
    int grayscaleValue = 0;
    boolean grayscaleLampStatus = false;
     
    void grayscaleSensorInit()
    {
      pinMode(GRAYSCALE_SENSOR_PIN, INPUT);
    }
     
    void grayscaleSensorHandler()
    {
      grayscaleValue = constrain(analogRead(GRAYSCALE_SENSOR_PIN), 0, 1024);
    }
     
    int getGrayscale()
    {
      return grayscaleValue;
    }
     
    int getGrayscaleLampStatus()
    {
      return grayscaleLampStatus;
    }
    RFIDReader:
    Код (Text):
    boolean RFIDScannerStartReadCardNumber = false;
    String RFIDReadedCardNumber;
    String AllowedRFIDCardNumber_1 = "67854443332129990004567";
     
    void RFIDReaderInit()
    {
      Serial3.begin(9600);
    }
     
    void RFIDReaderHandler()
    {
      if (Serial3.available())
      {
        while(Serial3.available())
        {
          byte b = Serial3.read();
     
          // Если пришло начало строки (0x02), начинаем читать
          if (b == 0x02 && !RFIDScannerStartReadCardNumber)
          {
            RFIDScannerStartReadCardNumber = true;
          }
          // Если пришёл конец строки (0x03), заканчиваем читать
          // и обрабатываем полученные данные
          else if (b == 0x03 && RFIDScannerStartReadCardNumber)
          {
            RFIDScannerStartReadCardNumber = false;
     
            // Выполнять код условия, если карточка проходит проверки
            if (RFIDReadedCardNumber == AllowedRFIDCardNumber_1)
            {
              dynamicPlayRFIDAccessGranted();
            }
            else
            {
              dynamicPlayRFIDAccessDenied();
            }
           
            RFIDReadedCardNumber = "";
          }
          // Если начало уже было, а конец ещё не пришёл,
          // последовательно читаем приходящие байты
          else if (RFIDScannerStartReadCardNumber)
          {
            RFIDReadedCardNumber += b;
          }
        }
      }
    }
    SoilHumiditySensor:
    Код (Text):
    #define SOIL_HUMIDITY_SENSOR_PIN 7
     
    int soilHumidityValue = 0;
     
    void soilHumiditySensorInit()
    {
      pinMode(SOIL_HUMIDITY_SENSOR_PIN, INPUT);
    }
     
    void soilHumiditySensorHandler()
    {
      soilHumidityValue = analogRead(SOIL_HUMIDITY_SENSOR_PIN);
    }
     
    int getSoilHumidity()
    {
      return soilHumidityValue;
    }
    XBeeModule:
    Код (Text):
    #define TEMPERATURE_FIRST_READ '1'
    #define TEMPERATURE_SECOND_READ '2'
    #define HUMIDITY_FIRST_READ '3'
    #define HUMIDITY_SECOND_READ '4'
    #define GAS_SENSOR_READ '5'
    #define SOIL_HUMIDITY_SENSOR_READ '6'
    #define GRAYSCALE_SENSOR_READ '7'
     
    char XBeeResponseMsg[128];
     
    void XBeeModuleInit()
    {
      Serial1.begin(9600);
    }
     
    void XBeeCommandListener()
    {
      if (Serial1.available() > 0) {
        char incomingCommand = Serial1.read();
       
        switch (incomingCommand)
        {
          case TEMPERATURE_FIRST_READ:
            sprintf(XBeeResponseMsg, "TF=%d;", getTemperatureFirst());
            break;
          case TEMPERATURE_SECOND_READ:
            sprintf(XBeeResponseMsg, "TS=%d;", getTemperatureSecond());
            break;
          case HUMIDITY_FIRST_READ:
            sprintf(XBeeResponseMsg, "HF=%d;", getHumidityFirst());
            break;
          case HUMIDITY_SECOND_READ:
            sprintf(XBeeResponseMsg, "HS=%d;", getHumiditySecond());
            break;
          case GAS_SENSOR_READ:
            sprintf(XBeeResponseMsg, "G=%d;", getGas());
            break;
          case SOIL_HUMIDITY_SENSOR_READ:
            sprintf(XBeeResponseMsg, "SH=%d;", getSoilHumidity());
            break;
          case GRAYSCALE_SENSOR_READ:
            sprintf(XBeeResponseMsg, "GS=%d;", getGrayscale());
            break;
          default:
            sprintf(XBeeResponseMsg, "Bad command");
            break;
           
        }
       
        Serial1.println(XBeeResponseMsg);
      }
    }
     
  3. aleksjet

    aleksjet Нуб

    у меня к вам вопрос а зачем вам светодиоды к датчикам ?
     
  4. Vir

    Vir Гик

    Они служат индикаторами за пределами помещения. Не смотря на то, что вся информация есть на сервере, я могу войти в комнату, и увидеть, что светодиод не горит, или наоборот горит, и быстро среагировать. Для этого мне не придется, каждые 5 минут мониторить инфу на сервере.
     
  5. aleksjet

    aleksjet Нуб

    т.е. эти индикаторы вынесены за пределы теплицы? я вас правильно понял ?
     
  6. Vir

    Vir Гик

    Да, всё верно.
     
  7. aleksjet

    aleksjet Нуб

    получается у вас две ардуины ?
     
  8. Vir

    Vir Гик

    Нет, одна. Светодиоды вынесены на проводах (будут). Пока, что всё это на макетной плате.
     
  9. aleksjet

    aleksjet Нуб

    а функции этой теплицы ?
    и где она будет стоять ?
     
  10. Vir

    Vir Гик

    Выращивать адениумы, и может, что-нибудь вроде помидор и салата. Стоять будет на балконе.
     
  11. aleksjet

    aleksjet Нуб

    понятно
    просто у меня аналогичные функции только для террариума
    правда без сервера и на ардцине uno
     
  12. Vir

    Vir Гик

    И так, что мы имеем на сегодняшний день.

    Я купил два модуля XBee, один подключил к компьютеру, другой подключил к arduino. Тот, который подключен к компьютеру прошит, как координатор. А тот, что на ардуине, прошит как роутер/конечное устройство.

    Вот, как теперь выглядит мой скетч:
    Код (Text):
    #include <dht.h> // Библиотека для сенсора температуры и влажности
     
    #define DEBUG 1 // Режим отладки
    #define D_DELAY 1000 // Задержка режима отладки
     
    #define DHT_SENSOR 52 // Цифровой вход для датчика температуры и влажности
    #define DT_LED 8 // Аналоговый выход светодиода для датчика температуры
    #define DH_LED 9 // Аналоговый выход светодиода для датчика влажности
    DHT dht = DHT(); // Инициализация библиотеки для работы с датчиком температуры и влажности
    int temperature = 0;
    int humidity = 0;
     
    #define GS_SENSOR 11 // Аналоговый вход для датчика освещенности
    #define GS_LED 10 // Аналоговый выход светодиода для датчика освещенности
    int grayscale = 0;
     
    #define TEMPERATURE_READ 1
    #define HUMIDITY_READ 2
    #define GRAYSCALE_READ 3
     
    void setup()
    {
      xbeeInit();
      temperatureAndHumiditySensorInit();
      grayscaleSensorInit();
     
     
      if (DEBUG)
      {
        Serial.begin(9600);
      }
    }
     
    void loop()
    {
      xbeeListenCommand();
      temperatureAndHumidityHandler();
      grayscaleHandler();
     
      if (DEBUG)
      {
        delay(D_DELAY);
      }
    }
     
    // Инициализация XBee
    void xbeeInit()
    {
      Serial1.begin(9600);
     
      /**
          Схема подключения:
         
          Левая нога 1 (сверху): источник питания 3.3V
          Левая нога 2 (сверху): RX1 (19-й pin)
          Левая нога 3 (сверху): TX1 (18-й pin)
          Левая нога 1 (снизу): земля (минус)
      */
    }
     
    void xbeeListenCommand()
    {
      if (Serial1.available() > 0)
      {
        int incoming = Serial1.read() - '0';
     
        switch (incoming)
        {
          case TEMPERATURE_READ:
            Serial1.println(getTemperature());
            break;
          case HUMIDITY_READ:
            Serial1.println(getHumidity());
            break;
          case GRAYSCALE_READ:
            Serial1.println(getGrayscale());
            break;
          default:
            Serial1.println("Bad command");
            break;
        }
      }
    }
     
    // Инициализация датчика температуры и влажности
    void temperatureAndHumiditySensorInit()
    {
      dht.attach(DHT_SENSOR);
      pinMode(DT_LED, OUTPUT);
     
      /**
          Схема подключения:
         
          Датчик:
            Красный - земля (минус)
            Черный  - источник питания 5V (плюс)
            Синий  - сигнал на цифровой вход
           
          Светодиоды
          :
            Короткая нога -> резистор (220 Ом) -> Земля (минус)
            Длинная нога  -> Аналоговый ШИМ выход
     
      */
    }
     
    int getTemperature()
    {
      return temperature;
    }
     
    int getHumidity()
    {
      return humidity;
    }
     
    // Обработчик датчика температуры и влажности
    void temperatureAndHumidityHandler()
    {
      dht.update();
     
      if (dht.getLastError() == DHT_ERROR_OK)
      {
        // Обработка температуры
        temperature = constrain(dht.getTemperatureInt(), 0, 40);
        int tLedPower  = 0;
       
        if (temperature > 22 && temperature < 32)
        {
          tLedPower = map(temperature, 22, 32, 30, 60);
        }
        else if (temperature < 22)
        {
          tLedPower = 2;
        }
        else
        {
          tLedPower = 255;
        }
     
        analogWrite(DT_LED, tLedPower);
       
        // Обработка влажности
        humidity  = constrain(dht.getHumidityInt(), 0, 100);
        int hLedPower = map(humidity, 0, 100, 0, 255);
       
        analogWrite(DH_LED, hLedPower);
       
        if (DEBUG)
        {
          char dbgMsg[128];
          sprintf(dbgMsg, "Temperature = %dC (DT_LED: %d), humidity = %d%% (DH_LED: %d)\t",
                  temperature, tLedPower, humidity, hLedPower);
          Serial.println(dbgMsg);
        }
      }
      else
      {
        switch (dht.getLastError())
        {
            case DHT_ERROR_START_FAILED_1:
                Serial.println("Error: start failed (stage 1)");
                break;
            case DHT_ERROR_START_FAILED_2:
                Serial.println("Error: start failed (stage 2)");
                break;
            case DHT_ERROR_READ_TIMEOUT:
                Serial.println("Error: read timeout");
                break;
            case DHT_ERROR_CHECKSUM_FAILURE:
                Serial.println("Error: checksum error");
                break;
        }
      }
    }
     
    // Инициализация датчика освещенности
    void grayscaleSensorInit()
    {
      pinMode(GS_LED, OUTPUT);
      pinMode(GS_SENSOR, INPUT);
     
      /**
          Схема подключения:
         
          Датчик:
            Красный - сигнал на аналоговый вход
            Черный  - земля (минус)
            Синий  - источник питания 5V (плюс)
           
          Светодиод:
            Короткая нога -> резистор (220 Ом) -> Земля (минус)
            Длинная нога  -> Аналоговый ШИМ выход
     
      */
    }
     
    int getGrayscale()
    {
      return grayscale;
    }
     
    // Обработчик датчика освещенности
    void grayscaleHandler()
    {
      grayscale = analogRead(GS_SENSOR);
      int gssVal = constrain(grayscale, 535, 580);
     
      int ledPower = map(gssVal, 580, 535, 0, 255);
      analogWrite(GS_LED, ledPower);
     
      if (DEBUG)
      {
        char dbgMsg[128];
        sprintf(dbgMsg, "Grayscale = %d (GS_LED: = %d)t",
                gssVal, ledPower);
        Serial.println(dbgMsg);  
      }
    }
    И вот библиотека на PHP, которую я написал, чтобы считывать информацию с датчиков по запросу (по факту, её использование будет запускаться по крону и считывать данные со всех датчиков по очереди):
    PHP:
    <?php
     
    namespace Arduino\DefaultBundle\Services\Arduino;
     
    class ArduinoLibrary
    {
        const TEMPERATURE_SENSOR = 1;
        const HUMIDITY_SENSOR   = 2;
        const GRAYSCALE_SENSOR  = 3;
       
        private $port;
       
        /**
        * @param string $port
        */

        public function __construct($port)
        {
            if (empty($port))
            {
                throw new \InvalidArgumentException('Empty parameter $port');
            }
           
            $this->port = $port;
        }
       
        public function read($sensor)
        {
            $portHandle = fopen($this->port, 'r+');
           
            if (!$portHandle)
            {
                return false;
            }
           
            $result = '';
            $writed = false;
           
            while (true)
            {
                if (!$writed)
                {
                    fwrite($portHandle, $sensor);
                    $writed = true;
                }
               
                $line = fread($portHandle, 1);
                $result .= $line;
               
                if ($line == chr(0xA))
                {
                    break;
                }
            }
           
            fclose($portHandle);
            return $result;
        }
    }
     
  13. Vir

    Vir Гик

    Теперь из консоли вызывается скрипт, который получает данные по сенсору и кладет их в базу данных. Как раз этот скрипт и будет запускаться по крону.
    Код (Text):
    vir@laptop:~/Work/WWW/chehome.local$ sudo app/console arduino:load temperature
    Done. Saved "22" value by sensor (temperature)
     
     
  14. Vir

    Vir Гик

  15. Vir

    Vir Гик

    Пока отказываюсь от датчика дыма. Потому, что то, что у меня есть - датчик газа, а не дыма =(
     
  16. Vir

    Vir Гик

    Подключил экран и написал для него ПО. Дело движется =)

    [​IMG]
     
  17. Vir

    Vir Гик

  18. Unixon

    Unixon Оракул Модератор

    А в качестве датчика дыма вам открытая оптопара не подойдет?
     
  19. Vir

    Vir Гик

    А я уже справился с датчиком газа =)
     
  20. Vir

    Vir Гик

    Подключил датчик прикосновения. Теперь можно будет управлять вручную работой лампы и вентилятора. Всего четыре сенсорные кнопки:
    1. Держать устройство включенным постоянно;
    2. Держать постоянно выключенным;
    3. Отдать управление устройством ардуине;
    4. Сменить устройство, над которым производится управление;
    Что самое характерно, что после переключения с управления одним устройство на другое, состояние предыдущего сохраняется, и когда мы вновь возвращаемся к управлению устройством с которого ушли, то его отображается то его состояние, в котором он был, когда мы с него переключались. Подробнее на видео.



    Ну, и для пущей радости код:
    PHP:
    #include <Wire.h>

    // Распиновка для LED-лампочки (индикатора)
    #define TP_LED_BLUE 10
    #define TP_LED_RED 11
    #define TP_LED_GREEN 12

    // Описание сигналов с панели (touch panel actions)
    #define TPA_TURN_ON 1           // Включить (сигнал с первой панели)
    #define TPA_TURN_OFF 2        // Отключить (сигнал со второй панели)
    #define TPA_ARDUINO_CONTROL 4   // Контролировать с помощью Arduino (сигнал с третей панели)
    #define TPA_CHANGE_COTROL_MODE 8 // Смена управляемого прибора (сигнал с четвертой панели)

    // Описание режимов управления
    #define TP_LAMP_CONTROL_MODE 1  // Режим управления лампой
    #define TP_FAN_CONTROL_MODE 2   // Режим управления вентилятором

    int  TPLastPressedPanel = TPA_ARDUINO_CONTROL; // Последняя нажатая панель (оно же последнее действие)
    int  TPLastLampPressed  = TPA_ARDUINO_CONTROL; // Последняя нажатая панель в режиме управления лампой
    int  TPLastFanPressed  = TPA_ARDUINO_CONTROL; // Последняя нажатая панель в режиме управления вентилятором
    long TPLastPressedTime;                     // Время последнего нажатия
    int  TPControlMode = TP_LAMP_CONTROL_MODE;  // Активный режим управления (по-умолчанию управление лампой)
     
    // Инициализация тач-панели
    void TouchPanelInit()
    {
      pinMode(TP_LED_BLUE, OUTPUT);
      pinMode(TP_LED_RED, OUTPUT);
      pinMode(TP_LED_GREEN, OUTPUT);
     
      Wire.begin();
      TPLastPressedTime = millis();
    }
     
    // Обработка тач-панели
    void TouchPanelHandler()
    {
      TPPressHandler();
      TPLedHandler();
    }
     
    // Обработка нажатий
    void TPPressHandler()
    {
      int pressed;
     
      Wire.beginTransmission(0x5A);
      Wire.requestFrom(0x5A, 1);
     
      while(Wire.available())
      {
        pressed = Wire.read();
      }
     
      // Если считывается нажатие с панели
      if (pressed != 0)
      {
        // Если можно обрабатывать нажатие
        if (TPCanPress(pressed))
        {
          TPSaveLastPressedPanel(pressed);
          TPExecutePressedAction(pressed);
          TPLastPressedTime = millis();
        }
      }
     
      Wire.endTransmission();
    }
     
    // Выполнение действия в соответствии с нажатием
    void TPExecutePressedAction(int pressed)
    {
      switch (pressed)
      {
        case TPA_CHANGE_COTROL_MODE:
          TPSwitchControlMode();
          break;
        case TPA_TURN_ON:
          TPSendTurnOnSignal();
          break;
        case TPA_TURN_OFF:
          TPSendTurnOffSignal();
          break;
        case TPA_ARDUINO_CONTROL:
          TPSendArduinoControlSignal();
          break;
        default:
          // нажато что-то, что не подлежит обработке
          break;
      }
    }
     
    // Отработка индикации (каким цветом светить)
    void TPLedHandler()
    {
      switch (TPControlMode)
      {
        case TP_LAMP_CONTROL_MODE:
          switch (TPLastLampPressed)
          {
            case TPA_TURN_ON:
              analogWrite(TP_LED_BLUE, 0);
              analogWrite(TP_LED_RED, 255);
              analogWrite(TP_LED_GREEN, 100);
              break;
            case TPA_TURN_OFF:
              analogWrite(TP_LED_BLUE, 100);
              analogWrite(TP_LED_RED, 255);
              analogWrite(TP_LED_GREEN, 0);
              break;
            case TPA_ARDUINO_CONTROL:
              analogWrite(TP_LED_BLUE, 0);
              analogWrite(TP_LED_RED, 255);
              analogWrite(TP_LED_GREEN, 0);
              break;
          }
     
          break;
        case TP_FAN_CONTROL_MODE:
          switch (TPLastFanPressed)
          {
            case TPA_TURN_ON:
              analogWrite(TP_LED_BLUE, 100);
              analogWrite(TP_LED_RED, 0);
              analogWrite(TP_LED_GREEN, 255);
              break;
            case TPA_TURN_OFF:
              analogWrite(TP_LED_BLUE, 255);
              analogWrite(TP_LED_RED, 255);
              analogWrite(TP_LED_GREEN, 255);
              break;
            case TPA_ARDUINO_CONTROL:
              analogWrite(TP_LED_BLUE, 0);
              analogWrite(TP_LED_RED, 0);
              analogWrite(TP_LED_GREEN, 255);
              break;
          }
      }
    }
     
    // Проверка возможности обработки нажатия
    boolean TPCanPress(int pressed)
    {
      boolean result = false;
     
      // Если предыдущая нажатая панель не совпадает с нынешней
      if (TPLastPressedPanel != pressed)
      {
        result = true;
      }
      // Если совпадает
      else
      {
        // Если предыдущее нажатие было сделано более чем 2 секунды назад
        if ((millis() - TPLastPressedTime) >= 2000)
        {
          result = true;
        }
      }
     
      return result;
    }
     
    // Переключение режимов управления и отработка последнего
    // зафиксированного действия для нынешнего режима
    void TPSwitchControlMode()
    {
      if (TPControlMode == TP_LAMP_CONTROL_MODE)
      {
        TPControlMode = TP_FAN_CONTROL_MODE;
        TPExecutePressedAction(TPLastFanPressed);
      }
      else
      {
        TPControlMode = TP_LAMP_CONTROL_MODE;
        TPExecutePressedAction(TPLastFanPressed);
      }
    }
     
    // Сохранение последнего нажатия
    void TPSaveLastPressedPanel(int pressed)
    {
      TPLastPressedPanel = pressed;
     
      if (pressed != TPA_CHANGE_COTROL_MODE)
      {
        switch (TPControlMode)
        {
          case TP_LAMP_CONTROL_MODE:
            TPLastLampPressed = pressed;
            break;
          case TP_FAN_CONTROL_MODE:
            TPLastFanPressed = pressed;
            break;
        }
      }
    }
     
    // Отправка сигнала включения
    void TPSendTurnOnSignal()
    {
        switch (TPControlMode)
        {
          case TP_LAMP_CONTROL_MODE:
            break;
          case TP_FAN_CONTROL_MODE:
            break;
        }
    }
     
    // Отправка сигнала выключения
    void TPSendTurnOffSignal()
    {
        switch (TPControlMode)
        {
          case TP_LAMP_CONTROL_MODE:
            break;
          case TP_FAN_CONTROL_MODE:
            break;
        }
    }
     
    // Отправка сигнала о том, что обрабатывать устройство должна ардуина
    void TPSendArduinoControlSignal()
    {
        switch (TPControlMode)
        {
          case TP_LAMP_CONTROL_MODE:
            break;
          case TP_FAN_CONTROL_MODE:
            break;
        }
    }
     
    Роман 77 нравится это.