Не работает Stream->readString()

switch
Offline
Зарегистрирован: 07.12.2015

Добрый день!

Что-то третий день уже ковыряюсь и не могу понять в чем дело. Имеем ардуину, к ней через программный последовательный порт подключен SIM800L. В последствии с программного переедет на аппаратный, когда все заработает как надо. Имеется объект, в котором реализую функции работы с модемом. В конструктор передается указатель типа Stream на объект SoftwareSerial. Только вот не получается с него данные в виде строки получить.

    String _resp = "";
    if (serialPort->available()) { 
       Serial.print(serialPort->available());  //показывает что в буфере что-то накопилось
       while (serialPort->available()) 
       {
         // пробовал так вот
         //char symbol = ' '; 
         //symbol=(char) serialPort->read();
         //_resp.concat(symbol);

         //пробовал и так
          _resp.concat((char)serialPort->read());

         //delay(10); Это тоже пробовал ставить

         //так выводит все в другой порт в нормальном виде
         //Serial.print((char) serialPort->read());
      
         //вот это не работает, казалось бы то что надо
         //_resp = serialPort->readString();
       } 
   
       Serial.print("readString: ");
       Serial.print(_resp);  
       Serial.println(":");   }
  }

Что я не так делаю?

 

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

А что возвращает вызов concat? Должен при успешной операции true вертать.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Можт этот ваш SIM800 строку не терминирует \n или шлёт более одной секунды свой реплай?

switch
Offline
Зарегистрирован: 07.12.2015

Ноль возвращает. Вот так проверял:

Serial.print(_resp.concat((char)serialPort->read()));

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

switch пишет:

Ноль возвращает. Вот так проверял:

Serial.print(_resp.concat((char)serialPort->read()));

 

Значит, false возвращает. Я обычно просто оператором сложения пользуюсь:

resp += (char) serialPort->read();

Проблем никогда не замечал. Надо смотреть в исходниках, шо там конкретно. Щас гляну.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

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

switch
Offline
Зарегистрирован: 07.12.2015

Запускается на меге168, может и не хватает памяти... Наверно придется от всех объектов избавляться. пока вот:

Скетч использует 7672 байт (46%) памяти устройства. Всего доступно 16384 байт.
Глобальные переменные используют 851 байт динамической памяти.

В последствии мега328 будет

Но когда памяти не хватает он же должен зависнуть? А оно работает.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

switch пишет:

Запускается на меге168, может и не хватает памяти... Наверно придется от всех объектов избавляться. пока вот:

Скетч использует 7672 байт (46%) памяти устройства. Всего доступно 16384 байт.
Глобальные переменные используют 851 байт динамической памяти.

В последствии мега328 будет

Но когда памяти не хватает он же должен зависнуть? А оно работает.

Если вы посмотрите исходники WString.cpp - то увидите, как String работает с памятью: пока объект жив - внутренний буфер, выделенный на куче - занят. Буфер переаллоцируется, если не хватает места. Следовательно, неумеючи можно легко одним глобальным объектом String усрать всю оперативку ;) 

switch
Offline
Зарегистрирован: 07.12.2015

вон оно что... придется попробовать другие методы...

спасибо за помощь!

andycat
andycat аватар
Онлайн
Зарегистрирован: 07.09.2017

switch пишет:

В последствии с программного переедет на аппаратный, когда все заработает как надо.

 

 

оно и на програмном uart нормально написанный код работает.

переходите на буфер char, и вручную обрабатывайте каждый входящий байт из UART и не будет никаких переполнений String

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Вообще-то скетч неполный. Не видно ни как работаетсте со стрингов, ни как передаёте поток, сделайте минимальный (по размерам) но полный скетч, на котором проявляется проблема. Тогда разговор будет предметным.

switch
Offline
Зарегистрирован: 07.12.2015

В качестве основы брал код из этих статей http://codius.ru/articles/GSM_%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D1%8C_SIM800L_%D1%87%D0%B0%D1%81%D1%82%D1%8C_1, только в классы решил обернуть для удобства. Весь код разбит на кучу файлов, все работает, осталось вот только модем заставить работать.

Вот описание класса

  //конструктор класса, первым параметром передаем имя порта для работы 
  //вторым - передаем номер сети оператора, к которой поднимаем инет и настройки
  // 1=МТС 2=Мегафон 3=Билайн 4=Теле2 5=Ростелеком
  cSIM800L::cSIM800L(Stream * _serialPort, int _netType)
  {
    netType=_netType;
    //задаем указатель на порт
    serialPort = _serialPort;
  }

  //отправка команды в модем с ожиданием ответа
  String cSIM800L::sendATCommand(String cmd, int timeout)
  { 
    String _resp = "";                                              // Переменная для хранения результата
    //debugout(cmd);
    serialPort->println(cmd);                                            // Отправляем команду модулю
    if (timeout > 0) 
    {                                                  // Если необходимо дождаться ответа...
      _resp = waitResponse(timeout);                                       // ... ждем, когда будет передан ответ
      if (_resp.startsWith(cmd)) {                                  // Убираем из ответа дублирующуюся команду
        _resp = _resp.substring(_resp.indexOf("\r", cmd.length()) + 2);
      }                                   
    }
    return _resp;                                                   // Возвращаем результат. Пусто, если проблема
  }
  //ожидание ответа
  String cSIM800L::waitResponse(int timeout=500)
  {  
    String _resp = "";                                              // Переменная для хранения результата
    long _timeout = millis() + timeout;                               // Переменная для отслеживания таймаута (10 секунд)
    while (!serialPort->available() && millis() < _timeout) {};         // Ждем ответа 10 секунд, если пришел ответ или наступил таймаут, то...
    if (serialPort->available()) {                                       // Если есть, что считывать...
      _resp += serialPort->read();                                  // ... считываем и запоминаем
    } 
       return _resp;                                                   // ... возвращаем результат. Пусто, если проблема
  }
  
  //проверяем включился ли модем
  //возвращает уровень сигнала или -1 если модем не отвечает или недоступен
  int cSIM800L::Test()
  {    
    Serial.println("test: ");
    String _response    = "";   
    _response=sendATCommand("AT+CPIN?",500);
    _response.trim();                                       // Убираем пробелы в начале/конце
        if (_response.endsWith("OK")) {                         // Если ответ заканчивается на "ОК"
          sendATCommand("AT+CSQ");   // Делаем сообщение прочитанным  
        }
  }

Работаем с ним так:



//определим порты до порта модема, чтоб потом переопределился выход 
cPorts ports;

//rxPin, txPin, inverse_logic, buffer size
SoftwareSerial modemPort(SRX, STX);
cSIM800L modem(&modemPort, OPER_MTS);

Ticker tickerTestModem(TestModem, 1000); 

void setup() {
  Serial.begin(9600);
  modemPort.begin(9600);
  Serial.println("Start!");
  modemPort.setTimeout(1000);
  //периодически пишем состояние портов 
  tickerTestModem.start(); //start the ticker.
  
  //modemPort.println("AT"); 
  modem.Test();

  //настраиваем порты
  PortsSetup();
}

//периодически вызываем тест
void TestModem()
{
  modem.Test();   
}


void loop() {
  //обновление таймера
  tickerTestModem.update();
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, по-прежнему, нет короткого полного кода, который я мог бы запустить у себя и посмотреть.

switch
Offline
Зарегистрирован: 07.12.2015

Вот код, компилируется


#include <SoftwareSerial.h>


#define STX 8  //scl
#define SRX 9 //D6

#define SIREN 2
#define LAMP 4

#define DEBUG 1

//тип указатель на функцию c аргументом в виде строки
typedef void (*PHandlerString)(String value);

#define OPER_MTS 0
#define OPER_MEGAFON 1
#define OPER_BEELINE 2
#define OPER_TELE2 3
#define OPER_RTK 4
 

class cSIM800L
{
  private:
    //тут храним заданный номер выбранной сети
    byte netType = 0;
 
    //этот сокет используется для работы модема
    //указатель на последовательный порт 
    //порт может быть любым, аппаратным или программным
    Stream* serialPort = NULL;
    
    //массив ключевых слов для обработки событий
    //массив обработчиков событий    

  public:

    //конструктор класса, первым параметром передаем имя порта для работы 
    //вторым - передаем номер сети оператора, к которой поднимаем инет и настройки
    // 1=МТС 2=Мегафон 3=Билайн 4=Теле2 5=Ростелеком
    cSIM800L(Stream * _serialPort, int _netType);
    //проверяем включился ли модем
    //возвращает уровень сигнала или -1 если модем не отвечает или недоступен
    int Test();
    //подключение к адресу в инете по протоколу тцп
    //если все окей то возвращает true
    bool OpenTCP(String ipaddr, String port);
    //отправка команды через подключение
    bool Send(String command, String value);
    bool Send(String command, int value);
    bool Send(String command, float value);
    //Подключено ли соединение
    bool IsConnected();
    //Возвращает true если модем завершил обработку команды
    bool Ready();
    //отключение от сокета: отключает текущее соединение
    bool CloseTCP();
    //отправка команды в модем с ожиданием ответа
    String sendATCommand(String cmd, int timeout=500);
    //ожидание ответа
    String waitResponse(int timeout=500);
    //основной цикл работы
    void DoLoop();
    void cSIM800L::debugout(String str);
};
  //конструктор класса, первым параметром передаем имя порта для работы 
  //вторым - передаем номер сети оператора, к которой поднимаем инет и настройки
  // 1=МТС 2=Мегафон 3=Билайн 4=Теле2 5=Ростелеком
  cSIM800L::cSIM800L(Stream * _serialPort, int _netType)
  {
    netType=_netType;
    //задаем указатель на порт
    serialPort = _serialPort;
  }

  //отправка команды в модем с ожиданием ответа
  String cSIM800L::sendATCommand(String cmd, int timeout)
  { 
    String _resp = "";                                              // Переменная для хранения результата
    //debugout(cmd);
    serialPort->println(cmd);                                            // Отправляем команду модулю
    if (timeout > 0) 
    {                                                  // Если необходимо дождаться ответа...
      _resp = waitResponse(timeout);                                       // ... ждем, когда будет передан ответ
      if (_resp.startsWith(cmd)) {                                  // Убираем из ответа дублирующуюся команду
        _resp = _resp.substring(_resp.indexOf("\r", cmd.length()) + 2);
      }                                   
    }
    return _resp;                                                   // Возвращаем результат. Пусто, если проблема
  }
  //ожидание ответа
  String cSIM800L::waitResponse(int timeout=500)
  {  
    String _resp = "";                                              // Переменная для хранения результата
    long _timeout = millis() + timeout;                               // Переменная для отслеживания таймаута (10 секунд)
    while (!serialPort->available() && millis() < _timeout) {};         // Ждем ответа 10 секунд, если пришел ответ или наступил таймаут, то...
    if (serialPort->available()) {                                       // Если есть, что считывать...
      _resp += serialPort->read();                                  // ... считываем и запоминаем
    } 
       return _resp;                                                   // ... возвращаем результат. Пусто, если проблема
  }
  
  //проверяем включился ли модем
  //возвращает уровень сигнала или -1 если модем не отвечает или недоступен
  int cSIM800L::Test()
  {    
    Serial.println("test: ");
    String _response    = "";   
    _response=sendATCommand("AT+CPIN?",500);
    _response.trim();                                       // Убираем пробелы в начале/конце
        if (_response.endsWith("OK")) {                         // Если ответ заканчивается на "ОК"
          sendATCommand("AT+CSQ");   // Делаем сообщение прочитанным              
        }
  }

//rxPin, txPin, inverse_logic, buffer size
SoftwareSerial modemPort(SRX, STX);
cSIM800L modem(&modemPort, OPER_MTS);


void setup() {
  Serial.begin(9600);
  modemPort.begin(9600);
  Serial.println("Start!");
  modemPort.setTimeout(1000);
  modem.Test();
 
}
void loop() {
  //обновление таймера
  modem.Test();   
  delay(1000);
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Так лучше.

Во-первых, если Вы хотите в строках №№ 97-99 вычитывать всё, что пришло, то в строке №97 должен быть while, а не if.

Во-вторых, в конце цикла (перед строкой №99) нужна задержка. На скорости 9600 байты приходят медленно и они просто не будут успевать прийти, как Вы уже перестанете читать.

Для начала поставьте какую-нибудь "бешеную" задержку типа 100мс.

После этих двух исправлений, у Вас всё должно заработать (у меня заработало). Потом подберёте разумную задержку, НО ... очень не советую делать так.

Лучше воспользоваться тем, что строка от модема имеет явный признак конца (возврат каретки). Вот и читайте пока не прочитаете этот символ. Ждите сколько надо.

--------------------

Ну и ещё ряд замечаний, которые прямо не влияют на данную проблему, но в глаза бросились:

  1. Строки №№95-96 с millis так не работают – возможны проблемы при  переполнении (см. как надо здесь).
  2. Строки №№ 59 и 77 – зачем String передаётся по значению? Какие на то причины? Память девать некуда? Напишите вместо «String» - «String &» и Вы не будете создавать новый экземпляр (копию) строки только ради того, чтобы передать её параметром.
  3. Строка №64: «cSIM800L::» не нужно. Кстати, «debugout» не определена.
  4. Строка №92 – умолчательное значение здесь не указывается (игнорируется). Оно уже указано при объявлении функции в строке №61
  5. В строке 95 переменную «_timeout» лучше объявлять unsigned long, чтобы избежать сравнения знакового с беззнаковым в следующей строке.
  6. Стоки №№ 105-114, функция, объявленная как «int» ничего не возвращает.
switch
Offline
Зарегистрирован: 07.12.2015

Спасибо большое!

по поводу 9600 - может увеличить скорость просто? Я как-то сталкивался с плохой работой программного последовательного порта на низкой скорости. Это имеет значение?

Касательно пункта 6 - функция не дописана просто. В этом коде много чего нету. 

Большое спасибо за помощь, я все учту!

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, хоть заработало-то? А скорость - не знаю, смотрите сами. В любом случае лучше ждать терминального символа, чем играться с задержками.

switch
Offline
Зарегистрирован: 07.12.2015

Нет, не заработало ;(

Терминального символа ждать не могу - модем отправляет данные как ему удобно, часто несколькими строками. 

Вот эта штука работает прозрачно:

void loop() {
  if (Serial.available()) {      // If anything comes in Serial (USB),
    modemPort.write(Serial.read());   // read it and send it out Serial1 (pins 0 & 1)
    //Serial.write('-');
  }

  if (modemPort.available()) {     // If anything comes in Serial1 (pins 0 & 1)
    Serial.write(modemPort.read());   // read it and send it out Serial (USB)
    //Serial.write('=');
  }

Значит данные ходят туда-сюда.

Serial.available()

Показывает что байты в буфере есть, Readstring() не читает их, хотя буфер опустошается. (char)modemPort->read()  не читает тоже. Сейчас хочу попробовать сделать буфер из char и работать без String, но не хочется фиксированный буфер иметь. Хотя планирую команды не больше 64 байт длинной и вроде на ответы модема это тоже хватит...

По-правде расстраивает то что везде в инете примеры работают, а у меня - нет. Как так-то?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

У меня появилось ощущение, что Вы меня троллите.

Ваш код из поста #13 отлично работает с теми правками, что я написал. Но! В нём (в коде из #13) нет никакого readString!

Вы показываете мне один код, а говорите о другом?

switch
Offline
Зарегистрирован: 07.12.2015

Я не троллю, я не клапануций сотоварищи. У меня не работает очевидный код, а я не понимаю почему. В общем разбираюсь, экспериментирую. Почему-то работа с последовательным портом у меня все время сопряжена с каким-то гимором...

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

switch пишет:

Я не троллю

Тогда откуда в коде вообще взялся readString, если его там нет?

switch
Offline
Зарегистрирован: 07.12.2015

В самом первом посте я перечислял как пробовал. За основу брал такой код из примеров:

String sendATCommand(String cmd, bool waiting) {
  String _resp = "";                            // Переменная для хранения результата
  Serial.println(cmd);                          // Дублируем команду в монитор порта
  SIM800.println(cmd);                          // Отправляем команду модулю
  if (waiting) {                                // Если необходимо дождаться ответа...
    _resp = waitResponse();                     // ... ждем, когда будет передан ответ
    // Если Echo Mode выключен (ATE0), то эти 3 строки можно закомментировать
    if (_resp.startsWith(cmd)) {                // Убираем из ответа дублирующуюся команду
      _resp = _resp.substring(_resp.indexOf("\r\n", cmd.length()) + 2);
    }
    Serial.println(_resp);                      // Дублируем ответ в монитор порта
  }
  return _resp;                                 // Возвращаем результат. Пусто, если проблема
}

String waitResponse() {                         // Функция ожидания ответа и возврата полученного результата
  String _resp = "";                            // Переменная для хранения результата
  long _timeout = millis() + 10000;             // Переменная для отслеживания таймаута (10 секунд)
  while (!SIM800.available() && millis() < _timeout)  {}; // Ждем ответа 10 секунд, если пришел ответ или наступил таймаут, то...
  if (SIM800.available()) {                     // Если есть, что считывать...
    _resp = SIM800.readString();                // ... считываем и запоминаем
  }
  else {                                        // Если пришел таймаут, то...
    Serial.println("Timeout...");               // ... оповещаем об этом и...
  }
  return _resp;                                 // ... возвращаем результат. Пусто, если проблема
}

Он не заработал сходу и я стал пробовать читать разными способами.

а read() данные хоть и читает, но они недоступны. Я так понимаю он возвращает тип byte, т.е. бинарные данные как они есть. В случае перенаправления из порта в порт все происходит как надо. Но в моем случае нужна строка, а значит преобразовывать это дело в строку.  Я делаю _resp.concat ((char)modemPort->read()), количество байт в буфере уменьшается, но на выходе ничего.

qbit
Offline
Зарегистрирован: 18.03.2019

switch пишет:

...а read() данные хоть и читает, но они недоступны. Я так понимаю он возвращает тип byte, т.е. бинарные данные как они есть. В случае перенаправления из порта в порт все происходит как надо...

Это как так? Например, на запрос "AT\r\n" что получаешь - 4F 4B 0D 0A? 

b707
Offline
Зарегистрирован: 26.05.2017

qbit пишет:

Это как так? Например, на запрос "AT\r\n" что получаешь - 4F 4B 0D 0A? 

и что в этом удивительного? - это одно и то же

b707
Offline
Зарегистрирован: 26.05.2017

switch пишет:

а read() данные хоть и читает, но они недоступны. Я так понимаю он возвращает тип byte, т.е. бинарные данные как они есть. В случае перенаправления из порта в порт все происходит как надо. Но в моем случае нужна строка, а значит преобразовывать это дело в строку.  Я делаю _resp.concat ((char)modemPort->read()), количество байт в буфере уменьшается, но на выходе ничего.

Вам уже несколько раз сказали - бросьте String, . concat у всех работает, а у вас нет - вероятно проблема не в нем,

переходите на массивы символов (char*) Команда read() как раз читает из буфера один символ

qbit
Offline
Зарегистрирован: 18.03.2019

b707 пишет:

и что в этом удивительного? - это одно и то же

Видимо ТС этого не понимает.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

switch пишет:

В самом первом посте я перечислял как пробовал. За основу брал такой код из примеров:

Причём тут Ваш первый пост?

давайте восстановим последовательность. Я попросил Вас дать мне полный, короткий код, чтобы я мог увидеть проблему (посты #10 и #12). Вы мне его дали в посте #13. Никаких readString там не было и в помине.

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

А на что я тратил время? Если Вы считаете. что не троллите, значит издеваетесь, хрен редьки не слаще. Вы заставили меня запускать и отлаживать код, который Вам, оказывается нахрен не нужен!

Я сегодня чрезвычайно добрый и потому не буду Вас посылать сразу, а дам ещё один шанс. дайте мне, мля, тот код с которым у Вас проблема (тот самый, а не какой-то другой!!!). Постарайтесь сделать его как можно короче. Опишите в чём именно проблема, и я его посмотрю. Но если опять выяснится. что это не тот код, уж извините, Вы будете помещены в список идиотов, с которыми общаться нельзя.

switch
Offline
Зарегистрирован: 07.12.2015

ЕвгенийП, уважаемый, я Вас делать и отлаживать ничего не заставлял, это ваше решение. Я благодарен Вам за помощь, но совершенно не понимаю такого отношения ко мне. Если Вам угодно, можете смело добавлять меня в свой список идиотов. Но все же это технический форум, а не место выяснения отношений

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

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

b707
Offline
Зарегистрирован: 26.05.2017

switch пишет:

Я благодарен Вам за помощь, но совершенно не понимаю такого отношения ко мне.

А что там понимать-то? Вы просили помощи - вам сделали код, отладили его - а вы даже не удосужились его проверить.

А теперь имеете наглость снова задавать этот же вопрос?

switch
Offline
Зарегистрирован: 07.12.2015

ЕвгенийП пишет:

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

Код, который Вы просите в полном виде разбит на десяток файлов, там куча классов с взаимодействием между ними. Я урезал код по-максимуму и дал его Вам. У Вас он заработал. У меня - нет. Я не уверен что у вас такое же окружение. Такой же модем. Такой же контроллер и подключен так же. У меня он работает на внутреннем генератора 8 МГц.  Я не просил решать задачу за меня. Я просил указать направление, мысли, идеи, которых у местных завсегдатаев точно больше чем у меня. 

А про ваше потраченное время могу лишь напомнить: Вы делали это добровольно. Я не требовал. Обвинять меня в том что вы по своей воле потратили время по-моему как-то странно. Я вполне бы согласился оплатить Ваши консультации и помощь в разработке. При условии, конечно, большего взаимного уважения.

b707 пишет:

switch пишет:

Я благодарен Вам за помощь, но совершенно не понимаю такого отношения ко мне.

А что там понимать-то? Вы просили помощи - вам сделали код, отладили его - а вы даже не удосужились его проверить.

А теперь имеете наглость снова задавать этот же вопрос?

а вы, уважаемый, по теме вообще ничего не сказали. Я проверил код сегодня утром. Что не так?  Неужели вы настолько изголодались по ругани и спорам? К чему весь этот срач?

b707
Offline
Зарегистрирован: 26.05.2017

switch пишет:

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

зачем вам код с десятком исходных файлов, если у вас элементарное не работает? Напишите взаимодействие с Сериал коротким куском, строк в 20-30 - без всяких десяти уровней классов - и сначала отладте его. А уж потом добавляйте в свой мега-проект.

Именно такой код, самодостаточный и короткий - у вас и просили.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

switch пишет:
К чему весь этот срач?
Вот и я не понимаю. Вроде, во всём разобрались, разговор окончен. К чему был Ваш пост? ХЗ.