КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

SMS-телеграф на базе GSM/GPRS Shield и термопринтера

В этом уроке мы создадим SMS-телеграф, по сути мы совместим две технологии, SMS и телеграф:

  • SMS (Short Message Service) - служба коротких сообщений.
  • Телеграф - теле (от греческого tēle) - далеко, граф (от греческого graphō) - пишу.

Значит наше устройство «SMS-телеграф» будет принимать SMS-сообщения с помощью GSM/GPRS Shield и печатать их на кассовой ленте при помощи термопринтера.

Термопринтер требует до 1,5 А (в пиках при печати), чего не сможет выдать USB порт компьютера, так что запитаем наш «SMS-телеграф» от Battery Shield.

Описание работы SMS-телеграфа:

При подаче питания GSM/GPRS Shield зарегистрируется в сети сотового оператора и термопринтер выведет приветственное сообщение о готовности SMS-телеграфа к работе (процесс регистрации в сети оператора занимает некоторое время). Приветственное сообщение завершается ответом на USSD запрос *100# (запрос баланса). При желании данный запрос можно изменить в скетче.

После этого все входящие SMS-сообщения будут печататься на кассовой ленте, в т.ч. и на Русском языке. В сообщениях будет указан номер отправителя и дата отправки SMS.

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

Получив входящее сообщение, устройство будет отправлять ответную SMS. Если сообщение распечатано, то текст ответной SMS будет "Ваше сообщение получено, спасибо!". А если сообщение не распечатано (отсутствует кассовая лента), то текст ответной SMS будет "Сообщение не распечатано, извините!".

Нам понадобится:

    Для реализации проекта нам необходимо установить библиотеки:
  • iarduino_GSM - для работы с GSM/GPRS Shield.
  • Adafruit_Thermal - для работы с термопринтером.
  • Библиотека SoftwareSerial входит в стандартный набор Arduino IDE.

О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.

Схема сборки:

В схеме GSM/GPRS Shield и термопринтер будут работать по программным шинам UART.

Arduino / Piranha UNO:

    Назначение выводов платы Arduino / Piranha UNO в данном проекте:
  • D7 - (RX) прием данных от GSM/GPRS Shield.
  • D8 - (TX) передача данных на GSM/GPRS Shield.
  • D9 - (PWR) включение/выключение GSM/GPRS Shield.
  • D10 - (RX) прием данных от термопринтера.
  • D11 - (ТХ) передача данных на термопринтер.

Batery Shield:

Установите Battery Shield на Arduino / Piranha UNO:
Во время установки Battery Shield должен быть в выключенном состоянии.

GSM/GPRS Shield:

На Battery Shield установите GSM/GPRS Shield A6:

На плате GSM/GPRS Shield A6 имеется слот для SIM-карт и переключатель UART. Установите SIM-карту в слот, выберите положение переключателя RX-7/TX-8.

Термопринтер:

Подключите термопринтер к выводам D10 (зелёный провод) и D11 (синий провод), а питание к 5V (красный провод), и GND (чёрный провод):

Желтый провод термопринтера (DTR) в данном проекте не используется.

Код программы (скетч):

