Побеждаем GPRS модуль от Амперки

    image
    Не успели мы победить шину CAN, как пришлось побеждать очередную железку, а именно, GPRS модуль. Такова она жизнь разработчика — всё время приходится кого-нибудь побеждать (тут должен стоять запрещённый смайл).

    Для одного из заказных проектов мне понадобилось добавить возможность управления и получения телеметрии по GSM при помощи SMS. Посмотрел я на список доступных вариантов и остановился на GPRS Shield от Амперки. Почему нет? Прилично выглядит, выпускается известной компанией, имеет техподдержку, по цене не особо отличается от конкурентов и вообще производит очень приятное впечатление.

    Но не тут-то было. О том квесте и невероятных курсах повышения квалификации которые мне пришлось пройти, интегрируя этот GPRS модуль с Arduino Mega Server вы можете узнать, нажав на кнопочку ниже.

    Сам модуль


    Как я уже сказал, сам модуль производит очень приятное впечатление своей аккуратностью исполнения и родством с известной компанией. Опять же, на сайте производителя есть примеры использования и родная библиотека. Поскольку модуль брендовый, есть надежда на адекватную поддержку в случае возникновения каких-то проблем с ним.

    image

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

    Проект


    Пара слов о проекте «GSM курятник» для которого потребовался GPRS модуль. Понадобилось разработать автоматизацию для управления курятником. Воистину в удивительное время мы живём, нынче курятнику требуется управление через веб-браузер, беспроводные Smart-сенсоры и телеметрия по GSM. Прямо стратегический объект какой-то, а не курятник.

    image

    Но раз надо — значит надо. Покупаем комплектующие, среди которых GPRS модуль и приступаем.

    Ахиллесова пята


    Теперь об особенностях работы модуля. В принципе, он работает. Он работает с примерами, представленными на сайте производителя и на его основе можно даже создать какой-нибудь несложный скетч. Но есть три «но».

    Примечание. В проекте использовались две платы: Arduino Mega 2560 и Arduino Due и всё нижеизложенное относится именно к ним и только к ним.

    Первое «но», хардверное. Модуль не работает с Arduino Due. Никак. Не помогла даже многодневная переписка с техподдержкой Амперки. Заставить работать GPRS Shield с Arduino Due не удалось ни мне, ни специалистам компании. И это очень обидно потому, что Due это отличный контроллер с большими возможностями и очень хотелось бы иметь возможность использовать его с GPRS Shield.

    Второе «но», системное. Для работы модулю требуется библиотека SoftwareSerial. Когда я начинал своё общение с GPRS Shield и техподдержкой Амперки это было безальтернативным решением. После наших разбирательств была выпущена исправленная версия библиотеки с поддержкой работы по железному Serial, но… он так и не заработал ни на Due, ни на Mega. Что вообще труднообъяснимо — если модуль работает по SoftSerial, то что ему мешает работать по железному Serial?

    Третье «но», концептуальное. Это ещё не всё. главная засада заключается в принципе работы библиотеки модуля. Она работает в закрытом режиме, то есть блокирует работу контроллера на время ожидания прихода SMS (а это 99 % времени) и во время всех прочих операций модуля. Что это значит? Это значит, что на стандартной библиотеке от производителя вы не сможете построить ничего, кроме тестового скетча. Стратегический курятник вам не светит, как не светят ещё тысячи классных приложений вроде охранных систем, управления теплицами, управления умным домом по GSM и т. д. и т. п.

    Подробнее о SoftwareSerial


    На Arduino Due модуль не работает, поэтому будем говорить об Arduino Mega. Пикантность ситуации заключается в том, что имея на Mega три свободных хардверных порта Serial1, Serial2 и Seria3, мы вынуждены подключать и использовать библиотеку SoftwareSerial. Никакими мыслимыми и немыслимыми способами, включая перестановку джамперов на модуле и допрос с пристрастием техподдержки Амперки, не удалось заставить работать GPRS Shield с хардверными портами на Arduino Mega.

    Ладно… Используем SoftwareSerial, но и тут нас ожидает засада. SoftwareSerial может работать на многих пинах Arduino Mega, но с модулем работает почему-то только на 62-м и 63-м пинах. Почему только на них? Загадка сия велика есть. Ответа на этот вопрос мы, судя по всему, не узнаем никогда.

    Муки выбора


    И вот, как обычно, мы приходим к пониманию, что стандартная библиотека непригодна для практического использования. Блокируя контроллер на время посылки о ожидания ответа GPRS модуля, библиотека блокирует и все остальные процессы. Контроллер просто выпадает из реальности на 99 % времени, ожидая пока придёт запрос или ответ или не закончится таймаут.

    Это ставит крест на любом применении GPRS Shield, кроме тестового: послал запрос включить розетку — она включилась, послал запрос выключить — она выключилась. Ни о какой работе веб-сервера, работе с датчиками, управлении актуаторами и прочем не может быть и речи, ничего из этого просто не будет работать.

    Вопрос встал ребром — либо мы отказываемся от библиотеки производителя и пишем свой код управления GPRS Shield (легко сказать), либо отказываемся от самого модуля и ищем альтернативное решение. Но модуль то уже есть, поэтому было решено отказаться от библиотеки и написать свой код управления модулем.

    Покорение Эвереста


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

    Для вас же всё будет просто — немного магии и готовый рабочий код модуля AMS для поддержки GPRS Shield готов.

    image

    Полный код модуля AMS, заменяющий стандартную библиотеку
    /*
      Modul GPRS
      part of Arduino Mega Server project
    */
    
    #ifdef GPRS_FEATURE
    
    #include <SoftwareSerial.h>
    
    #define ALWAYS           1
    #define GPRS_ON_PIN      2
    #define GPRS_STATE_PIN   3
    #define GPRS_RX_PIN     62
    #define GPRS_TX_PIN     63
    #define MASTER          "+7yyyxxxxxxx"
    #define MESSAGE         "Hello"
    
    #define CHECK_ERROR      0
    #define CHECK_MARKER     1
    #define CHECK_OK         2
    #define NO_CHECK         3
    
    #define MARKER_OK        "OK"
    #define MARKER_NO_CHECK  "-"
    
    // GPRS commands
    #define DATA_TEMP1  "temp1"
    #define DATA_CONT1  "cont1"
    #define DATA_LEAK1  "leak1"
    #define DATA_SMOKE1 "smoke1"
    #define DATA_ALL    "all"
    #define DATA_RELAY1 "relay1"
    #define DATA_SERVO1 "servo1"
    #define DATA_PERIOD "period"
    
    #define CMD_RELAY1 "relay1="
    #define CMD_SERVO1 "servo1="
    #define CMD_PERIOD "period="
    
    byte gprsPeriod = 24;
    
    SoftwareSerial GPRS(GPRS_RX_PIN, GPRS_TX_PIN);
    
    #define MAX_SOFT_SERIAL 128
    char bufferGprs[MAX_SOFT_SERIAL];
    
    int curBuf = 0;
    
    #define MESSAGE_LENGTH  20
    char message[MESSAGE_LENGTH];
    char phone[16];
    char datetime[24];
    
    
    void gprsInit() {
      initStart("GPRS", true);
      GPRS.begin(9600);
      gprsOnOff();
      gprsStart();
      sendGprs("AT+CFUN=1", "OK");
      sendGprs("AT+CNMI=2,1", "OK");
      sendGprs("AT+CMGF=1", "OK");
      sendGprs("AT+CLIP=1", "OK");
      modulGprs = MODUL_ENABLE;
      initDone(true);
    }
    
    void clearBufferGprs() {
      for (int i = 0; i < curBuf; i++) {
        bufferGprs[i] = 0;
      }
    }
    
    void gprsStart() {
      while (ALWAYS) {
        delay(1000);
        curBuf = 0;
        GPRS.println("AT");
        if (GPRS.available() > 0) {
          while (GPRS.available() > 0) {
            bufferGprs[curBuf++] = GPRS.read();
          }
          bufferGprs[curBuf] = '\0';
          if (strcmp(bufferGprs, "AT\r\n\r\nOK\r\n") == 0) {
            timeStamp();
            Serial.println(" GPRS ON");
            break;
          } else {
              Serial.print(".");
            }
        }
        clearBufferGprs();
      }
    } // gprsStart()
    
    void gprsOnOff() {
      pinMode(GPRS_ON_PIN, OUTPUT);
      if (digitalRead(GPRS_STATE_PIN) != HIGH) {
        digitalWrite(GPRS_ON_PIN, HIGH);
        delay(3000);
      }
      digitalWrite(GPRS_ON_PIN, LOW);
    }
    
    bool clearSoftwareSerial() {
      if (GPRS.available() > 0) {
        while (GPRS.available() > 0) {
          char c = GPRS.read();
        }
      }
    }
    
    #define DELAY_GPRS_COMMAND 2000
    
    byte sendGprs(String command, char marker[]) {
      unsigned long timer = millis();
      bool success = false;
      
      clearSoftwareSerial();
      clearBufferGprs();
      Serial.print(F("Send command: ")); //Serial.println(command);
      
      while (ALWAYS) {
        if (millis() - timer > DELAY_GPRS_COMMAND) {
          Serial.print(F("Send error: ")); Serial.println(command);
          //clearBufferGprs();
          return CHECK_ERROR;
        }
        curBuf = 0;
        GPRS.println(command);
        delay(280);
        if (GPRS.available() > 0) {
          while (GPRS.available() > 0) {
            char c = GPRS.read();
            if (curBuf > MAX_SOFT_SERIAL - 2) {break;}
            bufferGprs[curBuf++] = c;
            Serial.print(c);
          }
          Serial.println();
          bufferGprs[curBuf] = '\0';
          if (marker == MARKER_NO_CHECK) {
            Serial.print(F("Send no check, ")); Serial.println(millis() - timer);
            return NO_CHECK;
          }
          else if (StrContains(bufferGprs, marker)) {
            Serial.print(F("Send success, ")); Serial.println(millis() - timer);
            return CHECK_MARKER;
          }
          else if (StrContains(bufferGprs, MARKER_OK)) {
            Serial.print(F("Send success, ")); Serial.println(millis() - timer);
            return CHECK_OK;
          } else {
              Serial.println(".");
            }
        } // if (GPRS.available() > 0)
        delay(500);
      } // while (ALWAYS)
    } // sendGprs( )
    
    // +CMGR: "REC READ", "XXXXXXXXXXX", "", "16/10/01,10:00:00+12"
    // SMS text
    
    void parseSms(char *message, char *phone, char *datetime) {
      int i = 0;
      int j = 0;
      while (bufferGprs[i] != '\"') {i++;} i++;
      while (bufferGprs[i] != '\"') {i++;} i++;
      while (bufferGprs[i] != '\"') {i++;} i++;
      while (bufferGprs[i] != '\"') {phone[j++] = bufferGprs[i++];} phone[j] = '\0'; i++;
      while (bufferGprs[i] != '\"') {i++;} i++;
      while (bufferGprs[i] != '\"') {i++;} i++;
      while (bufferGprs[i] != '\"') {i++;} i++;
      j = 0;
      while (bufferGprs[i] != '\"') {datetime[j++] = bufferGprs[i++];} datetime[j] = '\0'; i++;
      while (bufferGprs[i] != '\n') {i++;} i++;
      j = 0;
      //Serial.print(F("strlen(bufferGprs): ")); Serial.println(strlen(bufferGprs));
      while (i < strlen(bufferGprs) - 1) {
        if (j > MESSAGE_LENGTH - 1) {break;}
        if ((byte)bufferGprs[i] == 13) {break;}
        message[j++] = bufferGprs[i++];
      }
      Serial.print(F("strlen(message1): ")); Serial.println(strlen(message));
      message[j] = '\0';
      Serial.print(F("strlen(message2): ")); Serial.println(strlen(message));
    
      for (int z = 0; z < strlen(message); z++) {
        Serial.print((byte)message[z]);
        Serial.print(F(" "));
      }
      Serial.println();
    }
    
    void deleteSms() {
      if (sendGprs("AT+CMGDA=\"DEL ALL\"", "OK")) {
        Serial.println(F("All SMS deleted"));
      } else {
          Serial.println(F("Error delete all SMS"));
        }
    }
    
    String stringSens(byte v)  {
      String s = "";
      switch (v) {
        case 0:
          s = (F("OFF"));
          break;
        case 1:
          s = (F("ON"));
          break;
        default: 
          s = (F("?"));
      }
      return s;
    }
    
    String stringLeak(byte v)  {
      String s = "";
      switch (v) {
        case 0:
          s = (F("LEAK!"));
          break;
        case 1:
          s = (F("OK"));
          break;
        default: 
          s = (F("?"));
      }
      return s;
    }
    
    String stringSmoke(byte v)  {
      String s = "";
      switch (v) {
        case 0:
          s = (F("OK"));
          break;
        case 1:
          s = (F("SMOKE!"));
          break;
        default: 
          s = (F("?"));
      }
      return s;
    }
    
    String mkTemp1()  {String s = DATA_TEMP1;  s += '='; s += String(lpTempTemp);    s += '\n'; return s;}
    String mkCont1()  {String s = DATA_CONT1;  s += '='; s += stringSens(lpContCont1);   s += '\n'; return s;}
    String mkLeak1()  {String s = DATA_LEAK1;  s += '='; s += stringLeak(lpLeakLeak1);   s += '\n'; return s;}
    String mkSmoke1() {String s = DATA_SMOKE1; s += '='; s += stringSmoke(smokeSmoke);   s += '\n'; return s;}
    String mkRelay1() {String s = DATA_RELAY1; s += '='; s += stringSens(relayRelay);   s += '\n'; return s;}
    String mkServo1() {String s = DATA_SERVO1; s += '='; s += stringSens(servoState); s += '\n'; return s;}
    String mkPeriod() {String s = DATA_PERIOD; s += '='; s += String(gprsPeriod);   s += '\n'; return s;}
    
    String mkAll() {
      String s = "";
      s += mkTemp1();
      s += mkCont1();
      s += mkLeak1();
      s += mkSmoke1();
      s += mkRelay1();
      s += mkServo1();
      s += mkPeriod();
      return s;
    }
    
    void gprsSetRelay(byte v) {
      switch (v) {
        case 0:
          if (relayRelay) {
            relayRelay = STATE_OFF;
          }
          break;
        case 1:
          if (!relayRelay) {
            relayRelay = STATE_ON;
          }
          break;
    
      }
    }
    
    void gprsSetServo(byte v) {
      switch (v) {
        case 0:
          servoState = STATE_OFF;
          break;
        case 1:
          servoState = STATE_ON;
          break;
    
      }
    }
    
    void gprsAnswer() {
      String s = "";
      String mess = String(message);
      String data = "";
      if      (mess == DATA_TEMP1)  {s += mkTemp1();}
      else if (mess == DATA_CONT1)  {s += mkCont1();}
      else if (mess == DATA_LEAK1)  {s += mkLeak1();}
      else if (mess == DATA_SMOKE1) {s += mkSmoke1();}
      else if (mess == DATA_RELAY1) {s += mkRelay1();}
      else if (mess == DATA_SERVO1) {s += mkServo1();}
      else if (mess == DATA_PERIOD) {s += mkPeriod();}
      else if (mess == DATA_ALL)    {s += mkAll();}
      else if (mess.indexOf(F("=")) >= 0) {
        byte p = mess.indexOf(F("="));
        if      (mess.indexOf(CMD_RELAY1) >= 0) {data = mess.substring(p + 1); gprsSetRelay(data.toInt()); s += DATA_RELAY1; s += '='; s += stringSens(relayRelay);}
        else if (mess.indexOf(CMD_SERVO1) >= 0) {data = mess.substring(p + 1); gprsSetServo(data.toInt()); s += DATA_SERVO1; s += '='; s += stringSens(servoState);}
        else if (mess.indexOf(CMD_PERIOD) >= 0) {data = mess.substring(p + 1); gprsPeriod = data.toInt();  s += DATA_PERIOD; s += '='; s += String(gprsPeriod);}
      } else {
          Serial.println(F("Not command!"));
        }
    
      //Serial.print(F("mess: ")); Serial.println(mess);
      //Serial.print(F("answ: ")); Serial.println(s);
    
      if (s == "") {s = "Error";}
    
      Serial.println(F("Send answer... "));
      if (sendSms(MASTER, s)) {
        Serial.println(F("success"));
      } else {
          Serial.println(F("error"));
        }
    }
    
    void readSms() {
      byte result = sendGprs("AT+CMGR=1,1", "+CMGR:");
      if (result == CHECK_MARKER) {
        parseSms(message, phone, datetime);
        Serial.print(F("Number:   ")); Serial.println(phone);
        Serial.print(F("Datetime: ")); Serial.println(datetime);
        Serial.print(F("Message:  ")); Serial.println(message);
        deleteSms();
        if (String(phone) == String(MASTER)) {
          Serial.println(F("Message from MASTER!"));
          gprsAnswer();
        }
      }
      else if (result == CHECK_OK) {
        Serial.println(F("No SMS"));
      } else {
          Serial.println(F("Error read SMS"));
        }
    }
    
    bool sendSms(char *number, String data) {
      String numstr = "AT+CMGS=\"" + String(number) + "\"";
      String messtr = data + String((char)26);
      if (sendGprs(numstr, ">")) {
        if (sendGprs(messtr, MARKER_NO_CHECK)) {
          return true;
        }
      }
      return false;
    }
    
    void sendPeriod() {
      Serial.println(F("Send period SMS... "));
      if (sendSms(MASTER, mkAll())) {
        Serial.println(F("success"));
      } else {
          Serial.println(F("error"));
        }
    }
    
    void gprsWorks() {
      if (cycle20s) {
        readSms();
      }
    
      switch (gprsPeriod) {
        case 1:  if (cycle1h)  {sendPeriod();} break;
        case 6:  if (cycle6h)  {sendPeriod();} break;
        case 12: if (cycle12h) {sendPeriod();} break;
        default: if (cycle24h) {sendPeriod();} break;
      }
    }
    
    #endif // GPRS_FEATURE
    


    Модуль разрабатывался и тестировался на AMS версии 0.16 для Arduino Mega. Тестирование показало абсолютно стабильную и надёжную работу GPRS Shield под управлением этого модуля.

    Подключение к AMS модуля поддержки GPRS Shield


    Для того, чтобы подключить модуль GPRS Shield к Arduino Mega Server, нужно произвести несколько несложных действий. Сначала нужно добавить в главный файл AMS следующие строки

    #define GPRS_FEATURE
    

    и

    byte modulGprs = MODUL_NOT_COMPILLED;
    

    в соответствующие секции. Туда же нужно добавить тестовые переменные, которые в вашем реальном проекте будут отвечать за контролируемые параметры (температура, состояние контакта и т. д.).

    float lpTempTemp;
    byte lpContCont1;
    byte lpLeakLeak1;
    byte smokeSmoke;
    byte relayRelay;
    byte servoState;
    

    Пояснения по поддерживаемым функциям


    Модуль содержит базовый набор запросов и команд, но вы можете заменить эти команды любыми другими и/или расширить базовый набор командами и запросами нужными вам.

    Запросы


    Запросы посылаются посредством SMS, система присылает ответы (тоже по SMS), содержащие информацию о запрашиваемом параметре. Очень просто: посылаем с телефона запрос «temp1», система в ответ присылает текущее значение температуры temp1=20.50.

    temp1 — запрос температуры
    cont1 — запрос состояния контакта
    leak1 — запрос состояния датчика протечки
    smoke1 — запрос состояния датчика задымления
    relay1 — запрос состояния реле (ключа)
    servo1 — запрос состояния сервопривода кормушки
    period — запрос периода автоматических посылок телеметрии
    all — запрос всех параметров системы

    В ответ на запрос «all» система присылает список всех параметров. В случае посылки незарегистрированной команды, система пришлёт ответ «Error». Если контролируемые параметры выходят за пределы нормы, система может сама прислать тревожные сообщения на смартфон оператора.

    Для того, чтобы управлять системой мог только зарегистрированный оператор, в системе есть защита от несанкционированного доступа — она реагирует только на команды с определённого номера (номеров) телефона.

    Команды


    Команды посылаются посредством SMS, приняв команду, система меняет своё состояние и присылает ответы с информацией о новом состоянии своих актуаторов или настроек.

    relay1= — команда управления реле (ключом)
    servo1= — команда управления сервоприводом кормушки
    period= — команда изменения периода автопосылки телеметрии

    Пример. Команда relay1=1, ответ relay1=ON. Команда relay1=0, ответ relay1=OFF.

    Пояснения по работе кода


    Инициализация модуля GPRS. Стандартная инициализация модуля AMS, запуск SoftwareSerial, и немного магии GSM AT команд. Цель этого блока — привести в чувство модуль GPRS и вернуть его из нирваны, если он к этому моменту в неё ушёл по каким-то независящим от нас причинам.

    void gprsInit() {
      initStart("GPRS", true);
      GPRS.begin(9600);
      gprsOnOff();
      gprsStart();
      sendGprs("AT+CFUN=1", "OK");
      sendGprs("AT+CNMI=2,1", "OK");
      sendGprs("AT+CMGF=1", "OK");
      sendGprs("AT+CLIP=1", "OK");
      modulGprs = MODUL_ENABLE;
      initDone(true);
    }
    

    Главная рабочая функция модуля. Каждые 20 секунд Arduino Mega Server проверяет наличие пришедших SMS и, в случае прихода каких-либо запросов или команд, выполняет их. В результате средняя задержка выполнения команды составляет 10 секунд (без учёта задержки передачи SMS оператором связи). Этот интервал можно настраивать, 20 секунд выбраны как компромисс между скоростью реакции на SMS команды и загрузкой системы.

    Эти 20 секунд AMS может делать всё, что ему нужно, а приходящее в этот промежуток времени SMS не теряется. При использовании стандартной библиотеки система ничего не может делать кроме ожидания прихода SMS, иначе она их просто теряет (и контроллер в это время тоже ничего не может делать).

    Тут же присутствует код посылки телеметрии с заданным интервалом в 1 час, 6 часов, 12 часов или 24 часа. Этот интервал можно изменить путём посылки по SMS соответствующей команды.

    void gprsWorks() {
      if (cycle20s) {
        readSms();
      }
    
      switch (gprsPeriod) {
        case 1:  if (cycle1h)  {sendPeriod();} break;
        case 6:  if (cycle6h)  {sendPeriod();} break;
        case 12: if (cycle12h) {sendPeriod();} break;
        default: if (cycle24h) {sendPeriod();} break;
      }
    }
    

    Взаимодействие GPRS модуля и контроллера производится при помощи специальных AT команд и нижеследующие функции формируют команды GPRS модулю в понятных для него кодах.

    Функция посылки SMS. В параметрах функции передаётся телефонный номер назначения и сама команда или запрос в текстовом виде.

    bool sendSms(char *number, String data) {
      String numstr = "AT+CMGS=\"" + String(number) + "\"";
      String messtr = data + String((char)26);
      if (sendGprs(numstr, ">")) {
        if (sendGprs(messtr, MARKER_NO_CHECK)) {
          return true;
        }
      }
      return false;
    }
    

    Функция чтения SMS. Принятые значения помещаются в переменные message, phone и datetime, которые содержат, соответственно, пришедшую команду, номер телефона и время посылки.

    void readSms() {
      byte result = sendGprs("AT+CMGR=1,1", "+CMGR:");
      if (result == CHECK_MARKER) {
        parseSms(message, phone, datetime);
        Serial.print(F("Number:   ")); Serial.println(phone);
        Serial.print(F("Datetime: ")); Serial.println(datetime);
        Serial.print(F("Message:  ")); Serial.println(message);
        deleteSms();
        if (String(phone) == String(MASTER)) {
          Serial.println(F("Message from MASTER!"));
          gprsAnswer();
        }
      }
      else if (result == CHECK_OK) {
        Serial.println(F("No SMS"));
      } else {
          Serial.println(F("Error read SMS"));
        }
    }
    

    Функция формирования ответа на SMS запросы и команды. Эта функция специфична для каждого проекта и вы в своих проектах можете изменить её в соответствии с логикой работы вашей системы.

    void gprsAnswer() {
      String s = "";
      String mess = String(message);
      String data = "";
      if      (mess == DATA_TEMP1)  {s += mkTemp1();}
      else if (mess == DATA_CONT1)  {s += mkCont1();}
      else if (mess == DATA_LEAK1)  {s += mkLeak1();}
      else if (mess == DATA_SMOKE1) {s += mkSmoke1();}
      else if (mess == DATA_RELAY1) {s += mkRelay1();}
      else if (mess == DATA_SERVO1) {s += mkServo1();}
      else if (mess == DATA_PERIOD) {s += mkPeriod();}
      else if (mess == DATA_ALL)    {s += mkAll();}
      else if (mess.indexOf(F("=")) >= 0) {
        byte p = mess.indexOf(F("="));
        if      (mess.indexOf(CMD_RELAY1) >= 0) {data = mess.substring(p + 1); gprsSetRelay(data.toInt()); s += DATA_RELAY1; s += '='; s += stringSens(relayRelay);}
        else if (mess.indexOf(CMD_SERVO1) >= 0) {data = mess.substring(p + 1); gprsSetServo(data.toInt()); s += DATA_SERVO1; s += '='; s += stringSens(servoState);}
        else if (mess.indexOf(CMD_PERIOD) >= 0) {data = mess.substring(p + 1); gprsPeriod = data.toInt();  s += DATA_PERIOD; s += '='; s += String(gprsPeriod);}
      } else {
          Serial.println(F("Not command!"));
        }
    
      //Serial.print(F("mess: ")); Serial.println(mess);
      //Serial.print(F("answ: ")); Serial.println(s);
    
      if (s == "") {s = "Error";}
    
      Serial.println(F("Send answer... "));
      if (sendSms(MASTER, s)) {
        Serial.println(F("success"));
      } else {
          Serial.println(F("error"));
        }
    }
    

    Функции формирования ответов. Это функции которые формируют ответы, которые система посылает по SMS на смартфон пользователя в ответ на запрос или по своему усмотрению, в соответствии с заложенной программой.

    String stringSens(byte v) 
    String stringLeak(byte v)
    String stringSmoke(byte v)
    
    String mkTemp1()
    String mkCont1()
    String mkLeak1()
    String mkSmoke1()
    String mkRelay1()
    String mkServo1()
    String mkPeriod()
    String mkAll()
    

    И функции выполнения команд. Это функции, которые выполняют пришедшие по SMS команды и меняют состояние системы, т. е. изменяют её настройки или включают или отключают актуаторы.

    void gprsSetRelay(byte v)
    void gprsSetServo(byte v)
    

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

    Ещё немного о SoftwareSerial


    Всего вышеприведённого недостаточно, чтобы GPRS Shield заработал в прозрачном режиме и перестал блокировать остальные процессы, работающие на микроконтроллере. Нужно ещё модифицировать код библиотеки SoftwareSerial. Дело в том, что её стандартного буфера в 64 байта недостаточно для переваривания большинства AT GSM команд и поддержания динамического взаимодействия между модулем и контроллером.

    Нужно увеличить этот буфер как минимум до 128 байт и только после этого магия заработает и GPRS Shield начнёт нормально работать в прозрачном режиме. Делается это в файле SoftwareSerial.h. Строку

    #define _SS_MAX_RX_BUFF 64 // RX buffer size
    

    нужно поменять на

    #define _SS_MAX_RX_BUFF 128 // RX buffer size
    

    Заключение


    Вы видите обмен командами оператора и ответы системы на экране смартфона. И при этом система выполняет ещё десятки процессов в псевдо-многозадачном режиме. А GPRS Shield стал занимать менее 1 % процессорного времени, вместо 99 % как раньше, освободив остальное время для веб-сервера и прочих задач по управлению курятником.

    image

    Вот и всё, GPRS Shield мы интегрировали в AMS, преодолели все препятствия, заставили его работать в прозрачном режиме, не блокируя остальные процессы вроде веб-сервера, работы беспроводных сенсоров и актуаторов и это открывает заманчивые перспективы по построению огромного количества систем с GSM SMS управлением и контролем на основе AMS — теплиц, охранных и отопительных систем, различных вариантов умного дома и т. д. и т. п. практически до бесконечности.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 38
    • +6

      И им не стыдно за ЭТО брать деньги? Я про "Амперку".

      • 0
        В принципе, из представленного кода модуля АМС можно сделать нормальную библиотеку и продавать GPRS Shield с ней. Ещё можно напрячь разработчиков и устранить проблемы освящённые в этой статье. Но сделать это может только сама Амперка, если захочет.
      • +1
        Взяли бы https://jt5.ru/shields/gsm-shield/ и описанных вами проблем не было.
        Работает с Due, работа напрямую с UART (никаких SoftwareSerial), есть готовая библиотека под реальное использование…
        Ну и стоит вменяемо.
        • 0
          Вон оно как. К сожалению я об это узнал только сейчас от вас. Но есть и положительный момент — теперь я стал специалистом по GSM AT-командам. :)
          • +1
            Ну раз вы специалист, тогда ждем от вас пример работы модема в CMUX :)
            • 0
              Я же не могу писать библиотеки ко всему железу на рынке. Акробатики с GPRS Shield мне надолго хватило. Надеюсь, что в ближайшее время мне будут попадаться задачи попроще.
            • 0
              У других тоже AT-команды. Это стандарт GSM 07.07, и производители более-менее его придерживаются (впрочем, у того же Telit есть уже 24-я ревизия их документации на AT-команды).
              Но да, команды командами, а реализация их в виде библиотеки — совсем другое дело :)
            • 0
              Мы взяли, лежит парочка сейчас на столе. Годные шилды, с возможностью кастомизации.
              Только посмотрите на наличие. Потом на наличие других шилдов и плат.
              Мы на этих ребят уже не рассчитываем, в общем.
              • 0
                Это потому, что производство идет в основном под заказ.
                Сейчас на монтаже партия GSM-шилдов, через неделю уйдут клиенту.
                В работе новая платформа и уже 3G-шилды, но это уже решение для профессионалов.
            • 0
              И в итоге raspberry pi + 3g/4g модем оказывается дешевле и с гораздо более крутыми возможностями.
              • 0
                Иногда их модули с их библиотеками даже в демо-проектах не работают. Купил ребенку набор робота строить, оказалось, что мотор шилд и ИК приемник вместе не работают, при включении моторов ИК приемник начинает херню принимать… Я даже догадываюсь, почему, но т.к. кода их библиотеки не нашел, догадки остались догадками… А задумка была неплохой, концепция мне понравилась.
                • +2
                  Цель этой статьи не в том, чтобы поругать Амперку. Но ради справедливости могу сказать, что купил в подарок набор «Микроник», ребёнок собрал первое упражнение со светодиодом и уставился на меня. Я: «Что?». Ребёнок: «Не работает!». Я: «Ну это ты ошибся!». Ребёнок: «Проверяй!». Я проверил — всё правильно, почесал затылок и полез за тестером. Оказалось, что пружинные клеммы из набора просто не работают. У ребёнка без тестера и знаний электроники ни одного шанса. :)

                  Второе упражнение ребёнок решил пока не делать. :)
                  • 0
                    Оказалось, что пружинные клеммы из набора просто не работают
                    Набор вернули? Деньги за него отдали?
                    • 0
                      Возвращать набор мне как-то в голову не пришло. Самих деталей, как вы понимаете, у меня целая гора и клеммник это не проблема. Так, посмеялся и забыл. Там самое ценное это книжка с опытами и пояснениями.
                      • +1
                        Книжек с опытами можно сколько угодно найти, Питер недавно пару книг тут показывал по Ардуино. А когда детали из набора не работают — не важно сколько есть дома таких же, с шилдом тоже оказалось множество проблем, а в рекламе все очень красиво «подключил и заработало». Потому надо возвращать неработающие наборы и писать такие обзоры, что бы не велись на русские наборы «все в тридорога и не работает».
                        • +2
                          Если все будут делать так — то производитель и будет выпускать х-ню. А если вернут массово — это будет повод задуматься. Электронщику, действительно, не нужен набор, кроме книжки. Собственно, набор деталек в мешочке можно получить и из Китая за 0.25 цены.
                  • 0
                    Статья прикольная. А почему было бы сделать на ESP8266+Какой нибудь wi-fi GPRS роутер. И управлять этим через например Telegram? Или смсками в данном случае удобней?
                    • 0
                      Можно было сделать и на ESP8266, но этот проект делался на заказ и все концептуальные решения принимал заказчик. Я только воплощал ТЗ.
                      • +1

                        Заказчик требовал именно управление смс-ками и ардуино?

                        • 0
                          Да, всё делалось в соответствии с его пожеланиями.
                    • 0
                      Ценник на плату древним, снятым с производства модемом, конечно, конский. Я так понимаю, плата ставится сверху, над платой с контроллером, а сверху можно поставить какие-то ещё платы, и потом долго разбираться почему всё глючит и перезагружается. Было бы простительно, если бы софт нормальный прилагался… А так лучше купить из Китая плату с SIM800H — модуль новее, дешевле и лучше по всем показателям, да и убрать его вместе с антенной подальше от других модулей будет гораздо проще.
                      • 0
                        Сверху я не решился ничего поставить. :) Но в принципе сейчас всё работает стабильно.
                        • 0

                          Взять готовый мобильник в конце концов ;) Что-то мне подсказывает, что АТ команды можно и прямо в телефон передавать.

                          • 0
                            Если взять самый простой телефон и найти где в нем COM-порт, то вполне. Правда, со вторым, скорее всего, будут проблемы.
                            • 0

                              Старые телефоны, у которых в data кабеле был преобразователь уровней — с ними проблем не должно быть. У новых, с microUSB — да, проблема.

                          • +1
                            Да, модуль SIM800H может и лучше, но в плане работы с ним возникают вопросы — как развести плату для такого корпуса в домашних условиях? Явно возможностей ЛУТа здесь не хватает image
                            • 0
                              Под ЛУТ подойдет SIM800C. Просто готовые платы на SIM800H в Китае предлагают за 7-8 долларов.
                            • 0

                              встетил вот такую статью про переделку SIM900A в SIM900, простите за ссылку

                            • 0
                              А M590 не подошёл или тоже не знали о существовании?
                              • 0
                                Когда встал вопрос о GSM модуле я нашёл десяток вариантов и встал вопрос какой купить. По картинкам понять какой из них лучше и какой хуже невозможно. Покупать все десять и тестировать тоже не вариант. Поэтому выбор пал на рассматриваемый модуль из за его брендовости и наличия поддержки. А что из этого вышло вы уже знаете.
                              • +5
                                Спасибо за статью, и мои поздравления с завершением квеста!

                                От лица Амперки могу сказать, что библиотеку мы переписали ещё раз. Теперь она работает с Arduino Mega, Due и даже M0.
                                Примеры работы с этими платами здесь:
                                http://wiki.amperka.ru/%D0%BF%D1%80%D0%BE%D0%B4%D1%83%D0%BA%D1%82%D1%8B:gprs-shield

                                Хотя, я думаю, что после изучения вашего модуля будем переписывать эту библиотеку снова:)

                                P.S. Спасибо за ваш труд. AMS — очень крутая штука.
                                • 0
                                  Это отличная новость для всех.
                                • 0
                                  А статья про CAN будет иметь продолжение? А то столько времени прошло, может решили не писать?
                                  • 0
                                    Я помню про этот цикл, но писать посредственную статью не хочется, а что-то интересное пока не складывается. Но как только появится интересные идеи и материал, то обязательно напишу.
                                  • +1
                                    а почему смс а не gprs
                                    на ардуине можно и веб страничку нарисовать и в смартфоне тыкать
                                    • 0
                                      Можно и GPRS, но в ТЗ было SMS, да и это немного разные вещи — SMS более лёгкие, по другому тарифицируются и т. д. Ещё планировалось управление при помощи нативных приложений на Android и iOS, но пока это реализовано не было.
                                    • 0
                                      Все смешалось — криворукость платомейкеров, кнопкодавов и реализаторов. Но радует, что куры живы остались.
                                      • 0
                                        Какой ад.

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.