25 ноября 2014 в 02:26

Контроль температуры в серверных шкафах с помощью Ардуино

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

Серверная на станции где я работаю, как и положено находится в отдельном помещении. Для охлаждения серверов там установлено два кондиционера. К сожалению это обычные бытовые модели, так что регулярно то один, то другой находятся в ремонте. Руководство отдела, зная о моей работе над проектом FLProg, предложило создать систему мониторинга температуры в серверных шкафах, и управления работой их вентиляторов. Информация о текущей температуре должна отображаться на табло в помещении дежурных инженеров, и в случае повышения температуры свыше определенного порога выдавать звуковую сигнализацию. Я взялся за эту работу, и вот что получилось…

Ситуация осложнялась тем что мы находимся где то в 200 километров от ближайшей цивилизации, и собирать систему пришлось из тех запасов что у меня были с собой, плюс что то из старого хлама из кладовок. В наличии у меня были:

1. Arduino Nano
2. Датчик температуры DS18B20
3. Два датчика температуры и влажности DHT-22
4. Четырехстрочный дисплей на 20 символов в строке с платой I2C

Была разработана схема устройства: Принципиальная схема

Потом разработаны печатные платы:


Основная плата в программе Sprint-Layout_6


Кнопочная плата в программе Sprint-Layout_6

Утюга и специальной бумаги для технологии ЛУТ не нашлось так что пришлось вспоминать детство и рисовать платы ручками лаком, выпрошенным у уборщицы.






После первоначальной сборки и заливки пробного скетча возникли первые проблемы. Во первых, реле отказались срабатывать, хотя на катушки приходило 5 вольт. Перед установкой на плату я естественно проверял сопротивление катушек и подавал на них питание с проверкой сработки. Оказалось что реле очень хитрое. Хотя внутри и нет диодов (в обе стороны катушки прозваниваются одинаково), для них играет роль полярность питания на катушках. Скорее всего, у них подмагниченный якорь для снижения тока срабатывания. Из-за этого, релюшки переехали на другую сторону платы. После переезда релюшек все нормально заработало.




Затем была разработана окончательная прошивка для платы.

Проект в программе FLProg.

Скетчь
#include <Wire.h>
#include <OneWire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include "DHT.h"
LiquidCrystal_I2C _lcd1(0x3F, 20, 4);
int _dispTempLength1=0;
boolean _isNeedClearDisp1;
byte _d18x2x1Addr[8]={0x28, 0xFF, 0x11, 0x94, 0x3C, 0x4, 0x0, 0x48};
DHT _dht1(11, DHT22);
OneWire  _ow10(10);
bool _gtv1 = 0;
bool _gtv3 = 0;
bool _gtv4 = 0;
bool _gtv5 = 0;
int _gtv2 = 23;
int _gtv6 = 27;
int _gtv7 = 30;
int _gtv8 = 35;
int _gtv9 = 0;
bool _gtv10 = 0;
String _gtv11 = "_C";
bool _gtv12 = 0;
bool _gtv13 = 0;
float _gtv14 = 0;
float _gtv15 = 0;
bool _gtv16 = 0;
int _gtv17 = 10;
bool _gtv18 = 0;
bool _gtv19 = 0;
bool _gtv20 = 0;
bool _gtv21 = 0;
bool _gtv22 = 0;
bool _gtv23 = 0;
bool _gtv24 = 0;
bool _gtv25 = 0;
bool _gtv26 = 0;
bool _gtv27 = 0;
bool _gtv28 = 0;
bool _trgrt9 = 0;
bool _trgrt9I = 0;
bool _bounse1S = 0;
bool _bounse1O = 0;
unsigned long _bounse1P = 0UL;
bool _trgrt4 = 0;
bool _trgrt4I = 0;
bool _bounse2S = 0;
bool _bounse2O = 0;
unsigned long _bounse2P = 0UL;
bool _trgrt5 = 0;
bool _trgrt5I = 0;
bool _bounse3S = 0;
bool _bounse3O = 0;
unsigned long _bounse3P = 0UL;
bool _trgrt8 = 0;
bool _trgrt8I = 0;
bool _count1I = 0;
int _count1P = 0;
bool _tim1I = 0;
bool _tim1O = 0;
unsigned long _tim1P = 0UL;
bool _trgrt1 = 0;
bool _trgrt1I = 0;
bool _trgrt2 = 0;
bool _trgrt2I = 0;
bool _trgrt3 = 0;
bool _trgrt3I = 0;
int _disp1oldLength = 0;
String _mux1;
int _disp3oldLength = 0;
int _disp4oldLength = 0;
int _disp5oldLength = 0;
int _disp6oldLength = 0;
int _disp7oldLength = 0;
String _swi2;
String _swi3;
int _disp2oldLength = 0;
String _mux2;
int _disp8oldLength = 0;
int _disp9oldLength = 0;
int _disp10oldLength = 0;
int _disp11oldLength = 0;
int _disp12oldLength = 0;
String _swi4;
String _swi5;
bool _trgrt10 = 0;
bool _trgrt10I = 0;
bool _trgrt11 = 0;
bool _trgrt11I = 0;
bool _trgrt12 = 0;
bool _trgrt12I = 0;
bool _trgrt13 = 0;
bool _trgrt13I = 0;
unsigned long _d18x2x1Tti = 0UL;
float _d18x2x1O = 0.00;
unsigned long _dht1Tti = 0UL;
float _dht1t = 0.00;
float _dht1h = 0.00;
bool _trgr1 = 0;
bool _trgr2 = 0;
bool _trgrt6 = 0;
bool _trgrt6I = 0;
bool _trgr3 = 0;
bool _trgr4 = 0;
bool _trgrt7 = 0;
bool _trgrt7I = 0;
bool _trgr5 = 0;
bool _pzs1OES = 0;
int _pzs1OFS = 0;
bool _gen1I = 0;
bool _gen1O = 0;
unsigned long _gen1P = 0UL;
int _swi1;
bool _D1B1 = 0;
bool _gen2I = 0;
bool _gen2O = 0;
unsigned long _gen2P = 0UL;
String _swi6;
int _disp13oldLength = 0;
bool _SEEPR1OSN = 0;
bool _SEEPR2OSN = 0;
bool _SEEPR3OSN = 0;
bool _SEEPR4OSN = 0;
void setup()
{
Wire.begin();
pinMode(12, OUTPUT);
pinMode(4, OUTPUT);
pinMode(3, OUTPUT);
pinMode(2, OUTPUT);

_lcd1.init();
_lcd1.noBacklight();
_dht1.begin();
}
void loop()
{if(_isTimer(_dht1Tti, 6000)) {
_dht1Tti = millis(); 
float tempDht11;
 tempDht11 = _dht1.readTemperature();
if (!(isnan(tempDht11))){_dht1t=tempDht11; }
}if (_isNeedClearDisp1) {_lcd1.clear(); _isNeedClearDisp1= 0;}




if (1) { if (_trgrt9I) { _trgrt9 = 0;} else {_trgrt9 = 1; _trgrt9I = 1;} } else {_trgrt9 = 0; _trgrt9I = 0;}; 
 if (_trgrt9) {
_gtv6 = (EEPROMReadInt(0));
}
if (_trgrt9) {
_gtv7 = (EEPROMReadInt(2));
}
if (_trgrt9) {
_gtv2 = (EEPROMReadInt(4));
}
if (_trgrt9) {
_gtv8 = (EEPROMReadInt(6));
}
bool  _bounceTmp1 = ! (( (analogRead (3))) > (500));

if (_bounse1S) 
    {
     if (millis() >= (_bounse1P + 40)) 
         {_bounse1O= _bounceTmp1; _bounse1S=0;}
     }
else
    {
     if (_bounceTmp1 != _bounse1O )
         {_bounse1S=1; _bounse1P = millis();} 
      } 
if (_bounse1O) { if (_trgrt4I) { _trgrt4 = 0;} else {_trgrt4 = 1; _trgrt4I = 1;} } else {_trgrt4 = 0; _trgrt4I = 0;}; 
 _gtv3 = _bounse1O;
_gtv12 =  (_trgrt4) && (!(_gtv20)) ;
bool  _bounceTmp2 = ! (( (analogRead (2))) > (500));

if (_bounse2S) 
    {
     if (millis() >= (_bounse2P + 40)) 
         {_bounse2O= _bounceTmp2; _bounse2S=0;}
     }
else
    {
     if (_bounceTmp2 != _bounse2O )
         {_bounse2S=1; _bounse2P = millis();} 
      } 
if (_bounse2O) { if (_trgrt5I) { _trgrt5 = 0;} else {_trgrt5 = 1; _trgrt5I = 1;} } else {_trgrt5 = 0; _trgrt5I = 0;}; 
 _gtv4 = _bounse2O;
_gtv13 =  (_trgrt5) && (!(_gtv20)) ;
bool  _bounceTmp3 = ! (( (analogRead (1))) > (500));

if (_bounse3S) 
    {
     if (millis() >= (_bounse3P + 40)) 
         {_bounse3O= _bounceTmp3; _bounse3S=0;}
     }
else
    {
     if (_bounceTmp3 != _bounse3O )
         {_bounse3S=1; _bounse3P = millis();} 
      } 
if (_bounse3O) { if (_trgrt8I) { _trgrt8 = 0;} else {_trgrt8 = 1; _trgrt8I = 1;} } else {_trgrt8 = 0; _trgrt8I = 0;}; 
 _gtv5 = _bounse3O;
_gtv19 =  (_trgrt8) && (!(_gtv20)) ;

if (_gtv19) 
   { 
   if (! _count1I)  
      {
       _count1P = _count1P+1;
       _count1I = 1;
      }
   }
else
   {
   _count1I=0;
   }
if (_count1P < 0 ) _count1P = 0; 
if ( (_gtv1) || (_gtv10) ) _count1P = 0;
if (_gtv5) { if (_trgrt1I) { _trgrt1 = 0;} else {_trgrt1 = 1; _trgrt1I = 1;} } else {_trgrt1 = 0; _trgrt1I = 0;}; 
 if (_gtv3) { if (_trgrt2I) { _trgrt2 = 0;} else {_trgrt2 = 1; _trgrt2I = 1;} } else {_trgrt2 = 0; _trgrt2I = 0;}; 
 if (_gtv4) { if (_trgrt3I) { _trgrt3 = 0;} else {_trgrt3 = 1; _trgrt3I = 1;} } else {_trgrt3 = 0; _trgrt3I = 0;}; 
 if ( ((_count1P) > (0)) && ((!  (_trgrt1) || (_trgrt2) || (_trgrt3) )) )
{
if (_tim1I)
{
if ( _isTimer(_tim1P, 60000)) _tim1O = 1;
}
else
{
_tim1I = 1;
_tim1P = millis();
}
}
else
{
_tim1O = 0; 
_tim1I = 0;
}
_gtv9 = _count1P;
_gtv1 =  _count1P  >=  5;
_gtv10 = _tim1O;
if((_gtv9) == 0) {_mux1 = String("Terst");}
if((_gtv9) == 1) {_mux1 = String("Value Off");}
if((_gtv9) == 2) {_mux1 = String("Value 1 On");}
if((_gtv9) == 3) {_mux1 = String("Value 2 On");}
if((_gtv9) == 4) {_mux1 = String("Value Alarm");}
if ((_gtv9) > (0)) {
_dispTempLength1 = ((_mux1)).length();
if (_disp1oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp1oldLength = _dispTempLength1;
_lcd1.setCursor(int((20 - _dispTempLength1)/2), 0);
_lcd1.print((_mux1));
} else {
if (_disp1oldLength > 0) {_isNeedClearDisp1 = 1; _disp1oldLength = 0;} 
}
if ((0) == (_gtv9)) {
_dispTempLength1 = (String("SU T:")).length();
if (_disp3oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp3oldLength = _dispTempLength1;
_lcd1.setCursor(0, 0);
_lcd1.print(String("SU T:"));
} else {
if (_disp3oldLength > 0) {_isNeedClearDisp1 = 1; _disp3oldLength = 0;} 
}
if ((0) == (_gtv9)) {
_dispTempLength1 = ((( _floatToStringWitRaz(_gtv14,1)))).length();
if (_disp4oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp4oldLength = _dispTempLength1;
_lcd1.setCursor(5, 0);
_lcd1.print((( _floatToStringWitRaz(_gtv14,1))));
} else {
if (_disp4oldLength > 0) {_isNeedClearDisp1 = 1; _disp4oldLength = 0;} 
}
if ((0) == (_gtv9)) {
_dispTempLength1 = (String("C")).length();
if (_disp5oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp5oldLength = _dispTempLength1;
_lcd1.setCursor(9, 0);
_lcd1.print(String("C"));
} else {
if (_disp5oldLength > 0) {_isNeedClearDisp1 = 1; _disp5oldLength = 0;} 
}
if(_gtv21)
{_swi2=String("*");}
else
{_swi2=String("-");}
if ((0) == (_gtv9)) {
_dispTempLength1 = ((_swi2)).length();
if (_disp6oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp6oldLength = _dispTempLength1;
_lcd1.setCursor(11, 0);
_lcd1.print((_swi2));
} else {
if (_disp6oldLength > 0) {_isNeedClearDisp1 = 1; _disp6oldLength = 0;} 
}
if(_gtv22)
{_swi3=String("*");}
else
{_swi3=String("-");}
if ((0) == (_gtv9)) {
_dispTempLength1 = ((_swi3)).length();
if (_disp7oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp7oldLength = _dispTempLength1;
_lcd1.setCursor(13, 0);
_lcd1.print((_swi3));
} else {
if (_disp7oldLength > 0) {_isNeedClearDisp1 = 1; _disp7oldLength = 0;} 
}
if((_gtv9) == 0) {_mux2 = String("test");}
if((_gtv9) == 1) {_mux2 = (((String(_gtv2))) + (_gtv11));}
if((_gtv9) == 2) {_mux2 = (((String(_gtv6))) + (_gtv11));}
if((_gtv9) == 3) {_mux2 = (((String(_gtv7))) + (_gtv11));}
if((_gtv9) == 4) {_mux2 = (((String(_gtv8))) + (_gtv11));}
if ((_gtv9) > (0)) {
_dispTempLength1 = ((_mux2)).length();
if (_disp2oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp2oldLength = _dispTempLength1;
_lcd1.setCursor(int((20 - _dispTempLength1)/2), 1);
_lcd1.print((_mux2));
} else {
if (_disp2oldLength > 0) {_isNeedClearDisp1 = 1; _disp2oldLength = 0;} 
}
if ((0) == (_gtv9)) {
_dispTempLength1 = (String("CU T:")).length();
if (_disp8oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp8oldLength = _dispTempLength1;
_lcd1.setCursor(0, 1);
_lcd1.print(String("CU T:"));
} else {
if (_disp8oldLength > 0) {_isNeedClearDisp1 = 1; _disp8oldLength = 0;} 
}
if ((0) == (_gtv9)) {
_dispTempLength1 = ((( _floatToStringWitRaz(_gtv15,1)))).length();
if (_disp9oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp9oldLength = _dispTempLength1;
_lcd1.setCursor(5, 1);
_lcd1.print((( _floatToStringWitRaz(_gtv15,1))));
} else {
if (_disp9oldLength > 0) {_isNeedClearDisp1 = 1; _disp9oldLength = 0;} 
}
if ((0) == (_gtv9)) {
_dispTempLength1 = (String("C")).length();
if (_disp10oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp10oldLength = _dispTempLength1;
_lcd1.setCursor(9, 1);
_lcd1.print(String("C"));
} else {
if (_disp10oldLength > 0) {_isNeedClearDisp1 = 1; _disp10oldLength = 0;} 
}
if(_gtv23)
{_swi4=String("*");}
else
{_swi4=String("-");}
if ((0) == (_gtv9)) {
_dispTempLength1 = ((_swi4)).length();
if (_disp11oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp11oldLength = _dispTempLength1;
_lcd1.setCursor(11, 1);
_lcd1.print((_swi4));
} else {
if (_disp11oldLength > 0) {_isNeedClearDisp1 = 1; _disp11oldLength = 0;} 
}
if(_gtv24)
{_swi5=String("*");}
else
{_swi5=String("-");}
if ((0) == (_gtv9)) {
_dispTempLength1 = ((_swi5)).length();
if (_disp12oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp12oldLength = _dispTempLength1;
_lcd1.setCursor(13, 1);
_lcd1.print((_swi5));
} else {
if (_disp12oldLength > 0) {_isNeedClearDisp1 = 1; _disp12oldLength = 0;} 
}
if ( (_gtv5) && (_gtv3) && (_gtv4) ) { if (_trgrt10I) { _trgrt10 = 0;} else {_trgrt10 = 1; _trgrt10I = 1;} } else {_trgrt10 = 0; _trgrt10I = 0;}; 
 if ( (_gtv12) && ((_gtv9) == (1)) ) {
_gtv2 = (_gtv2)+(1);
}
if ( ((_gtv9) == (1)) && (_gtv13) ) {
_gtv2 = (_gtv2)-(1);
}
if (_trgrt10) {
_gtv2 = 25;
}
_gtv25 =  ( (_gtv12) && ((_gtv9) == (1)) ) || ( ((_gtv9) == (1)) && (_gtv13) ) || (_trgrt10) ;
if ( (_gtv5) && (_gtv3) && (_gtv4) ) { if (_trgrt11I) { _trgrt11 = 0;} else {_trgrt11 = 1; _trgrt11I = 1;} } else {_trgrt11 = 0; _trgrt11I = 0;}; 
 if ( (_gtv12) && ((_gtv9) == (2)) ) {
_gtv6 = (_gtv6)+(1);
}
if ( ((_gtv9) == (2)) && (_gtv13) ) {
_gtv6 = (_gtv6)-(1);
}
if (_trgrt11) {
_gtv6 = 27;
}
_gtv26 =  ( (_gtv12) && ((_gtv9) == (2)) ) || ( ((_gtv9) == (2)) && (_gtv13) ) || (_trgrt11) ;
if ( (_gtv5) && (_gtv3) && (_gtv4) ) { if (_trgrt12I) { _trgrt12 = 0;} else {_trgrt12 = 1; _trgrt12I = 1;} } else {_trgrt12 = 0; _trgrt12I = 0;}; 
 if ( (_gtv12) && ((_gtv9) == (3)) ) {
_gtv7 = (_gtv7)+(1);
}
if ( ((_gtv9) == (3)) && (_gtv13) ) {
_gtv7 = (_gtv7)-(1);
}
if (_trgrt12) {
_gtv7 = 30;
}
_gtv27 =  ( (_gtv12) && ((_gtv9) == (3)) ) || ( ((_gtv9) == (3)) && (_gtv13) ) || (_trgrt12) ;
if ( (_gtv5) && (_gtv3) && (_gtv4) ) { if (_trgrt13I) { _trgrt13 = 0;} else {_trgrt13 = 1; _trgrt13I = 1;} } else {_trgrt13 = 0; _trgrt13I = 0;}; 
 if ( (_gtv12) && ((_gtv9) == (4)) ) {
_gtv8 = (_gtv8)+(1);
}
if ( ((_gtv9) == (4)) && (_gtv13) ) {
_gtv8 = (_gtv8)-(1);
}
if (_trgrt13) {
_gtv8 = 35;
}
_gtv28 =  ( (_gtv12) && ((_gtv9) == (4)) ) || ( ((_gtv9) == (4)) && (_gtv13) ) || (_trgrt13) ;
if(_isTimer(_d18x2x1Tti, 5000)) {
_d18x2x1Tti = millis(); 
_d18x2x1O=  _readDS18_ow10(_d18x2x1Addr, 0);}
_gtv14 = (_d18x2x1O);
_gtv15 = _dht1t;
if(((int((_gtv14)*(_gtv17)))) < ((_gtv2)*(_gtv17))) _trgr1 = 0;
if(((int((_gtv14)*(_gtv17)))) > ((_gtv6)*(_gtv17))) _trgr1 = 1;
if(((int((_gtv14)*(_gtv17)))) < ((_gtv2)*(_gtv17))) _trgr2 = 0;
if(((int((_gtv14)*(_gtv17)))) > ((_gtv7)*(_gtv17))) _trgr2 = 1;
if (((int((_gtv14)*(_gtv17)))) > ((_gtv17)*(_gtv8))) { if (_trgrt6I) { _trgrt6 = 0;} else {_trgrt6 = 1; _trgrt6I = 1;} } else {_trgrt6 = 0; _trgrt6I = 0;}; 
 if(((int((_gtv17)*(_gtv15)))) < ((_gtv2)*(_gtv17))) _trgr3 = 0;
if(((int((_gtv17)*(_gtv15)))) > ((_gtv6)*(_gtv17))) _trgr3 = 1;
if(((int((_gtv17)*(_gtv15)))) < ((_gtv2)*(_gtv17))) _trgr4 = 0;
if(((int((_gtv17)*(_gtv15)))) > ((_gtv7)*(_gtv17))) _trgr4 = 1;
if (((int((_gtv17)*(_gtv15)))) > ((_gtv17)*(_gtv8))) { if (_trgrt7I) { _trgrt7 = 0;} else {_trgrt7 = 1; _trgrt7I = 1;} } else {_trgrt7 = 0; _trgrt7I = 0;}; 
 _gtv16 = _trgrt6;
_gtv21 = _trgr1;
digitalWrite(12, _trgr1);
digitalWrite(3, _trgr3);
_gtv18 = _trgrt7;
_gtv23 = _trgr3;
_gtv22 = _trgr2;
digitalWrite(4, _trgr2);
digitalWrite(2, _trgr4);
_gtv24 = _trgr4;
if( (_gtv5) || (_gtv3) || (_gtv4) ) _trgr5 = 0;
if( (_gtv16) || (_gtv18) ) _trgr5 = 1;

if (_trgr5) 
   {
   if (! _gen1I)
      {
      _gen1I = 1;
      _gen1O = 1;
      _gen1P = millis();
      }
   }
else
   {
   _gen1I = 0 ;
   _gen1O= 0;
   }
if (_gen1I )
   {
   if ( _isTimer ( _gen1P , 500 ))
      {
      _gen1P = millis();
      _gen1O = ! _gen1O;
      }
   }  
if( (!(_trgr5)) || (_gen1O) ) {if(! _D1B1){_lcd1.backlight(); _D1B1=1; }} else {if(_D1B1){_lcd1.noBacklight(); _D1B1=0; }}
if(_gen1O)
{_swi1=3000;}
else
{_swi1=2500;}
if(_trgr5)
{if((!_pzs1OES) || (_swi1 != _pzs1OFS)){ tone(13, (_swi1)); _pzs1OES =1;_pzs1OFS = _swi1;}} else {if(_pzs1OES){noTone(13); _pzs1OES =0;  }}
_gtv20 = _trgr5;

if (1) 
   {
   if (! _gen2I)
      {
      _gen2I = 1;
      _gen2O = 1;
      _gen2P = millis();
      }
   }
else
   {
   _gen2I = 0 ;
   _gen2O= 0;
   }
if (_gen2I )
   {
   if ( _isTimer ( _gen2P , 1000 ))
      {
      _gen2P = millis();
      _gen2O = ! _gen2O;
      }
   }  
if(_gen2O)
{_swi6=String("----");}
else
{_swi6=String("****");}
if (1) {
_dispTempLength1 = ((_swi6)).length();
if (_disp13oldLength > _dispTempLength1) {_isNeedClearDisp1 = 1;} 
_disp13oldLength = _dispTempLength1;
_lcd1.setCursor(int((20 - _dispTempLength1)/2), 2);
_lcd1.print((_swi6));
} else {
if (_disp13oldLength > 0) {_isNeedClearDisp1 = 1; _disp13oldLength = 0;} 
}
if(_gtv26){ if(!_SEEPR1OSN){ EEPROMWriteInt(0, _gtv6); _SEEPR1OSN=1;} }else{ if(_SEEPR1OSN){_SEEPR1OSN=0;}}
if(_gtv27){ if(!_SEEPR2OSN){ EEPROMWriteInt(2, _gtv7); _SEEPR2OSN=1;} }else{ if(_SEEPR2OSN){_SEEPR2OSN=0;}}
if(_gtv25){ if(!_SEEPR3OSN){ EEPROMWriteInt(4, _gtv2); _SEEPR3OSN=1;} }else{ if(_SEEPR3OSN){_SEEPR3OSN=0;}}
if(_gtv28){ if(!_SEEPR4OSN){ EEPROMWriteInt(6, _gtv8); _SEEPR4OSN=1;} }else{ if(_SEEPR4OSN){_SEEPR4OSN=0;}}



}
bool _isTimer(unsigned long startTime, unsigned long period )
  {
  unsigned long endTime;
  endTime = startTime+period;
  return (millis() >= endTime);
  }
String  _floatToStringWitRaz(float value, int raz)
{
  float tv;
  int ti = int(value);
  String ts = String(ti);
  if (raz == 0) {
    return ts;
  }
  ts += ".";
  float tf = abs(value - ti);
  for (int i = 1; i <= raz; i++ )
  {
    tv = tf * 10;
    ti = int(tv);
    ts += String(ti);
    tf = (tv - ti);
  }
  return ts;
}
float _convertDS18x2xData(byte type_s, byte data[12])
{
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s) 
  {
  raw = raw << 3; 
  if (data[7] == 0x10) { raw = (raw & 0xFFF0) + 12 - data[6]; }
  }
else 
  {
   byte cfg = (data[4] & 0x60);
   if (cfg == 0x00) raw = raw & ~7;   else if (cfg == 0x20) raw = raw & ~3;  else if (cfg == 0x40) raw = raw & ~1;
  }
return  (float)raw / 16.0;
}
float _readDS18_ow10(byte addr[8], byte type_s)
{  byte data[12];
byte i;
_ow10.reset();
_ow10.select(addr);
_ow10.write(0xBE);
for ( i = 0; i < 9; i++) {
 data[i] = _ow10.read();}
_ow10.reset();
_ow10.select(addr);
_ow10.write(0x44, 1);
return _convertDS18x2xData(type_s, data);}int EEPROMReadInt(int p_address)
        {
        byte lowByte = EEPROM.read(p_address);
        byte highByte = EEPROM.read(p_address + 1);

        return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
        }
void EEPROMWriteInt(int p_address, int p_value)
       {
        byte lowByte = ((p_value >> 0) & 0xFF);
        byte highByte = ((p_value >> 8) & 0xFF);

        EEPROM.write(p_address, lowByte);
        EEPROM.write(p_address + 1, highByte);
        }



Как работает система.
При подаче питания на контроллер, из EEPROM вычитываются уставки порогов включения ступеней вентиляторов шкафов, и аварийной сигнализации.
Каждые 5 секунд считываются данные из датчиков и отображаются на дисплее. При превышении температуры в шкафу первого порога включается первый вентилятор, второго порога – второй, а при превышении порога аварийной температуры включается двухтональная звуковая сигнализация и начинает моргать подсветка дисплея. Квитирование аварии происходит при нажатии любой из кнопок. При падении температуры ниже уставки отключения вентиляторов оба вентилятора останавливаются.
При каждом нажатии кнопки “Меню” на экран дисплея выводится последовательно все уставки. Каждую уставку можно изменить кнопками “Прибавить” и “Убавить”. При этом новое значение уставки сразу записывается в EEPROM.
Если в течение 1 минуты не производить никаких действий с кнопками происходит переход на основной экран индикации температуры
При нажатии сразу трех кнопок все уставки сбрасываются на начальные значения, записанные в программе. Это необходимо при первом запуске контроллера, когда в EEPROM находятся непредсказуемые значения. У меня, например, аварийная температура оказалась 387 градусов, и я бы очень устал ее сваливать до 35 градусов нажатиями на кнопку “Убавить”.
Запуск системы на столе и прогрев датчиков феном показал работоспособность программы.



Были установлены датчики и проложены кабеля до шкафов






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



Прошу не обращать внимание на оригинальное крепление дисплея, но я посчитал слишком жирно ставить четырехстрочный дисплей на постоянную основу, и на следующей вахте заменю его на двухстрочный.
На текущий момент система работает третью неделю без зависанй и отказов 24/7.
Теперь немного гик-порно. Я не удержался и распотрошил сгоревший датчик DHT-22. Внутри он оказался очень умным.


Автор: @totuin
FLProg
рейтинг 23,61
Графическая среда программирования Arduino

Комментарии (16)

  • +1
    У меня всегда вызывают неприятные ощущения читатели которые минусуют без комментариев. Как то трусовато получается. Вроде подбежал, сделал бяку и спрятался. А в лицо сказать что не нравится — страшно.
    • +4
      Хотите критики? Их есть у меня:
      1. Ваша статья — не менее чем четвертая на избитую тему:
        Каким образом у вас поддерживается климат в серверной?
        Измеряем температуру: TEMPer + Python + Windows
        Мониторинг температуры гермозоны с помощью 1-wire датчиков и Zabbix 2
      2. На фотографиях мы видим страшненький монтаж (на который нас просят не обращать внимания). На фоне ставшей уже антиквариатом Cisco 2960 (которая в свое время стоила немалых денег) это выглядит непрезентабельно. Колхозно это выглядит, честно говоря.
      3. «Программисты» любят информацию складировать и анализировать, а также предоставлять к ней удаленный доступ. Не сидеть же в серверной у дисплейчика все время.
      4. Внутри коммутатора (и наверняка внутри других железяк в шкафу) тоже есть датчик, но вы его проигнорировали. А ведь это самое интересное — температура внутри железа.
      • +2
        Вот так уже интереснее. Я за нормальный диалог. Отвечаю по пунктам.

        1. Это корпоративный блог программы FLProg, и здесь одна из задач поста показать реализацию решений по организации меню, работе с ЕЕPROM, реализацию режима поддержания заданной температуры с помощью программы. Судя по вопросам на форумах посвященных Ардуино, в группах в Контакте, да и на форуме программе многих эти вопросы очень интересуют. Проект для программы выложенный в посту снабжен максимально возможными комментариями.

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

        3. Блок управления вынесен в помещение дежурных инженеров(о чем написано в начале статьи), где всегда находится оперативный дежурный. так что в серверной сидеть нет необходимости. В удаленном доступе, как и логировании показаний не вижу смысла. Зачем нужны эти данные? Может это потому что мы все таки не программисты, а инженеры АСУ, и мы не держим лишнюю информацию, и привыкли решать задачи максимально эффективным и экономичным способом.

        4. Идеология системы была в автономности и отсутствии зависимости от других железяк. Единственная связь с другим оборудованием — питание от бесперебойника размещенного в шкафу.
      • 0
        Это те статьи — избитая тема. По сути — там простые в реализации но очень сложные и зависимые технические решения(от сети(локальной), от работы промежуточного железа, от операционной системы).

        Температура внутри железа не имеет отношения к работе кондиционеров.
        • 0
          «Програмиисту» зарезервировать соединения в локальной сети — это написать по одной строчке в конфиге каждого коммутатора, ну и провода дополнительные бросить. То же относится к работе операционной системы — весь экземпляр ОС (тем белее, что он не имеет состояния), можно виртуализировать и поднимать на другом железе в случае отказа или зависания. Это рутинные админские задачи.

          В работе кондиционеров очень желательно учитывать состояние и работу железа, чтобы не устраивать в серверной забеги температуры за нагрузкой, а предсказывать и корректировать температуру, удерживая ее всегда на одном уровне.
          • 0
            Как я написал в посте, кондиционеры у нас обычные, бытовые. Одного кондиционера всегда хватает. Он поддерживает температуру в серверной около 20 С. Система поддерживает температуру в шкафу между 25 и 27 С. При достижении 27 С включается первая ступень, а при опускании до 25 вентиляция выключается. За три недели температура ни разу не поднялась выше 27С. В случае выхода из строя одного вентилятора при температуре внутри шкафа 30 градусов включится вторая ступень. Ну а что будет при выходе из строя кондиционера и повышении температуры выше 35С я описал ниже, не буду поаторяться
            • 0
              Видел я такую серверную, в которой бытовой кондиционер круглосуточно молотил с уставкой 20 градусов. В итоге стены помещения были вечно холодными, летом на них (снаружи серверной) шло обильное выпадение конденсата и начала развиваться плесень.
              Вывод — в серверной не стоит делать «нормальные условия для газовой отрасли» (20 градусов Цельсия и 760мм. ртутного столба). Если в серверной 20 а в шкафах почти тридцать, это симптомчик применения для серверов шкафов с глухими дверцами, предназначенных для коммутации.
              UPD: Или применения вместо серверных корпусов с продувом «насквозь» стандартных писюковых кейсов.
              • 0
                Есть и то и другое. Но как говорится что поставили, то и стоит. С тем и приходится работать. Насчет плесени — не переживаем, находимся на крайнем севере, так что стены очень теплые, и конденсат не образуется. Помещение естественно не специализированная серверная, а приспособленное. Шкафы то же какие пришли. Есть сервера и в обычных писюковых корпусах.
                А что делать — с тем и живем.
        • 0
          Совершенно верно. Важнее поддерживать температуру внутри шкафа, а за температуру внутри железа пускай отвечает система терморегуляции серверов. Но в случае выхода из строя кондиционера и превышения температуры внутри шкафа выше аварийной уставки в помещении дежурных инженеров запищит сирена(вообще очень похоже получилось) заморгает подсветка дисплея. Подымет даже спящего (проверенно). Соответственно спасем сервера и диски от перегрева.
      • 0
        Дополнительное замечание по первому пункту.
        Сейчас посмотрел Ваши ссылки. Это все системы завязаны на использование сервера( Измеряем температуру: TEMPer + Python + Windows), дорогущей железяки (Каким образом у вас поддерживается климат в серверной? ) которую заказывать и утверждать особая задача, а потом еще год ждать, ну или опять таки сервер (Мониторинг температуры гермозоны с помощью 1-wire датчиков и Zabbix 2).
        У нас же получилась небольшая автономная система за смешные деньги (по моему даже 1000 р не набралось) которую можно в принципе безболезненно перенести на любой объект где необходим контроль температуры, и управление вентиляцией или нагревом. Так что отличие все таки есть
        • +2
          Понимаете, серверная — это место высокой ответственности, и любой незапланированный отказ по вине самопального железа может в итоге обойтись дорого, или даже очень дорого.
          Я думаю, организация, позволившая себе купить Cisco за примернно две с половиной тысячи долларов, сможет купить железку за триста баксов, причем в стоечном исполнении, оттестированную на электромагнитную совместимость и с гарантией…
          • 0
            Да конечно, наша организация может себе позволить купить практически любую железяку. Но только пока заявка пройдет все инстанции про эту железяку, придет к нам, пройдет где-то год. Да и зачем ставить что то сложное. У нас все равно проводится регулярный обход помещений, а эта система только способ скорее отреагировать на выход из строя кондиционера, и перейти на резервный. И мне автономная вынесенная система кажется более надежной чем встроенная в стойку вместе со всем остальным оборудованием. В любом случае доверять ей начнем только после нескольких месяцев безотказной работы.
            • 0
              Я бы все-таки проработал вопрос помехоустойчивости. То что вы развели силовые цепи и сигнальные это всего лишь сокрытие проблемы а не её решение. Провода длиннее 30 сантиметров уже нуждаются в мерах защиты от ЭМИ. Сейчас не зависает, а начнется статика или какие-то работы по соседству и будет снова виснуть. Там бы в контроллере вач-дог использовать и т.п.
              • 0
                На следующую вахту из офиса привезут экранированную сетевуху. Я думаю это решит проблему окончательно. Для начала попробую кинуть концы раза в два — три длиннее чем нужно. У меня будет месяц на тестирование и эксперименты, там и выяснится. Насчет вач-дог-а я подумаю. Он пока не реализован в FLProg, подумаю над реализацией.
  • +2
    Руководство отдела, зная о моей работе над проектом FLProg, предложило создать систему мониторинга температуры в серверных шкафах

    Было бы лучше, если бы руководство предложило купить оборудование климатического контроля шкафов.
    • 0
      Да это было бы конечно лучше. Но Вы же знаете как это происходит. В конце года делается заказ на следующий год, потом в конторе половина выкидывается, потом тендер на поставку, и к концу следующего года может быть придет. Да и менеджерам в офисе необходимо еще доказать необходимость этого оборудования. Процентов на 90 ответ будет такой «У вас же регулярные обходы, не нужно вам этого». Не раз такое уже было.

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

Самое читаемое Разное