//  Подключаем библиотеки:                                        //
#include         <SoftwareSerial.h>                               // Подключаем библиотеку для работы по программной шине UART.
#include         <Adafruit_Thermal.h>                             // Подключаем библиотеку для работы с термопринтером.
#include         <iarduino_GSM.h>                                 // Подключаем библиотеку iarduino_GSM для работы с GSM/GPRS Shield.
//  Создаём объекты:                                              //
SoftwareSerial   softSerial1( 7, 8);                              // Создаём объект softSerial назначая выводы RX и TX платы Arduino, подключенные к GSM/GPRS Shield.
SoftwareSerial   softSerial2(10,11);                              // Создаём объект softSerial назначая выводы RX и TX платы Arduino, подключенные к термопринтеру (RX=10-зелёный, TX=11-синий).
Adafruit_Thermal prn(&softSerial2);                               // Создаём объект prn для работы с функциями и методами библиотеки Adafruit_Thermal, указывая ссылку на созданный ранее объект softSerial2 библиотеки SoftwareSerial.
iarduino_GSM     gsm;                                             // Создаём объект gsm для работы с функциями и методами библиотеки iarduino_GSM.
//  Объявляем переменные и функции:                               //
char             SMStxt[161];                                     // Объявляем строку для хранения текста принятых SMS сообщений.
char             SMSnum[ 13];                                     // Объявляем строку для хранения номера отправителя SMS сообщений.
char             SMStim[ 18];                                     // Объявляем строку для хранения даты и времени отправки SMS сообщений.
uint16_t         SMS_ID;                                          // Объявляем переменную для хранения идентификатора составного SMS сообщения.
uint8_t          SMS_SUM;                                         // Объявляем переменную для хранения количества SMS в составном сообщении.
uint8_t          SMS_NUM;                                         // Объявляем переменную для хранения номера SMS в составном сообщении.
char*            R(char*);                                        // Объявляем функцию преобразования текста из кодировки UTF-8 в кодировку CP866.
                                                                  //
void setup(){                                                     // Код функции setup выполняется только 1 раз, при старте скетча.
//  Готовим GSM к работе:                                         //
    gsm.begin(softSerial1);                                       // Инициируем работу с GSM/GPRS Shield, указывая объект шины UART softSerial1.
    while(gsm.status()!=GSM_OK){delay(1000);}                     // Ждём завершения регистрации модема в сети оператора связи.
    gsm.TXTreadCoding(GSM_TXT_CP866);                             // Выбираем кодировку CP866 для вывода текста SMS сообщений и ответов на USSD (так как в этой кодировке выводит текст принтер).
    gsm.TXTsendCodingDetect("п");                                 // Определяем кодировку скетча для отправки SMS (функция автоматически определит кодировку по строке состоящей из символа 'п').
    do{gsm.runUSSD("*100#").toCharArray(SMStxt, 161);}            // Отправляем USSD запрос "*100#" (баланс) сохраняя ответ в строку SMStxt, не более 161 байта.
    while(strlen(SMStxt)==0);                                     // Если строка пуста, то повторяем запрос.
//  Переключаемся на принтер:                                     //
    softSerial1.end();                                            // Завершаем  работу с шиной UART через объект softSerial1 (gsm).
    softSerial2.begin(9600);                                      // Инициируем работу с шиной UART через объект softSerial2 (prn).
    prn.begin();                                                  // Инициируем работу с термопринтером. В качестве параметра можно указать время нагрева пикселей от 3 (0,03 мс) до 255 (2,55 мс), чем выше тем темнее пикселы. Значение по умолчанию = 120 (1,20 мс)
    prn.println(F("================================"));           // Выводим приветствие:
    prn.justify('C');                                             // Устанавливаем выравнивание текста по центру 'C' (Center).
    prn.setSize('M');                                             // Устанавливаем средний размер шрифта 'M' (Medium).
    prn.println(R("ПРИВЕТСТВУЕМ ВСЕХ НА КАНАЛЕ"));                // 
    prn.setSize('L');                                             // Устанавливаем крупный размер шрифта 'L' (Large).
    prn.println(R("IARDUINO"));                                   // 
    prn.setSize('S');                                             // Устанавливаем маленький размер шрифта 'S' (Small) - используется по умолчанию.
    prn.justify('L');                                             // Устанавливаем выравнивание текста по левому краю 'L' (Left) - используется по умолчанию.
    prn.println(R(" GSM/GPRS Shield и термопринтер "));           // 
    prn.println(R(" готовы к приёму SMS сообщений! "));           // 
    prn.println(F("--------------------------------"));           //
    prn.print  (R("Баланс: ")); prn.println(SMStxt);              // И полученный ранее баланс из строки SMStxt.
    prn.feed(3);                                                  // Прокручиваем ленту на 3 строки
//  Переключаемся на GSM:                                         //
    softSerial2.end();                                            // Завершаем работу  с шиной UART через объект softSerial2 (prn).
    softSerial1.begin(9600);                                      // Инициируем работу с шиной UART через объект softSerial1 (gsm).
}                                                                 //
                                                                  //
