Не дай себе засохнуть (продолжение) … или о том, как программист создавал первое в своей жизни устройство на базе Arduino


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

Часть 4. Либретто.

Начнём исследовать код от простого к сложному.
Для начала рассмотрим работу «внешнего» модуля.

Как я уже говорил, «внешнему» модулю отводится работа по получению и обработки команды от «домашнего» модуля. Каждая команда состоит из двух частей: её адреса(номера пина на Ардуино, который замыкает или размыкает определённое реле), и непосредственно команды на этот пин. На данный момент обрабатываются пока лишь три команды: «0» — выключить реле, «1» — включить реле, «2» — проверить состояние реле. Т.е. если «внешний модуль получил команду например «81» — это означает то, что мы надлежаще просим подать ток на пин номер 8 контролера Ардуино, что собственно и делаем после анализа поступившей команды.

Теперь привожу непосредственно код:

/*

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 version 2 as published by the Free Software Foundation.
 */
 /*
  Hack.lenotta.com
  Modified code of Getting Started RF24 Library
  It will switch a relay on if receive a message with text 1, 
  turn it off otherwise.
  Edo
 */

#include 

Как вы уже заметили из комментариев, за основу, я взял код, уже разрекламированного мною HACK.LENOTTA(), не знаю, кто он такой, но он мне уже явно должен, за рекламу. Надеюсь, он уже знает куда заносить денюжку, ну а мы продолжим. Собственно говоря, ни чего особенного и сложного для понимания в коде нет. Этот код явно присутствует в примерах к библиотеке "RF24.h", хотя, некоторые особенности в нём всё же присутствуют:

void performAction(unsigned long rawMessage){
unsigned short action, id, length, callback;
char* castedMessage;

  length = getLength(rawMessage);
  castedMessage = convertNumberIntoArray(rawMessage, length);
  action = getMessage(castedMessage);
  id = getId(castedMessage, length);

  if (action == 0 || action == 1){
      callback = action;
      doAction(id, action);
  }else if(action == 2){
      callback = getState(id);
  }

  sendCallback(callback);
}

Эта функция - анализатор поступающих в модуль команд. Сначала, она вычленяет из полученной строки команду - "action", затем адрес - "id" и после этого выполняет необходимое действие вызывая функции "doAction" или "getState" соответственно, а выполнив которые, отсылает обратно "домашнему" модулю ответ. Если, у вас возникнет желание расширить функциональность этого модуля, то, вам, всего лишь, стоит добавить сюда свой код, не занимаясь видоизменением всей программы. Вот так вот, элегантно и красиво.

Пришлось немного попотеть с подключением библиотеки "printf.h", но, порывшись на просторах интернета, легко нашёлся исходный код этой библиотеки, дальнейшее дело рук и Copy/Paste и вуаля, компилятор с восторгом прожевал ссылку на эту библиотеку. Если кого интересует, я с удовольствием выложу её, если уважаемый администратор поможет мне это сделать.

Вот, в общем то и всё о "внешнем" модуле, на возникающие вопросы, я, с удовольствием, отвечу в комментариях к этому топику.

Теперь, поговорим о "домашнем" модуле и подробно рассмотрим все, возложенные на него, функции. Тут, уж, уважаемый читатель, пришлось основательно попотеть. И так, вот, собственно и сам код:

#include  0) {
    
    // Read the incoming character
    char incoming_char = Serial.read();
    
    // End of line?
    if(incoming_char == '\n') {
      // Parse the command
      ParseIncomingComand();
    }
    // Carriage return, do nothing
    else if(incoming_char == '\r');
    // Normal character
    else {
      // Buffer full, we need to reset it
      if(buffer_position == BUFFER_SIZE - 1) buffer_position = 0;

      // Store the character in the buffer and move the index
      serial_buffer[buffer_position] = incoming_char;
      buffer_position++;      
    }
  }
  else{
    DateTime now = RTC.now();
    
    if (bAlarmWasSet == true){
    
      if((now.hour() == atNextAlarm.aValue.AlarmHour) && (now.minute() == atNextAlarm.aValue.AlarmMinute)){
        int iMinutesRemain = atNextAlarm.aValue.AlarmLen * 5 + now.hour() * 60 + now.minute();

        if(atNextAlarm.aValue.AlarmDevice1 == 1){
          SendNRFCommand((unsigned short)11);
          if(uiDeviceStatus[0] < iMinutesRemain) uiDeviceStatus[0] = iMinutesRemain;
        }
        if(atNextAlarm.aValue.AlarmDevice2 == 1){
          SendNRFCommand((unsigned short)21);
          if(uiDeviceStatus[1] < iMinutesRemain) uiDeviceStatus[1] = iMinutesRemain;
        }
        if(atNextAlarm.aValue.AlarmDevice3 == 1){
          SendNRFCommand((unsigned short)31);
          if(uiDeviceStatus[2] < iMinutesRemain) uiDeviceStatus[2] = iMinutesRemain;
        }
        if(atNextAlarm.aValue.AlarmDevice4 == 1){
          SendNRFCommand((unsigned short)41);
          if(uiDeviceStatus[3] < iMinutesRemain) uiDeviceStatus[3] = iMinutesRemain;
        }
        if(atNextAlarm.aValue.AlarmDevice5 == 1){
          SendNRFCommand((unsigned short)51);
          if(uiDeviceStatus[4] < iMinutesRemain) uiDeviceStatus[4] = iMinutesRemain;
        }
        if(atNextAlarm.aValue.AlarmDevice6 == 1){
          SendNRFCommand((unsigned short)61);
          if(uiDeviceStatus[5] < iMinutesRemain) uiDeviceStatus[5] = iMinutesRemain;
        }
        if(atNextAlarm.aValue.AlarmDevice7 == 1){
          SendNRFCommand((unsigned short)71);
          if(uiDeviceStatus[6] < iMinutesRemain) uiDeviceStatus[6] = iMinutesRemain;
        }
        if(atNextAlarm.aValue.AlarmDevice8 == 1){
          SendNRFCommand((unsigned short)81);
          if(uiDeviceStatus[7] < iMinutesRemain) uiDeviceStatus[7] = iMinutesRemain;
        }
        
        bAlarmWasSet = GetNextAlarm();
      }
    }
    else if(now.dayOfWeek()!= iAlarmDayOfWeek){
        bAlarmWasSet = GetNextAlarm();
        iAlarmDayOfWeek = now.dayOfWeek();
    }
    
    if(IsAllDevicesOff() == false){
        int iMinutesNow = now.hour() * 60 + now.minute();

        for(int i = 0; i < NUMBER_DEVICE; i++){
          if((uiDeviceStatus[i] > 0) && ((uiDeviceStatus[i] <= iMinutesNow) || ((uiDeviceStatus[i] >= MINUTES_IN_DAY) && ((uiDeviceStatus[i] - MINUTES_IN_DAY) <= iMinutesNow)))){
            SendNRFCommand((unsigned short)(i + 1) * 10);
            uiDeviceStatus[i] = 0;
          }
        }
    }
  }
//    delay(1000);    
}

bool IsAllDevicesOff(){
bool bRet = true;

  for(int i = 0; i < NUMBER_DEVICE; i++){
    if(uiDeviceStatus[i] > 0){
      bRet = false;
      break;
    }
  }
  
  return bRet;
}

