Добро пожаловать, Гость
Логин: Пароль: Запомнить меня

ТЕМА: Блок управления Пиролизным котлом на базе Arduino Mega 2560

Блок управления Пиролизным котлом на базе Arduino Mega 2560 11 март 2017 15:48 #1

  • glory24
  • glory24 аватар
  • Не в сети
  • new member
  • Сообщений: 1
  • Спасибо получено: 1
Имею такой вот блок управления Твердотопливным котлом.
В его состав входят Arduino Mega 2560 + Ethernet Shield W5100 +microSD
в общем все необходимо для организации Ардуино Мега Сервера
Кроме этого есть
Графический дисплей QC12864B
Усилитель термопары MAX31855
Датчик температуры DS18B20
4-канальный реле модуль
Как в данный момент функционирует блок можно почитать тут.
Данные с датчиков температуры блок отправляет сейчас на сторонний сервер
Требуется:
разобраться, как функционирует этот могучий инструмент (Ардуино Мега Сервер) и вывести показания с этих датчиков на AMS в виде аналогичных графиков с сохранением функционала блока, то есть не отправлять данные на xively.com, а формировать графики на SD карточке и просматривать их через браузер.
Я сам с этим справится не смог.
Скетч в виде кода
#include <SPI.h>
#include <Ethernet.h>
#include <HttpClient.h>
#include <Xively.h>
#include <OneWire.h>
#include "U8glib.h"
//+vasko 150224
#include "Adafruit_MAX31855.h"

// назначаем выходы Ардуино для взаимодействия с MAX31855
#define thermoDO  34
#define thermoCS  38
#define thermoCLK 36
Adafruit_MAX31855 thermocouple(thermoCLK, thermoCS, thermoDO);
//-vasko 150224

// закрепляем за оборудованием цифровые выходы ардуино 
#define nasosKranKotel 47     // Реле 1 - верхнее - НЦ1, КЭ1 47
#define nasosPotrebiteli 48   // Реле не присвоено - НЦ2, НЦ3, НЦ4 48
#define dymosos 44            // Реле 2 - второе сверху - Дымосос 44
#define kranTA 45             // Реле 3 - третье сверху - КЭ2 45
#define zaslonkaVozduha 46    // Реле 4 - нижнее - МЗ 46
//#define txa 8 // Аналоговый вход A8 ТХА (Датчик термопары дыма).

byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
//-----
IPAddress ip(192,168,0,20);
char xivelyKey[] = "yxLIRTV1LH6WAR52rvLSsXDidl7sGgN0efvah8hr0461";// Идентификатор на 
unsigned long feedId = 192768;// xively
//---------------------------------------------
//IPAddress ip(192,168,0,21);
//char xivelyKey[] = "8kSv3fJUP6SrrlEbCZvKZWI6NNCdbWAZj3PTTuiof9i";
//unsigned long feedId = 556923;
//----------------------------------------------------
// присваеваем типы значений числовым переменным
// присваеваем тип значения переменной для темп. сенсоров - char
char sensor1Id[] = "1_Dymovaya_Truba"; // ТХА
char sensor2Id[] = "2_Kotel_Vyhod";    // ДТ2
char sensor3Id[] = "3_Kotel_Obratka";  // ДТ3
char sensor4Id[] = "4_Verh_TA";        // ДТ4
char sensor5Id[] = "5_Niz_TA";         // ДТ5
char sensor0Id[] = "6_Test_Sensor";

XivelyDatastream datastreams[] = {
  XivelyDatastream(sensor0Id, strlen(sensor0Id), DATASTREAM_FLOAT),   
  XivelyDatastream(sensor1Id, strlen(sensor1Id), DATASTREAM_FLOAT),
  XivelyDatastream(sensor2Id, strlen(sensor2Id), DATASTREAM_FLOAT),
  XivelyDatastream(sensor3Id, strlen(sensor3Id), DATASTREAM_FLOAT),
  XivelyDatastream(sensor4Id, strlen(sensor4Id), DATASTREAM_FLOAT),
  XivelyDatastream(sensor5Id, strlen(sensor5Id), DATASTREAM_FLOAT),
};
// Finally, wrap the datastreams into a feed
XivelyFeed feed(feedId, datastreams, 6/* number of datastreams */);// назначаем выходы Ардуино для взаимодействия с дисплеем

EthernetClient client;
XivelyClient xivelyclient(client);

// назначаем выходы Ардуино для взаимодействия с дисплеем
U8GLIB_ST7920_128X64_1X u8g(24, 22, 23);	// SPI Com: SCK = en = 24, MOSI = rw = 22, CS = di = 23
// Кроме этого GND на (VSS, K(BLK) и PSB), +5В на (VDD и А(BLA))

//int tempCelsVal[6], tempCelsPre[6], txaCode;
// присваеваем тип значения переменной для термопары - int
int txaCode;
// присваеваем тип значения переменной для термопары - float
float tempCels[6], txaTemp;
// присваеваем тип значения переменной для описывающей состояние Отопительной системы - boolean
boolean reactorActive=0, accumulatorWarm=0, systemCRIHot=0, systemVeryHot=0, dymVeryCold=0, postplenieGar=0, sensorOnline[5];
// закрепляем входы Ардуино за датчиками температуры в СО
OneWire  ds1(28); // Неиспользуемый датчик 
OneWire  ds2(29); // Температура воды Котел подача  ДТ2
OneWire  ds3(30); // Температура воды Котел обратка ДТ3
OneWire  ds4(27); // Температура воды ТА Верхняя часть ДТ4
OneWire  ds5(31); // Температура воды ТА Нижняя  часть ДТ5
//----------------------------------------
// Графика дисплея
void draw(void) 
{
  u8g.setFont(u8g_font_freedoomr10r);
  //-------------------Котел Подача--------------
  u8g.drawFrame(0,15,33,48); // прямоугольник корпуса котла
  tempOutGraph(2,3,32); // Определяет положение индикации  ds2 "Температура воды Котел подача  ДТ2"
  u8g.drawVLine(25,0,15); // труба дымовая левая часть 
  u8g.drawVLine(29,0,15); // труба дымовая правая часть
  if (!digitalRead(zaslonkaVozduha)) 
  {
    u8g.drawLine(24,9,30,3);
  } // вывод изображения заслонки, если она включена (закрыта)

  //-------------------Аккумулятор Верх--------
  u8g.drawEllipse(67,32,4,10,U8G_DRAW_UPPER_LEFT|U8G_DRAW_LOWER_LEFT);
  u8g.drawEllipse(110,32,4,10);
  u8g.drawHLine(69,21,41); // верхняя линия
  u8g.drawHLine(69,42,41); // нижняя линия
  tempOutGraph(4,85,20); // Определяет положение индикации  ds4 "Температура воды ТА Верхняя часть ДТ4" 
  // (№ датчика темп.,x,y)
  //----------------Аккумулятор Низ----------
  tempOutGraph(5,85,59); // Определяет положение индикации  ds5 "Температура воды ТА Нижняя  часть ДТ5"
  //--------------------Трубы-------------

  u8g.drawHLine(33,17,16); // выход котла
  u8g.drawHLine(33,48,16); // вход котла
  u8g.drawFrame(49,0,79,64); // рамка СО
  u8g.drawVLine(78,42,22); // выход аккумулятора
  u8g.drawVLine(78,2,20); // вход аккумулятора 
  u8g.drawDisc(126,26,3); // насос система
  u8g.drawDisc(49,48,3); // насос обратка 
  //-------------
   tempOutGraph(3,3,62); // Определяет положение индикации  ds3  "Температура воды Котел обратка ДТ3"
  //--------------------------------------
   if (!digitalRead(nasosPotrebiteli)) // Если включен насос СО
  { // оператор (!) перд digitalRead обеспечивает правильное положенеи контактов рере соответствующее работе светодиода
    u8g.drawVLine(120,25,12); // стрелка после насоса системы (x,y,длина лин.)(119,37,12)
    u8g.drawLine(120,39,118,34);// наконечник стрелки (Х,У,х-коца линии наконечника,у-конец лин. нак.) (119,49,117,47)
    u8g.drawLine(120,39,122,34);
  }
    //---------------------------
  if (!digitalRead(nasosKranKotel)) // Если включены кран и насос Котла
  { // оператор (!) перд digitalRead обеспечивает правильное положенеи котактов рере соответствующее работе светодиода
    u8g.drawHLine(39,21,6); 
    u8g.drawVLine(44,21,10);  // обратка
    u8g.drawHLine(39,31,6);
    u8g.drawLine(39,31,41,29);
    u8g.drawLine(39,31,41,33); 
 
    u8g.drawVLine(46,3,12); // выход в систему
    u8g.drawLine(46,3,44,5);
    u8g.drawLine(46,3,48,5);
      //------------
    u8g.drawHLine(54,5,11); // стрелка перед краном ТА
    u8g.drawLine(68,5,63,3);
    u8g.drawLine(68,5,63,7);
      //------------
    u8g.drawHLine(54,58,12); // стрелка на вход в котел (x,y,длина линии) drawHLine-горизонтальная
    u8g.drawLine(51,58,55,56); // перед краном котла линия по 2-м точкам (х,у,х,у)
    u8g.drawLine(51,58,55,60);
  }
   //-------------
   if (digitalRead(nasosKranKotel))// Kран котла закрыт (Рисуем крестик на трубе)
  {  
    u8g.drawLine(40,46,36,50);// кран котла 1-я линия (Х,У,х-коца линии,у-конца линии)
    u8g.drawLine(40,50,36,46);// кран котла 2-я линия (Х,У,х-коца линии,у-конца линии)
  }   
      //-------------
   if (!digitalRead(kranTA) && !digitalRead(nasosKranKotel)) // Если открыт кран ТА и включены кран и насос Котла 
  { // рисуем Стрелку в ТА
    u8g.drawVLine(73,6,10); // стрелка ТА drawVLine-вертикальная
    u8g.drawLine(75,13,73,18);
    u8g.drawLine(71,13,73,18);// (Х,У,х-коца линии наконечника,у-конец лин. нак.)
  }  
  if (!digitalRead(kranTA) && digitalRead(nasosKranKotel))// Если открыт кран ТА и вЫключены кран и насос Котл
  { // рисуем Стрелку выход из ТА
    u8g.drawVLine(73,6,10); // стрелка ТА drawVLine-вертикальная
    u8g.drawLine(73,3,71,8);
    u8g.drawLine(73,3,75,8);// (Х,У,х-коца линии наконечника,у-конец лин. нак.)
  } 
   if (digitalRead(kranTA))// Если закрыт кран ТА
  {
    u8g.drawLine(80,7,76,11);// кран ТА 1-я линия (Х,У,х-коца линии,у-конца линии)
    u8g.drawLine(76,7,80,11);// кран ТА 2-я линия (Х,У,х-коца линии,у-конца линии)   
  }
  //-------------------------------------- 
  u8g.setPrintPos(0, 12); // Вывод показаний датчика 
  u8g.print((int)txaTemp); // температуры дыма
  
  //--------------------------------------   
}
//----------------------------------------
// Определение первоначальных состояний цифровым выходам  
void setup()
{ 
  pinMode(zaslonkaVozduha, OUTPUT); // Назначаем порт "Выходом"
  pinMode(kranTA, OUTPUT); // Назначаем порт "Выходом"
  //pinMode(nasosPotrebiteli, OUTPUT); // Назначаем порт "Выходом"
  pinMode(dymosos, OUTPUT); // Назначаем порт "Выходом"
  pinMode(nasosKranKotel, OUTPUT); // Назначаем порт "Выходом"
  digitalWrite(zaslonkaVozduha, HIGH); // Назначаем первичное состояние ячейки Воздушой заслонки "HIGH" - подача воздуха закрыта
  digitalWrite(kranTA, LOW); // Назначаем первичное состояние ячейки Крана ТА "HIGH"
  //digitalWrite(nasosPotrebiteli, HIGH); // Назначаем первичное состояние ячейки Насос потребители "HIGH"
  digitalWrite(dymosos, HIGH); // Назначаем первичное состояние ячейки Дымососа "HIGH"
  digitalWrite(nasosKranKotel, LOW); // Назначаем первичное состояние ячейки Насоса и крана котла "HIGH"
  u8g.setRot180();
  u8g.setColorIndex(1);         // pixel on
  Serial.begin(9600);
  Serial.println("Starting single datastream upload to Xively...");
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Ошибка DHCP, установка стандартного IP-адреса 
    Ethernet.begin(mac, ip);
  }
  Serial.print("IP address: ");
  for (byte thisByte = 0; thisByte < 4; thisByte++)
  {
    Serial.print(Ethernet.localIP()[thisByte], DEC);
    Serial.print(".");
  } 
  Serial.println();
  Serial.println();
} 
//----------------------------------------
//Циклически исполняемая программа
//----------------------------------------
void loop()
{
  //+vasko 150224
  //txaCode=analogRead(8); // считывание показаний ТХА (код от 0 до 1023, 4.9 мВ/ед)
  //txaTemp=txaCode*1.0+1; // вычисление температуры дыма и добавление примерной температуры холодного спая 1 гр.С
  txaTemp= thermocouple.readCelsius()+0.5;  // 0.5 поправочное значение
  //-vasko 150224

  tempCels[1] = txaTemp;
  sendToXively(1);
  secondSensorInquiry(2);// Считывает показания с sensor2Id[]
  temperaturePrint(2);// Пишет на дисплее показания sensor2Id[]
  sendToXively(2);// Отправляет на Xively показания sensor2Id[]
  thirdSensorInquiry(3);// Считывает показания с sensor3Id[]
  temperaturePrint(3);//// Пишет на дисплее показания sensor3Id[
  sendToXively(3);//Отправляет на Xively показания sensor3Id[]
  fourthSensorInquiry(4);// Считывает показания с sensor4Id[]
  temperaturePrint(4); // Пишет на дисплее показания sensor4Id[]
  sendToXively(4);//Отправляет на Xively показания sensor4Id[]
  fifthSensorInquiry(5);// Считывает показания с sensor5Id[] 
  temperaturePrint(5);// Пишет на дисплее показания sensor5Id[]
  sendToXively(5);//Отправляет на Xively показания sensor5Id[]
  Serial.print("Smoke temp:");
  Serial.println(txaTemp);
  //-----------------------------
  // Условия определяющие состояние Котла -reactorActive-
  //--------------------------------------
  if (tempCels[2] > 95 || txaTemp > 109){ // если температура воды
    reactorActive=1;//на выходе из котла более 95 градусов или дыма более 109 (105) градусов  
  } 
  if (tempCels[2] < 94 && txaTemp < 108){ // если  температура воды менее 94 и
    reactorActive=0;//  температура дыма менее 108 (100) град. С
  }  
  //-----------------------------
  // Условия определяющие состояние ТА при разборе с него тепла
  //-----------------------------------
  if (tempCels[4] > 28){ // есть ли в аккумуляторе тепло (больше чем 27С)
    accumulatorWarm=1;
  }
  if (tempCels[4] <28){ // есть ли в аккумуляторе холодно (мньше чем 27С)
    accumulatorWarm=0;
  }
  //-----------------------------
  // Условие определяющее состояние Заслонки Дыма при перегреве котла
  //-----------------------------
  if (tempCels[2] > 96){ // если котел нагрет, более 96 градусов
    systemCRIHot=1;
  }
  if (tempCels[2] < 95){ // если котел нагрет, менее 95 градусов
    systemCRIHot=0;
  }
  //-----------------------------
  // Условие определяющее подпитку Котла водой из ТА
  //-----------------------------
  if (tempCels[2] > 85){ // если температура котла более 85 градусов
    systemVeryHot=1;
  }
  if (tempCels[2] < 85){ // если температура котла меньше 84 градусов
    systemVeryHot=0;
  }
  //---------------------
  // Порядок работы СО в период работы котла (reactorActive)
  //----------------------
   if (reactorActive)
{
    on(nasosKranKotel);
    // Условие открытия кран ТА по перегреву
    if (systemVeryHot) 
    { 
      on(kranTA); // в случае нагрева котла до 85 градусов открыть кран подачи в ТА
    } 
    else 
    { 
      off(kranTA); 
    }
      // Условия работы дымососа
    if (txaTemp < 112) // Температура дыма меньше 112 градусов
    { 
      //on(zaslonkaVozduha);// Закроет заслонку дым трубы, 
      on(dymosos);// включит мотор дымососа;  
    } 
    if (txaTemp > 125) // Температура дыма больше 125 градусов
    { 
      off(dymosos);// остановит мотор дымососа
      //off(zaslonkaVozduha);// Откроет заслонку дым. трубы
    } 
      // Условие работы звслонки дыма и дымососа при перегреве котла выше 95 гр.
    if (systemCRIHot) // Если котел перегрет более 95 градусов
    { 
      on(zaslonkaVozduha);// Закроет заслонку дым трубы, остановить подачу воздуха в котел
      off(dymosos);// остановит мотор дымососа;
    } 
    else 
    {
     off (zaslonkaVozduha);// откроет подачу воздуха в котел
    }   
}
  else 
{
    off(nasosKranKotel); // Остановлен насос котла, закрыт кран котла
    on(zaslonkaVozduha); // остановить подачу воздуха в котел
    off(dymosos);
    if (accumulatorWarm) 
    { // если в аккумуляторе есть тепло
      on(kranTA); // обогрев  
      delay (1000); // от
      on(nasosPotrebiteli);  // ТА
    } 
    else 
    {
      off(nasosPotrebiteli);
      delay (1000);
      off(kranTA);
    }
}
  //-----------------------------
  u8g.firstPage();  
  do {
    draw();
  } 
  while( u8g.nextPage() );
  delay(100);// 300
  //-----------------------------
}
//-----------------------------
void sendToXively(byte sensorNumber) 
{
  datastreams[sensorNumber].setFloat(tempCels[sensorNumber]);
  Serial.print("Sensor ");
  Serial.print(sensorNumber);
  Serial.print(" value is: ");
  Serial.print(datastreams[sensorNumber].getFloat());
  Serial.print("  Sending...  ");
  int ret = xivelyclient.put(feed, xivelyKey);
  Serial.print("xivelyclient.put returned ");
  Serial.println(ret);
} 
//-----------------------------
/*void firstSensorInquiry(byte sensorNumber)
 {
 byte i;  
 byte ram[12];
 byte rom[8];
 float temp;
 int divider;   
 ds1.reset_search();
 if ( !ds1.search(rom)) {
 sensorOnline[sensorNumber] = 0;
 tempCels[sensorNumber]=0;}// если не включен- задает значение ноль
 else {
 sensorOnline[sensorNumber] = 1;    
 ds1.reset();
 ds1.select(rom);
 ds1.write(0x44, 1);     
 delay(900);
 ds1.reset();
 ds1.select(rom);    
 ds1.write(0xBE);
 for ( i = 0; i < 9; i++) {  
 ram[i] = ds1.read();}
 crcTest(rom, ram, sensorNumber);
 divider = sensorType(rom[0]);   
 temp=(ram[1]<<8)+ram[0];
 tempCels[sensorNumber] = temp/divider;
 }
 return;
 }  */
//-----------------------------
void secondSensorInquiry(byte sensorNumber)
{
  byte i;  
  byte ram[12];
  byte rom[8];
  float temp;
  int divider;  
  ds2.reset_search();
  if ( !ds2.search(rom)) {
    sensorOnline[sensorNumber] = 0;
    tempCels[sensorNumber]=0;
  }// если не включен- задает значение ноль
  else {
    sensorOnline[sensorNumber] = 1;    
    ds2.reset();
    ds2.select(rom);
    ds2.write(0x44, 1);     
    delay(800);// 900
    ds2.reset();
    ds2.select(rom);    
    ds2.write(0xBE);
    for ( i = 0; i < 9; i++) {  
      ram[i] = ds2.read();
    }
    crcTest(rom, ram, sensorNumber);      
    divider = sensorType(rom[0]);
    temp=(ram[1]<<8)+ram[0];
    tempCels[sensorNumber] = temp/divider;
  }
  return;
}  
//-----------------------------
void thirdSensorInquiry(byte sensorNumber)
{
  byte i;  
  byte ram[12];
  byte rom[8];
  float temp;
  int divider;   
  ds3.reset_search();
  if ( !ds3.search(rom)) {
    sensorOnline[sensorNumber] = 0;
    tempCels[sensorNumber]=0;
  }// если не включен- задает значение ноль
  else {
    sensorOnline[sensorNumber] = 1;    
    ds3.reset();
    ds3.select(rom);
    ds3.write(0x44, 1);     
    delay(800); // 900
    ds3.reset();
    ds3.select(rom);    
    ds3.write(0xBE);
    for ( i = 0; i < 9; i++) {  
      ram[i] = ds3.read();
    }
    crcTest(rom, ram, sensorNumber);
    divider = sensorType(rom[0]);   
    temp=(ram[1]<<8)+ram[0];
    tempCels[sensorNumber] = temp/divider;
  }
  return;
}  
//-----------------------------
void fourthSensorInquiry(byte sensorNumber)
{
  byte i;  
  byte ram[12];
  byte rom[8];
  float temp;
  int divider;   
  ds4.reset_search();
  if ( !ds4.search(rom)) {
    sensorOnline[sensorNumber] = 0;
    tempCels[sensorNumber]=0;
  }// если не включен- задает значение ноль
  else {
    sensorOnline[sensorNumber] = 1;    
    ds4.reset();
    ds4.select(rom);
    ds4.write(0x44, 1);     
    delay(800); // 900
    ds4.reset();
    ds4.select(rom);    
    ds4.write(0xBE);
    for ( i = 0; i < 9; i++) {  
      ram[i] = ds4.read();
    }
    crcTest(rom, ram, sensorNumber); 
    divider = sensorType(rom[0]);   
    temp=(ram[1]<<8)+ram[0];
    tempCels[sensorNumber] = temp/divider;
  }
  return;
}
//-----------------------------
void fifthSensorInquiry(byte sensorNumber)
{
  byte i;  
  byte ram[12];
  byte rom[8];
  float temp;
  int divider;   
  ds5.reset_search();
  if ( !ds5.search(rom)) {
    sensorOnline[sensorNumber] = 0;
    tempCels[sensorNumber]=0;
  } // если не включен- задает значение ноль
  else {
    sensorOnline[sensorNumber] = 1;    
    ds5.reset();
    ds5.select(rom);
    ds5.write(0x44, 1);     
    delay(800); //900
    ds5.reset();
    ds5.select(rom);    
    ds5.write(0xBE);
    for ( i = 0; i < 9; i++) {  
      ram[i] = ds5.read();
    }
    crcTest(rom, ram, sensorNumber);
    divider = sensorType(rom[0]);   
    temp=(ram[1]<<8)+ram[0];
    tempCels[sensorNumber] = temp/divider;
  }
  return;
}  
//-----------------------------
void crcTest(byte romCode[8], byte ramCode[12], byte sensorNumber)
{
  if ( OneWire::crc8(romCode, 7) == romCode[7] && OneWire::crc8(ramCode, 8)== ramCode[8] ){
    Serial.print("CRC-");
    Serial.print(sensorNumber);
    Serial.print(" OK.");
    return;
  }
  else {   
    Serial.print("CRC-");
    Serial.print(sensorNumber);
    Serial.print(" ERROR!");
    sensorOnline[sensorNumber] = 0; 
    return;
  }   
}
//-----------------------------
int sensorType(byte serCode)
{
  if ( serCode == 0x10) {
    Serial.print("Sensor is 18S20.");
    return 2;
  }
  if ( serCode == 0x28) {
    Serial.print("Sensor is 18B20.");
    return 16;
  }
}  
//----------------------------- 
void temperaturePrint(int sensorNumber)
{
  if (sensorOnline[sensorNumber]) {  
    Serial.print("Temp ");
    Serial.print(sensorNumber);
    Serial.print(" is ");
    Serial.print(tempCels[sensorNumber],2);
    Serial.print("C.\n");
  } 
  else { 
    Serial.print("Sensor ");
    Serial.print(sensorNumber);
    Serial.print(" not connected\n");
    delay (200); // 200
  }
  return;
}  
//----------------------------- 
void on (int relay)
{
  digitalWrite(relay, LOW);  
}
//----------------------------- 
void off (int relay)
{
  digitalWrite(relay, HIGH);  
}
//----------------------------- 
void tempOutGraph(int sensor, int x, int y)
{
  if (sensorOnline[sensor]){
    u8g.setPrintPos(x, y);
    u8g.print(tempCels[sensor],1);
  }
  else {
    u8g.drawStr(x+7, y, "NC");
  }  
}  
.
Так как он слишком длинный, поэтому креплю его и

библиотеки

в виде .zip
Последнее редактирование: 11 март 2017 15:53 от glory24.
Администратор запретил публиковать записи гостям.
Спасибо сказали: sergey380
Модераторы: wolf2000, Vakula, Айнцвайдрайченко