void loop (){                                                     //
    if(gsm.SMSavailable()){                                       // Функция SMSavailable() возвращает количество входящих непрочитанных SMS сообщений.
//  Если есть входящее непрочитанное SMS, то читаем его ...       //
        gsm.SMSread(SMStxt,SMSnum,SMStim,SMS_ID,SMS_SUM,SMS_NUM); // Читаем SMS сообщение в ранее объявленные переменные (текст SMS сообщения, адрес отправителя, дата отправки, идентификатор SMS, количество SMS, номер SMS).
//      Определяем переменные:                                    //
        bool f1 = false;                                          // Определяем Флаг сигнализирующий о наличии кассовой ленты.
        bool f2 = true;                                           // Определяем Флаг сигнализирующий о необходимости отправки ответного SMS-сообщения.
        bool f3 = false;                                          // Определяем Флаг сигнализирующий о успешной отправке ответного SMS-сообщения.
        int  f4 = 3;                                              // Определяем переменную с количеством попыток отправки ответного SMS-сообщения.
//      Переключаемся на принтер:                                 //
        softSerial1.end();                                        // Завершаем  работу с шиной UART через объект softSerial1 (gsm).
        softSerial2.begin(9600);                                  // Инициируем работу с шиной UART через объект softSerial2 (prn).
//      Печатаем текст принятого SMS-сообщения:                   //
        if(prn.hasPaper()){                                       // Если в термопринтере есть кассовая лента, то ...
            f1 = true;                                            // Устанавливаем Флаг сигнализируя о наличии кассовой ленты.
            prn.println(F("================================"));   // Печатаем текст "================================".
            prn.setSize('M');                                     // Устанавливаем средний размер шрифта 'M' (Medium).
            prn.println(R("Получено SMS сообщение:"));            // Печатаем текст "Получено SMS сообщение:".
            prn.setSize('S');                                     // Устанавливаем маленький размер шрифта 'S' (Small) - используется по умолчанию.
            if(SMS_SUM>1){                                        // Если это составное сообщение, то печатаем:
                prn.print(F("SMS " )); prn.print  (SMS_NUM);      // - номер SMS в составе составного сообщения.
                prn.print(R(" из " )); prn.print  (SMS_SUM);      // - количество SMS в составном сообщении.
                prn.print(F(", ID=")); prn.println(SMS_ID );      // - идентификатор составного сообщения.
            }                                                     //
            prn.print(R("От абонента ")); prn.println(SMSnum );   // Печатаем номер отправителя SMS сообщения.
            prn.print(R("Отправлено  ")); prn.println(SMStim );   // Печатаем дату отправки SMS сообщения.
            prn.println(F("--------------------------------"));   // Печатаем текст "--------------------------------".
            prn.println(SMStxt);                                  // Печатаем текст SMS сообщения.
            prn.feed(3);                                          // Прокручиваем ленту на 3 строки
        }                                                         //
//      Переключаемся на GSM:                                     //
        softSerial2.end();                                        // Завершаем работу  с шиной UART через объект softSerial2 (prn).
        softSerial1.begin(9600);                                  // Инициируем работу с шиной UART через объект softSerial1 (gsm).
//      Запрещаем отправлять ответные SMS-сообщения на номера:    //
        if( strlen(SMSnum)               <  11  ){f2=false;}      // Если сообщение пришло с короткого номера (меньше 11 знаков), то на него не отвечаем.
        if(        SMSnum[0]             != '7' ){f2=false;}      // Если сообщение пришло с номера начинающегося не с +7 а, например, с +3 (номера заморские), то на них не отвечаем.
        if( strcmp(SMSnum,"70123456789") == 0   ){f2=false;}      // Если сообщение пришло с номера +7(012)345-67-89 (можно создать чёрный список номеров), то на него не отвечаем.
                                                                  // А можно в начале определить f2=false; а тут не запрещать, а разрешать отправку ответного SMS. Например, так: if( strcmp(SMSnum,"70123456789") != 0 ){f2=true;} // отвечать только на указанный номер.
//      Определяем текст для ответного SMS-сообщения:             //
        if(f1){                                                   // Если в принтере есть кассовая лента, то ...
            strcpy(SMStxt,"Ваше сообщение получено, спасибо!");   // Сохраняем положительный ответ в SMStxt.
        }else{                                                    // Иначе, если в принтере нет кассовой ленты, то ...
            strcpy(SMStxt,"Сообщение не распечатано, извините!"); // Сохраняем отрицательный ответ в SMStxt.
        }                                                         //
//      Отправляем ответное SMS-сообщение:                        //
        if(f2==true){                                             // Если на номер телефона SMSnum не запрешено отправлять ответные SMS-сообщения, то ...
            if(SMS_SUM==SMS_NUM){                                 // Если порядковый номер SMS совпадает с количеством SMS в сообщении (чтоб не подтверждать длинное сообщение несколько раз), то ...
                do{                                               // Код в теле оператора do...while выполняется один раз, а потом повторяется пока истинно условие while.
                    f3 = gsm.SMSsend(SMStxt, SMSnum); f4--;       // Отправляем ответное SMS-сообщение с текстом SMStxt на номер SMSnum. Результат отправки true/false сохраняем в f3, а значение f4 уменьшаем на 1.
                    if(f3==false){delay(3000);}                   // Если SMS не отправлена, то ждём 3 секунды ...
                }                                                 //
                while( f3==false && f4>0 );                       // Если сообщение не отправлено (f3==false) и количество попыток не исчерпано (f4>0), то повторяем отправку SMS-сообщения.
            }                                                     //
        }                                                         //
    }                                                             //
    delay(1000);                                                  // Ждём 1 секунду (нет смысла проверять наличие входящих SMS чаще).
}                                                                 //
                                                                  //
//  Функция преобразования текста из кодировки UTF-8 в кодировку CP866:
char* R(char* str){                                                                                                                        // Определяем функцию которая преобразует код русских символов из кодировки UTF-8 в кодировку CP866
    uint8_t i=0, j=0;                                                                                                                      // Определяем переменные: i - счетчик входящих символов, j - счетчик исходящих символов
    while(str[i]){                                                                                                                         // Проходим по всем символам строки str, пока не встретим символ конца строки (код 0)
        if(uint8_t(str[i]) == 0xD0 && uint8_t(str[i+1]) >= 0x90 && uint8_t(str[i+1]) <= 0xBF ){str[j] = (uint8_t) str[i+1]-0x10; i++;}else // Символы «А-Я а-п» (код UTF-8: D090-D0AF D0B0-D0BF) сохраняем в кодировке CP866: код 80-9F A0-AF (символ занимал 2 байта, а стал занимать 1 байт)
        if(uint8_t(str[i]) == 0xD1 && uint8_t(str[i+1]) >= 0x80 && uint8_t(str[i+1]) <= 0x8F ){str[j] = (uint8_t) str[i+1]+0x60; i++;}else // Символы «р-я»     (код UTF-8: D180-D18F)           сохраняем в кодировке CP866: код E0-EF       (символ занимал 2 байта, а стал занимать 1 байт)
        if(uint8_t(str[i]) == 0xD0 && uint8_t(str[i+1]) == 0x81                              ){str[j] = 0xF0;                    i++;}else // Символ «Ё»        (код UTF-8: D081)                сохраняем в кодировке CP866: код F0          (символ занимал 2 байта, а стал занимать 1 байт)
        if(uint8_t(str[i]) == 0xD1 && uint8_t(str[i+1]) == 0x91                              ){str[j] = 0xF1;                    i++;}else // Символ «ё»        (код UTF-8: D191)                сохраняем в кодировке CP866: код F1          (символ занимал 2 байта, а стал занимать 1 байт)
                                                                                              {str[j] = (uint8_t) str[i];}  j++; i++;      // Остальные символы оставляем как есть, без преобразования, но их место в строке могло сдвинуться, если до них были встречены русские символы
    }   while(j<i){str[j]=0; j++;} return str;                                                                                             // Так как место занимаемое символами в строке могло уменьшиться, заполняем оставшиеся байты символами конца строки (код 0)
}

В скетче создаются два объекта для работы с программными шинами UART, один для общения с GSM/GPRS Shield A6 - softSerial1, а второй для общения с термопринтеромsoftSerial2, но библиотека SoftwareSerial позволяет работать только с одной программной шиной UART. Как выполняется работа с двумя программными шинами UART описано в разделе "Алгоритм работы скетча".

Обратите внимание на некоторую особенность Arduino IDE (возможно в новых версиях этот недочёт будет исправлен):

  • Если скетч сохранить и загрузить в Arduino, то текст будет иметь кодировку UTF-8.
  • Если скетч не сохранять и загрузить в Arduino, то текст будет иметь кодировку WIN1251. Следовательно, для не сохранённых скетчей, функция R() работать не будет!

Алгоритм работы скетча:

В коде setup() происходит инициализация работы GSM/GPRS Shield A6, ожидание регистрации модема в сети сотового оператора, задание кодировки входящих SMS, определение кодировки для исходящих SMS и выполняется USSD запрос баланса *100#. Как показала практика, оператор может быть не готов к ответу на USSD запрос баланса сразу после регистрации, по этому запрос отправляется в цикле do{}while() пока мы не получим ответ. Как только ответ получен, на термопринтере, выводится приветственное сообщение и ответ на запрос баланса.

В коде loop() каждую секунду функцией SMSavailable() проверяется наличие входящих SMS. Как только появляется входящая SMS, её данные читаются в переменные SMStxt, SMSnum, SMStim, SMSlongID, SMSlongSUM, SMSlongNUM и выводятся на термопринтере.  Перед началом печати проверяется наличие кассовой ленты. После печати полученного сообщения отправляется ответная SMS с текстом "Ваше сообщение получено, спасибо!" или, если нет кассовой ленты, "Сообщение не распечатано, извините!". Ответная SMS отправляется с проверкой отправки и в случае неудачи отправка повторяется. Количество попыток отправки ответной SMS - 3 раза.

Дополнительно реализована функция с коротким именем R(). Она нужна для преобразования текста скетча написанного на Русском языке из кодировки скетча (UTF-8) в кодировку принтера (CP866). Текст SMS-сообщений не нуждается в дополнительном преобразовании, так как его кодировка указана функцией TXTreadCoding() в коде setup().

Библиотека SoftwareSerial позволяет работать только с одной программной шиной UART, а в нашем проекте два устройства используют программный UART. Это стало возможным благодаря разделению работы с устройствами по времени. В коде loop() постоянно работает шина UART объекта softSerial1 (для чтения входящих SMS), а как только SMS прочитана, то мы завершаем работу с программной шиной UART GSM/GPRS Shield обращаясь к функции end() объекта softSerial1 и инициируем работу программной шины UART термопринтера, обращаясь к функции begin()  объекта softSerial2. Далее прочитанные данные выводятся на принтер, после чего работа с ним прекращается, мы выполняем те же действия, но вызываем функцию end() объекта softSerial2, а функцию brgin() объекта softSerial1. Таким образом опять начинаем работать с GSM/GPRS Shield для проверки и чтения SMS.

Библиотека iarduino_GSM конфигурирует GSM/GPRS Shield так, что все входящие SMS-сообщения сохраняются на SIM-карте, а при чтении удаляются из неё. Так что если входящие SMS поступят в момент печати, они не потеряются, а будут прочитаны из SIM-карты после завершения печати.

Ссылки:




Обсуждение

Гарантии и возврат Используя сайт Вы соглашаетесь с условями