void ParseIncomingComand(){
AlarmType at;
String strRet = "Error: ";

      switch(serial_buffer[0]){
        case '#':                                                // '##' - Test conektion
          if(serial_buffer[1] == '#') strRet = "!!";
          break;
        case '?':{                                               // '?' Get information
            switch(serial_buffer[1]){
              case 'V':                                          // '?V' Get Number Version
                strRet = VERSION;
                break;
              case 'T':{                                         // '?T' Get RTC Time
                  DateTime now = RTC.now();
                  char chStr[20];
                  sprintf(chStr, "%02d.%02d.%d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
                  strRet = chStr;
                }
                break;
                case 'W':{                                       // '?Woo' Send command to nRF24 module get status remote pin.
                  String message_string = String(serial_buffer);
                  
                  unsigned short id = message_string.substring(2, 4).toInt();
                  unsigned short message = id * 10 + 2;
                  
                  strRet = (SendNRFCommand(message) == 1) ? "On" : "Off";
                }
                break;
              case 'A':{                                         // '?A  ' Get Alarm data
                  if(serial_buffer[2] == 'N'){                   // '?AN ' Get Alarm Number
                      byte value = EEPROM.read(0);
                      char chStr[2];
                      sprintf(chStr, "%02d", value);
                      strRet = chStr;
                  }
                  else if(serial_buffer[2] == 'C') {            // '?AC ' Get Nearest Alarm value
                    if(bAlarmWasSet == true){
                      char chStr[9];
                      sprintf(chStr, "%02d:%02d %03d", atNextAlarm.aValue.AlarmHour, atNextAlarm.aValue.AlarmMinute, atNextAlarm.aValue.AlarmLen * 5);
                      strRet = chStr;
                     
                      if((atNextAlarm.bValue[2] == 0) || (at.bValue[2] == 0x80))
                        strRet += " Nothing";
                      else{
                        if(atNextAlarm.aValue.AlarmMonday == 1) strRet += " Mon.";
                        if(atNextAlarm.aValue.AlarmTuesday == 1) strRet += " Tue.";
                        if(atNextAlarm.aValue.AlarmWednesday == 1) strRet += " Wed.";
                        if(atNextAlarm.aValue.AlarmThursday == 1) strRet += " Thu.";
                        if(atNextAlarm.aValue.AlarmFriday == 1) strRet += " Fri.";
                        if(atNextAlarm.aValue.AlarmSaturday == 1) strRet += " Sat.";
                        if(atNextAlarm.aValue.AlarmSunday == 1) strRet += " Sun.";
                      }  
                      
                      if(atNextAlarm.bValue[3] == 0)
                        strRet += "No Devices";
                      else{
                        if(atNextAlarm.aValue.AlarmDevice1 == 1) strRet += " Dev1.";
                        if(atNextAlarm.aValue.AlarmDevice2 == 1) strRet += " Dev2.";
                        if(atNextAlarm.aValue.AlarmDevice3 == 1) strRet += " Dev3.";
                        if(atNextAlarm.aValue.AlarmDevice4 == 1) strRet += " Dev4.";
                        if(atNextAlarm.aValue.AlarmDevice5 == 1) strRet += " Dev5.";
                        if(atNextAlarm.aValue.AlarmDevice6 == 1) strRet += " Dev6.";
                        if(atNextAlarm.aValue.AlarmDevice7 == 1) strRet += " Dev7.";
                        if(atNextAlarm.aValue.AlarmDevice8 == 1) strRet += " Dev8.";
                      }
                      
                      strRet += (atNextAlarm.aValue.AlarmActive == 1) ? " Active" : " No Active";
                    }
                    else
                      strRet = " Not Active";
                  }
                  else{                                          // '?Aoo' Get Alarm data value
                      int address = atoi((serial_buffer + 2));
                      
                      at.bValue[3] = EEPROM.read(1 + address * ALARM_DATA_LENGHT);
                      at.bValue[2] = EEPROM.read(1 + address * ALARM_DATA_LENGHT + 1);
                      at.bValue[1] = EEPROM.read(1 + address * ALARM_DATA_LENGHT + 2);
                      at.bValue[0] = EEPROM.read(1 + address * ALARM_DATA_LENGHT + 3);
                      
                      char chStr[9];
                      sprintf(chStr, "%02d:%02d %03d", at.aValue.AlarmHour, at.aValue.AlarmMinute, at.aValue.AlarmLen * 5);
                      strRet = chStr;
                     
                      if((at.bValue[2] == 0) || (at.bValue[2] == 0x80))
                        strRet += " Nothing";
                      else{
                        if(at.aValue.AlarmMonday == 1) strRet += " Mon.";
                        if(at.aValue.AlarmTuesday == 1) strRet += " Tue.";
                        if(at.aValue.AlarmWednesday == 1) strRet += " Wed.";
                        if(at.aValue.AlarmThursday == 1) strRet += " Thu.";
                        if(at.aValue.AlarmFriday == 1) strRet += " Fri.";
                        if(at.aValue.AlarmSaturday == 1) strRet += " Sat.";
                        if(at.aValue.AlarmSunday == 1) strRet += " Sun.";
                      }  
                      
                      if(at.bValue[3] == 0)
                        strRet += "No Devices";
                      else{
                        if(at.aValue.AlarmDevice1 == 1) strRet += " Dev1.";
                        if(at.aValue.AlarmDevice2 == 1) strRet += " Dev2.";
                        if(at.aValue.AlarmDevice3 == 1) strRet += " Dev3.";
                        if(at.aValue.AlarmDevice4 == 1) strRet += " Dev4.";
                        if(at.aValue.AlarmDevice5 == 1) strRet += " Dev5.";
                        if(at.aValue.AlarmDevice6 == 1) strRet += " Dev6.";
                        if(at.aValue.AlarmDevice7 == 1) strRet += " Dev7.";
                        if(at.aValue.AlarmDevice8 == 1) strRet += " Dev8.";
                      }
                      
                      strRet += (at.aValue.AlarmActive == 1) ? " Active" : " No Active";
                  }
                }
                break;
            }
          }
          break;
        case '!':{                                             // '! ' Set data
            switch(serial_buffer[1]){
              case 'W':{                                       // '!Woo' Send command to nRF24 module
                String message_string = String(serial_buffer);
                
                unsigned short id = message_string.substring(2, 4).toInt();
                unsigned short message = id * 10;
                
                if (message_string.indexOf("On", 4) >= 0) message += 1;
                
                if(SendNRFCommand(message) != 3)
                  strRet = "OK";
                else
                  strRet+= "4";
              }
              break;
              case 'T':{                                       // '!T' Set Current Time
                String time_string = String(serial_buffer);
                int day = time_string.substring(2, 4).toInt();
                int month = time_string.substring(4, 6).toInt();
                int year = time_string.substring(6, 10).toInt();
                int hour = time_string.substring(10, 12).toInt();
                int minute = time_string.substring(12, 14).toInt();
                int second = time_string.substring(14, 16).toInt();
                DateTime set_time = DateTime(year, month, day, hour, minute, second);
                RTC.adjust(set_time);
                strRet = "OK";
              }
              break;
              case 'A':{                                       // '!A '  Set Alarm data
                String alarm_string = String(serial_buffer);
                int CurrentAlarm;
                byte AlarmNumber;
                
                switch(serial_buffer[2]){
                  case 'C':{                                   // '!AC'  Clear All Alarm List
                    EEPROM.write(0, 0);
                    alarmNumber = 0;
                    delay(100);
                    strRet = "OK";
                  }
                  break;
                  case 'D':{                                   // '!ADoo' Delete Alarm data
                    CurrentAlarm = alarm_string.substring(3, 5).toInt();
                    AlarmNumber = EEPROM.read(0);
                    AlarmNumber --;          
           
                    if(AlarmNumber >= CurrentAlarm){
                      for(int i = CurrentAlarm; i < AlarmNumber; i++){
                        delay(100);
                        EEPROM.write(1 + i * ALARM_DATA_LENGHT, EEPROM.read(1 + (i + 1) * ALARM_DATA_LENGHT));
                        delay(100);
                        EEPROM.write(1 + i * ALARM_DATA_LENGHT + 1, EEPROM.read(1 + (i + 1) * ALARM_DATA_LENGHT + 1));
                        delay(100);
                        EEPROM.write(1 + i * ALARM_DATA_LENGHT + 2, EEPROM.read(1 + (i + 1) * ALARM_DATA_LENGHT + 2));
                        delay(100);
                        EEPROM.write(1 + i * ALARM_DATA_LENGHT + 3, EEPROM.read(1 + (i + 1) * ALARM_DATA_LENGHT + 3));
                      }
                      
                      EEPROM.write(0, AlarmNumber);
                      
                      bAlarmWasSet = GetAlarmList();
                      
                      strRet = "OK";
                    }
                    else
                      strRet+= "3";
                  }
                  break;
                  case 'A':{                                   // '!AA'  Add Alarm data
                    CurrentAlarm = EEPROM.read(0);
                    AlarmNumber = CurrentAlarm + 1;
                  
                    if(AlarmNumber < MAX_ALARM_NUMBER){
                      EEPROM.write(0, AlarmNumber);
                      if (AddAlarmData(CurrentAlarm, alarm_string))
                        strRet = "OK";
                      else
                        strRet+= "2";
                    }              
                    else
                      strRet+= "1";
                  }
                  break;
                  default:{                                  // '!Aoo' Add Alarm data
                    CurrentAlarm = alarm_string.substring(2, 4).toInt();
                    
                    if (AddAlarmData(CurrentAlarm, alarm_string))
                      strRet = "OK";
                    else
                      strRet+= "2";
                  }
                  break;
              }
              break;
            }
          }
          break;
      }
      break;
    }      
    
    Serial.println(strRet);
      
    resetBuffer();
}

void resetBuffer(){
  // Reset the buffer
  for(int i = 0; i < BUFFER_SIZE; i++){
    serial_buffer[i] = 0;
  }
  
  buffer_position = 0;
}

bool AddAlarmData(int CurrentAlarm, String alarm_string){
bool bRet = false;
AlarmType at;

    if(CurrentAlarm < MAX_ALARM_NUMBER){
      at.bValue[0] = 0;
      at.bValue[1] = 0;
      at.bValue[2] = 0;
      at.bValue[3] = 0;
      
      at.aValue.AlarmHour = alarm_string.substring(4, 6).toInt();
      at.aValue.AlarmMinute = alarm_string.substring(6, 8).toInt();
      at.aValue.AlarmLen = (byte)(alarm_string.substring(8, 11).toInt() / 5);

      if(alarm_string.indexOf("Sun.", 10) >= 0) at.aValue.AlarmSunday = 1;
      if(alarm_string.indexOf("Mon.", 10) >= 0) at.aValue.AlarmMonday = 1;
      if(alarm_string.indexOf("Tue.", 10) >= 0) at.aValue.AlarmTuesday = 1;
      if(alarm_string.indexOf("Wed.", 10) >= 0) at.aValue.AlarmWednesday = 1;
      if(alarm_string.indexOf("Thu.", 10) >= 0) at.aValue.AlarmThursday = 1;
      if(alarm_string.indexOf("Fri.", 10) >= 0) at.aValue.AlarmFriday = 1;
      if(alarm_string.indexOf("Sat.", 10) >= 0) at.aValue.AlarmSaturday = 1;
      
      at.aValue.AlarmActive = (alarm_string.indexOf("No", 10) >= 0) ? 0 : 1;

      if(alarm_string.indexOf("Dev1.", 10) >= 0) at.aValue.AlarmDevice1 = 1;
      if(alarm_string.indexOf("Dev2.", 10) >= 0) at.aValue.AlarmDevice2 = 1;
      if(alarm_string.indexOf("Dev3.", 10) >= 0) at.aValue.AlarmDevice3 = 1;
      if(alarm_string.indexOf("Dev4.", 10) >= 0) at.aValue.AlarmDevice4 = 1;
      if(alarm_string.indexOf("Dev5.", 10) >= 0) at.aValue.AlarmDevice5 = 1;
      if(alarm_string.indexOf("Dev6.", 10) >= 0) at.aValue.AlarmDevice6 = 1;
      if(alarm_string.indexOf("Dev7.", 10) >= 0) at.aValue.AlarmDevice7 = 1;
      if(alarm_string.indexOf("Dev8.", 10) >= 0) at.aValue.AlarmDevice8 = 1;
      
      delay(100);
      EEPROM.write(1 + CurrentAlarm * ALARM_DATA_LENGHT, at.bValue[3]);
      delay(100);
      EEPROM.write(1 + CurrentAlarm * ALARM_DATA_LENGHT + 1, at.bValue[2]);
      delay(100);
      EEPROM.write(1 + CurrentAlarm * ALARM_DATA_LENGHT + 2, at.bValue[1]);
      delay(100);
      EEPROM.write(1 + CurrentAlarm * ALARM_DATA_LENGHT + 3, at.bValue[0]);
     
     bAlarmWasSet = GetAlarmList();
     bRet = true;
  }

  return bRet;
}

unsigned short SendNRFCommand(unsigned short message){
unsigned long got_message = 3;
bool bRet = false;

    // First, stop listening so we can talk.
    radio.stopListening();

    // Take the time, and send it.  This will block until complete
    bRet = radio.write( &message, sizeof(unsigned short) );
    
    // Now, continue listening
    radio.startListening();
    
    if (bRet == true){
      // Wait here until we get a response, or timeout (250ms)
      unsigned long started_waiting_at = millis();
      bool timeout = false;
      while (!radio.available() && !timeout )
        if (millis() - started_waiting_at > 250 )
          timeout = true;

      // Describe the results
      if ( timeout == false )
      {
        // Grab the response, compare, and send to debugging spew
        radio.read( &got_message, sizeof(unsigned long) );
      }
    }
    
    return got_message;
}

bool GetNextAlarm(){
int iMinutesNow;
int iDeltaAlarm;
int iMinDeltaAlarm;
bool bFound;
AlarmType atOldAlarm;

  iMinDeltaAlarm = -1;
  atNextAlarm.lValue = 0;

  if(alarmNumber > 0){
    DateTime now = RTC.now();
    iMinutesNow = now.hour() * 60 + now.minute();
    iAlarmDayOfWeek = now.dayOfWeek();
    atOldAlarm.lValue = atNextAlarm.lValue;

    for(int i = 0; i < alarmNumber; i++){
      if(atOldAlarm.lValue == alarmArray[i].lValue) continue;
      
      switch((eDayOfWeek)now.dayOfWeek()){
        case eSunday:
          bFound = (alarmArray[i].aValue.AlarmSunday == 1);
          break;
        case eMonday:
          bFound = (alarmArray[i].aValue.AlarmMonday == 1);
          break;
        case eTuesday:
          bFound = (alarmArray[i].aValue.AlarmTuesday == 1);
          break;
        case eWednesday:
          bFound = (alarmArray[i].aValue.AlarmWednesday == 1);
          break;
        case eThursday:
          bFound = (alarmArray[i].aValue.AlarmThursday == 1);
          break;
        case eFriday:
          bFound = (alarmArray[i].aValue.AlarmFriday == 1);
          break;
        case eSaturday:
          bFound = (alarmArray[i].aValue.AlarmSaturday == 1);
          break;
      }
      
      if(bFound == true){
        iDeltaAlarm = alarmArray[i].aValue.AlarmHour * 60 + alarmArray[i].aValue.AlarmMinute - iMinutesNow;
        
        if((iDeltaAlarm > 0) && ((iMinDeltaAlarm > iDeltaAlarm) || (iMinDeltaAlarm == -1))){
            iMinDeltaAlarm = iDeltaAlarm;
            atNextAlarm.lValue = alarmArray[i].lValue;
        }
      }
     }
  }
  
  return (iMinDeltaAlarm > 0);
}

bool GetAlarmList(){
AlarmType at;
unsigned short uNumAlarms = 0;

  uNumAlarms = EEPROM.read(0);
  alarmNumber = 0;

  if(uNumAlarms > 0){
    for(int address = 0; address < uNumAlarms; address++){
      at.bValue[3] = EEPROM.read(1 + address * ALARM_DATA_LENGHT);
      at.bValue[2] = EEPROM.read(1 + address * ALARM_DATA_LENGHT + 1);
      at.bValue[1] = EEPROM.read(1 + address * ALARM_DATA_LENGHT + 2);
      at.bValue[0] = EEPROM.read(1 + address * ALARM_DATA_LENGHT + 3);
      
      if((at.aValue.AlarmActive == 1) && (!((at.bValue[2] == 0) || (at.bValue[2] == 0x80))) && (at.bValue[3] != 0)){
        alarmArray[alarmNumber].lValue = at.lValue;
        alarmNumber ++; 
      }
    }
  }

  return GetNextAlarm();
}

Да, уж, большой код, хотя, и занимает всего лишь около 50% памяти Arduino Nano. Теперь обо всём по подробнее.
В "домашнем" модуле мы прибегаем к помощи:

- часов реального времени RTC;
- памяти EEPROM для запоминания времени начала полива;
- приёмо-передатчика nRF24L01, для общения с "внешним" модулем.
- последовательного порта, для общения с базовым компьютером.

Вся эта радость присутствует в этом коде и взаимодействует между собой.

А теперь ещё подробнее:

Работа с модулем реального времени заключается в получении на каждом такте циклической процедуры "loop()" текущего времени и дня недели, сравнением её со временем и днём недели ближайшего к нему установленного "будильника". Если время срабатывания "будильника" совпадает с текущим, то, из установок конкретного "будильника", берётся время его отключения и список устройств необходимых для включения на "внешнем" модуле. Соответственные команды включения посылаются на "внешний" модуль, а в массив состояний внешних устройств uiDeviceStatus записывается время отключения устройств, отмеченных в конкретном "будильнике", если предыдущее время отключения какого нибудь устройства, установленного предыдущим "будильником", не истекло, а в новом "будильнике", это устройство, опять же включается, то новое время отключения просто переписывается в массив.

EEPROM - память "будильников". "Будильник" своего рода единица информации, которая хранится в EEPROM контролера Ардуино, содержащая информацию о:

- продолжительности включения будильника;
- времени (часы, минуты) включения;
- дней недели включения;
- устройств, которые должны быть включены этим будильником.

Всё это элегантно уместилось у меня в следующую бинарную структуру:

struct ALARM_DATA {
    unsigned short AlarmLen        : 5;  // 00000000 00000000 00000000 000?????
    unsigned short AlarmMinute     : 6;  // 00000000 00000000 00000??? ???00000
    unsigned short AlarmHour       : 5;  // 00000000 00000000 ?????000 00000000
    unsigned short AlarmSunday     : 1;  // 00000000 0000000? 00000000 00000000
    unsigned short AlarmMonday     : 1;  // 00000000 000000?0 00000000 00000000
    unsigned short AlarmTuesday    : 1;  // 00000000 00000?00 00000000 00000000
    unsigned short AlarmWednesday  : 1;  // 00000000 0000?000 00000000 00000000
    unsigned short AlarmThursday   : 1;  // 00000000 000?0000 00000000 00000000
    unsigned short AlarmFriday     : 1;  // 00000000 00?00000 00000000 00000000
    unsigned short AlarmSaturday   : 1;  // 00000000 0?000000 00000000 00000000
    unsigned short AlarmActive     : 1;  // 00000000 ?0000000 00000000 00000000
    unsigned short AlarmDevice1    : 1;  // 0000000? 00000000 00000000 00000000
    unsigned short AlarmDevice2    : 1;  // 000000?0 00000000 00000000 00000000
    unsigned short AlarmDevice3    : 1;  // 00000?00 00000000 00000000 00000000
    unsigned short AlarmDevice4    : 1;  // 0000?000 00000000 00000000 00000000
    unsigned short AlarmDevice5    : 1;  // 000?0000 00000000 00000000 00000000
    unsigned short AlarmDevice6    : 1;  // 00?00000 00000000 00000000 00000000
    unsigned short AlarmDevice7    : 1;  // 0?000000 00000000 00000000 00000000
    unsigned short AlarmDevice8    : 1;  // ?0000000 00000000 00000000 00000000
};

Как вы заметили, что каждый "будильник" размером в 4-е байта, что позволяет нам в частности для Arduino Nano, записать около 100 с небольшим соответствующих "будильников" - это более чем нужно, но, возможно в будущем для системы потребуется более широкий функционал, как то опрос по времени разного рода сенсоров или передачи, по "будильнику" данных "на верх" (интернет или серверный компьютер), то такой запас, будет вполне оправдан.

По скольку "будильник" срабатывает не только по времени, но и по дню недели, как у iPhone, это добавляет дополнительную гибкость в настройке времени включения системы.

И ещё одна небольшая хитрость, для упрощения жизни, я по нулевому адресу EEPROM записываю количество сохранённых будильников. Всего один байт, а жить намного проще.

Ну, как бы, вкратце и всё про "будильник".

Рассмотрим теперь систему команд, созданную для управлением этим механизмом из вне, будь то сервер, интернет страница или ещё один модуль, отвечающий за отображение информации и управлением работой.

Команды управления "домашним" модулем:

  • ## Тест соединения, получив эту команду, модуль генерирует ответ !!
  • ?V Запрос на номер версии текущей программы зашитой в домашний модуль, на настоящее время это "3.0"
  • ?T Запрос на текущее время от модуля RTC.
  • ?W** Запрос о состоянии пинов, замыкающих реле, на внешнем модуле, вместо звездочек необходимо указать адрес пина - например "?W08" или "?W12". Ответ будет в текстовом виде "On" или "Off"
  • ?AN Запрос на количество будильников, записанных в EEPROM "домашнего" модуля.
  • ?AC Запрос на ближайший по срабатыванию на текущий день "будильник"
  • ?A** Запрос на информацию о конкретном, в соответствии с указанным номером вместо звёздочек, "будильнике"
  • !W** Установка состояния пина, замыкающих реле на "внешнем" модуле.
  • !Тddmmyyyyhhmmss Установка времени на модуле RTC.
  • !AC Очистка списка "будильников", записанных в EEPROM "домашнего" модуля.
  • !AD** Удаление "конкретного" будильника из списка.
  • !AA(alarm) Добавление нового "будильника" в конец списка.
  • !A**(alarm) Добавление нового "будильника" по конкретному адресу в списке.

Все эти команды можно посылать через терминал последовательного порта на "домашний" модуль, этим управляя системой.
Вот, пока и всё, надеюсь, что не сильно утомил вас, дорогие мои читатели.
Очень надеюсь на вопросы и коментарии, пишите.

Ссылка на исходники на ГитХабе


0 комментариев на «“Не дай себе засохнуть (продолжение) … или о том, как программист создавал первое в своей жизни устройство на базе Arduino”»

  1. Отличная работа!
    Правда, немножко смущает функция convertNumberIntoArray() — в ней происходит динамическое выделение памяти, которая потом нигде не освобождается.
    Обычно, для встраиваемых систем (у которых с памятью очень туго), стараются динамическую память не использовать.

    см. ATmega — использование памяти

    Если кого интересует, я с удовольствием выложу её

    библиотеку можно выложить на гитхаб, а здесь вставить на неё ссылку.

    • Вы абсолютно правы на счёт функции convertNumberIntoArray() постараюсь в ближайшее время внести в неё исправления, для упрощения её работы и экономии ресурсов. Очень рад за толковые советы, надеюсь, на дальнейшее сотрудничество.

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Android Arduino Bluetooth CraftDuino DIY IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение