Sunday, May 23, 2010

Arduino in datacenter rack environmental monitoring



 
 
TOMBOX* : An Arduino based solution for 
environmental monitoring of datacenter racks


Marco Guardigli  - TomWare -  
started 2010 may 23
 
last updated 2011 nov 18 (pictures added)



*it was monbox, but conflicted with another project.


Target:

Build a cheap and simple data collector and alerting system to monitor a set of server and network racks in a datacenter, via ethernet network. Project requires sensors for temperature, humidity, liquid spills, vibrations, door openings, ecc...



Solution:

Engineer and build a small box, including an Arduino with ethernet shield, some visible leds, a local power supply, and sensor connections. Each rack will have its box, and these will be connected to an autonomous monitoring vlan.
A central Zabbix system will collect data from the boxes, manage historical series, and eventually generate alerts via email and sms (sms are sent via a bluetooth connection to a dedicated cellphone, so to be independent from main network availability)



Project state:

Underway, with prototype already working.



Description:

We are using Arduino 2009 boards, with ethernet shields.
Due to the arduino integration with ethernet shield, some pins are not available.

The available pins are:
Digital pins 2-9 (total 8 lines)
Analog pins 0-5 (total 6 lines)



Network Addressing:

Each box needs to have a unique mac address and ip address. To keep circuit simple and avoid dip switches, we decided to "burn" this configuration directly on the code. Each box gets a personalized code, which differs just for mac and ip address configuration. The code is uploaded to each box via usb interface, upon setup. The address is then written on a label on each box.
We discarded the option of implementing dhcp, because in any case we would have needed a software definition of a unique mac address.


Power:

Each unit is powered via a small and cheap USB power supply, to be connected to the local rack power. (here is a picture of the power supply)



Sensors:

We are planning to connect to each environmental monitoring box the following sensors:

Front Rack Door: open/close, with a simple switch, on a digital pin.
Back Rack Door: open/close, with a simple switch, on a digital pin.

Temperature & Humidity: Here I have some options, and I have to choose the best one.
  • Temperature Option1: a simple thermistor, to be connected to an analog pin, in parallel with a resistor. This is very cheap, but requires calibration.
  • Temperature Option2: a National LM35 sensor, to be connected to an analog pin. This is cheap, and should be linear, with 0,5 °C accuracy which is ok for me. This should be ok if I put the sensor in the box itself.
  • Temperature Option3: a Dallas Semiconductor DS18S20 digital thermometer sensor, to be interfaced via 1-wire protocol a digital pin. This also has 0,5 °C accuracy, and should allow to use a longer cable and have the sensor at a longer distance from the box. I am afraid this is not going to be cheap.
  • Temperature + Humidity Option 1: a Sensirion SHT1x. This is a digital sensor that reads both temperature and humidity. It is already calibrated. I am afraid this is not going to be cheap, but there are many variants of this sensor, from the low-end SHT10 to the high-end SHT75. I need to ask about prices.
  • Temperature + Humidity Option 2: I found the Kele/Precon HS2000V which is an analog sensor that reads both temperature and humidity. This could be connected to two analog pins, and requires power. I could embed this sensor in the box itself.
I decided to go on using a Sensirion SHT-11 digital sensor. It is a very small device, quite sophisticated.
 The quite difficult part was to solder it, because the contacts are very small and close each other. Also the device is quite delicate, and soldering has to be done quickly not to damage it.


Smoke:
I built a simple sensor, made of two components: an infrared led, and an infrared photodiode. The infrared photodiode is mounted about 5cm apart from the led, and I put a heat shrinking tube on it. The resistor for the infrared led and for the photodiode are selected so to allow a "weak" reading which is altered by smoke on the ir path.

The photodiode output is sampled in an analog input. In this way, if some smoke gets in the middle, the output will change, and it can be detected.


Flood:
I have to find (or maybe build) the proper sensor.


Current Prototype:



Actual product, as delivered to the customers
The Arduino 2009 with atmega328, the wiznet ethernet shield, and a small hand made custom shield for our circuitry, have been packaged in hand made boxes.
I choose Simona.de Simona sheet 6mm and 3mm (a PVC foam material used in building and for making signs, also called FOREX or FOAMEX) as the material for the boxes. It is quite cheap.


It is easy to cut, easy to glue, and has enough rigidity to allow screws. This material is fire resistant, easy to work and well suited for its strenght.

  


As of november 2011, some dozens of these box have been built, and they work seamlessly in several datacenter environments.


Software:

I already hacked something which is working, at least to perform some tests with the zabbix central sampling engine. Each Arduino box runs a webserver, which is providing in its page the reading of all the sensors.
On the Zabbix server, there is a polling process and a parser which decodes the data, and archives it, eventually generating alarms.

I will update the code posted here as soon it is a bit more refined.
Here you have the code that runs on the arduino, to read the reed sensors (digital pin 4), and the temperature and humidity sensor (via digital pins 5 and 6). Reed sensors are to be put in series, so whatever is open triggers the door open alarm.

Analog pin 0 will be used for smoke sensor, but code is not yet here.
Digital pins 2 and 3 are used to briefly flash two colored leds, that in the future will become the box visual interface.


(CAVEATS: the following source code has been copied and pasted from the IDE to the blog, and this process unfortunately changed a bit some code lines, when they contain HTML tags. Beware!)


/*
 * Arduino + Ethernetshield (Wiznet)
 * datacenter sensor box
 * TOMBOX
 * Marco Guardigli mgua@tomware.it 
 * this code is GPL, see www.gnu.org
 * see http://marco.guardigli.it/2010/05/arduino-in-datacenter-rack.html
 *
 * developed on arduino 21 ide, oct 2010.
 *
 * based on arduino Web Server example code
 * SHT-11 portions shamelessly adapted from the following sources
 *   http://bbrack.net/Decimilia/sht1x.pde
 *   and
 *   http://www.railsimstuff.com/files/SHT11.pde
 *   http://www.nuelectronics.com/download/projects/sht10_float.pde
 *
 * sensor reading happens when http request comes in.
 * sensors are not read if there are not incoming http requests
 * It works this way so to have a central monitoring software to 
 * perform http reads
 * 
 * in the future snmp trap to a remote server will probably be added
 * as well as polling independent sensor readings
 *
 * components:
 *   reed magnetic sensor (on digital pin 4)
 *   sensirion sht-11 temp+humidity sensor (digital pins 5,6)
 *   home made smoke sensor, with infrared led and photodiode analog output (on analog pin 0)
 *   free digital i/o: 7,8,9
 *   free analog i/o: 1,2,3,4,5
 *
 * Pin Connections (SHT-11 pin count starts from top left going down: the right pins are not connected)
 *      SHT-11  -  arduino netshield
 *      1 gnd   -  gnd
 *      2 DATA  -  digital 6
 *      3 SCK   -  digital 5
 *      4 VDD   -  5V
 *
 */
#include 
#include 

#define DATA    6              /* Arduino pin for SHT11 data read/write */
#define CLOCK   5              /* Arduino pin for toggling the clock */

#define NOACK   0               /* Flags to tell read_byte routine whether */
#define ACK     1                /* or not to send an ACK after the read   */ 

/* Define the SHT1x commands to be their command codes */
#define         MEASURE_TEMP    0x03
#define         MEASURE_HUMI    0x05
#define         STATUS_REG_W    0x06
#define         STATUS_REG_R    0x07
#define         RESET           0x1e

/* Following constants are in microseconds */
#define         LONG_DELAY      delayMicroseconds(50)
#define         MEDIUM_DELAY    delayMicroseconds(10)
#define         SHORT_DELAY     delayMicroseconds(5)

unsigned char debug;          /* When set, intermediate data is printed */

#define ALERTLED    2    /* bicolor led, RED, on if LOW */
#define OKLED       3    /* bicolor led, RED, on if LOW */
#define OKBLINK     20000 /* loops for OK led blink */
#define ALERTBLINK  3000  /* loops for OK led blink */

int i;
byte mac[] = { 0xDE, 0xAD, 0xBA, 0xEF, 0x00, 0x01 };
byte ip[] = { 172, 30, 4, 177 };
Server server(80);

int okcnt = OKBLINK;
int alertcnt = ALERTBLINK;
int okledstate = LOW;     /* led is on if state is LOW */
int alertledstate = HIGH; /* led is on if state is LOW */ 
int alert = false;        /* this goes true if we are in alert state. When this is true, alert led blinks */


/**
 * sWriteByte
 *
 *      Routine to write a byte to the SHT1x and check for the acknowledge
 *
 * Parameters:
 *      @value          byte value to be written
 *
 * Returns:
 *      0 for success, 1 if no Acknowledge received from SHT1x
 */
char sWriteByte(unsigned char value)
{
    unsigned char ix;                   /* loop index */
    unsigned char error;                /* result of the write */

    pinMode (DATA, OUTPUT);
    for (ix = 0x80; ix; ix >>= 1) {
        digitalWrite (DATA, ix & value);/* Next bit to I/O port */
        SHORT_DELAY;   /* Shouldn't be needed */
        digitalWrite (CLOCK, HIGH); /* Set clock signal high */
        MEDIUM_DELAY;   /* some delay needed */
        digitalWrite (CLOCK, LOW); /* Set clock back to low */
    }
    pinMode (DATA, INPUT);              /* Prepare to read the ACK bit */
    digitalWrite (DATA, HIGH);          /* Engage pull-up resistor */
    digitalWrite (CLOCK, HIGH);         /* Send 9th clock for ack) */
    SHORT_DELAY;
    error = digitalRead (DATA);         /* Expect pulled down by SHT1x */
    digitalWrite (CLOCK, LOW);          /* complete the clock pulse */
    return error;
}

/**
 * sReadByte
 *
 *      Routine to read one byte from the SHT1x.  An acknowledge may or
 *      may not be generated, according to the routine's argument.
 *
 * Parameters:
 *      @sendAck        If non-zero, an ACK is sent to the SHT1x if
 *                      the read was successful.
 *
 * Returns:
 *      The value read from the SHT1x
 */
char sReadByte (unsigned char sendAck)
{
    unsigned char ix;                   /* loop index */
    unsigned char val = 0;              /* for building the received data */

    /*
     *  Note to Bill:   we should assure DATA is input by default
     *                  and get rid of these next two lines
     */
    pinMode (DATA, INPUT);              /* Set data pin for input mode */
    digitalWrite (DATA, HIGH);          /* Engage pull-up resistor */

    for (ix = 0x80; ix; ix >>= 1) {
        digitalWrite (CLOCK, HIGH);     /* High tells SHT1x we're reading */
        SHORT_DELAY;
        if (digitalRead (DATA))
            val |= ix;                  /* If DATA high, set corr. bit */
        digitalWrite (CLOCK, LOW);      /* Tell SHT1x ready for next bit */
        SHORT_DELAY;
    }
    pinMode (DATA, OUTPUT);             /* Change mode to prepare for ack */
    digitalWrite (DATA, !sendAck);      /* Set DATA LOW if sendAck requested */
    SHORT_DELAY;
    digitalWrite (CLOCK, HIGH);         /* Let SHT1x get the data */
    MEDIUM_DELAY;                       /* delay for safety */
    digitalWrite (CLOCK, LOW);          /* Signal we're done with this */
    pinMode (DATA, INPUT);              /* Return Arduino pin to input */
    digitalWrite (DATA, HIGH);          /* And engage the pull-up */
    return val;
}

/**
 * sTransmitStart
 *
 *      Routine to generate a "Transmission Start", which looks like:
 *
 *                      _______           _______
 *              DATA:          |_________|
 *                            ____      ____
 *              CLOCK:  _____|    |____|    |____
 *
 * Parameters:
 *      None
 *
 * Returns:
 *      No return value.
 */
void sTransmitStart (void)
{
    pinMode (DATA, OUTPUT);             /* Set DATA mode output */
    digitalWrite (DATA, HIGH);          /* Start DATA in high state */
    digitalWrite (CLOCK, LOW);
    MEDIUM_DELAY;
    digitalWrite (CLOCK, HIGH);
    MEDIUM_DELAY;
    digitalWrite (DATA, LOW);
    MEDIUM_DELAY;
    digitalWrite (CLOCK, LOW);
    LONG_DELAY;
    digitalWrite (CLOCK, HIGH);
    MEDIUM_DELAY;
    digitalWrite (DATA, HIGH);
    MEDIUM_DELAY;
    digitalWrite (CLOCK, LOW);
    /* This routine will normally be followed by a write, so leave pinMode */
}

/**
 * sConnectionReset
 *
 *      Routine to generate a transmission reset, i.e. reset the SHT1x
 *      to a known state to begin a transmission.  It produces a pulse
 *      train that looks like this:
 *         _____________________________________________________         _____
 *   DATA:                                                      |_______|
 *            _    _    _    _    _    _    _    _    _        ___     ___
 *   SCK : __| |__| |__| |__| |__| |__| |__| |__| |__| |______|   |___|   |___
 *
 * Parameters:
 *      None
 *
 * Returns:
 *      No return value.
 */
void sConnectionReset (void)
{
    unsigned char ix;
    pinMode (DATA, OUTPUT);             /* Set Data to output mode */
    digitalWrite (DATA, HIGH);          /* Start Data in high state */
    digitalWrite (CLOCK, LOW);          /* Start Clock in low state */
    /* Now generate 9 clock "pulses" */
    for (ix = 0; ix < 9; ix++) {
        SHORT_DELAY;
        digitalWrite (CLOCK, HIGH);
        SHORT_DELAY;
        digitalWrite (CLOCK, LOW);
    }
    sTransmitStart ();                  /* Follow with start pulse */
}

/**
 * sSoftReset
 *
 *      Routine to do a "soft" reset, i.e. send a "Reset" command to the SHT1x
 *
 * Parameters:
 *      None
 *
 * Returns:
 *      0 for success, 1 if bad response from SHT1x
 */
char sSoftReset (void)
{
    sConnectionReset ();                /* Reset SHT1x communication */
    return sWriteByte (RESET);          /* Send command and return result */
}

/**
 * doCRC
 *
 *      Routine to calculate the CRC while message is sent / received
 *
 * Parameters:
 *      @ch             character to be added to CRC
 *      @crc            crc to which character is to be added
 *
 * Returns:
 *      Target CRC value is updated
 */
#define CRC_POLY        0x31            /* CRC polynomial x**8 + x**5 + x**4 */
void doCRC (unsigned char ch, unsigned char *crc)
{
    int ix;
    unsigned char b7;
    
    for (ix = 0; ix < 8; ix++) {
        b7 = ch ^ *crc;
        *crc <<= 1;
        ch <<= 1;
        if (b7 & 0x80)
            *crc ^= CRC_POLY;
    }
    return;
}
        
/**
 * sMeasure
 *
 *      Routine to make a measurement of either temperature or humidity, and
 *      return the result.
 *
 * Parameters:
 *      @pValue         pointer to where value should be stored
 *      @command        command to be sent to the SHT1x
 *      @singleFlag     Flag to show a single byte only should be read
 *
 * Returns:
 *      Returns the status from the 'read' of the data, and places the value
 *      read into the the locations pointed at by the arguments.  Note that the
 *      SHT1x returns data as (MSB, LSB) so this routine stores the
 *      (short integer) value in reverse sequence.
 */
char sMeasure (unsigned char *pValue, unsigned char command,
               unsigned char singleFlag)
{
    unsigned char error;                /* holds return value from routine */
    unsigned int ix;                    /* used for 'wait for data' loop */
    unsigned char ch, crc, revCRC;
    
    crc = 0;                            /* Initialize CRC to zero */
    sTransmitStart ();                  /* Start transmission of command */
    error = sWriteByte (command);       /* Send the requested command */
    /* Note that sWriteByte leaves DATA in input mode */
    doCRC (command, &crc);              /* Include command in CRC */
    if (debug) {
        Serial.print("After 'command': CRC is 0x");
        Serial.println(crc, HEX);
    }
    for (ix = 0; ix < 65535; ix++)
        if (!digitalRead (DATA))
            break;
    if (digitalRead (DATA)) 
         Serial.println("DATA did not go low after writing command");
    if (!singleFlag) {                  /* If a 2-byte reply */
        ch = sReadByte (ACK);           /* Read MSB of data */
        doCRC (ch, &crc);               /* Include in CRC */
        if (debug) {
            Serial.print("After MSB: CRC is 0x");
            Serial.println(crc, HEX);
        }
        *(pValue + 1) = ch;             /* Store MSB byte */
    }
    ch = sReadByte (ACK);               /* Read LSB of data */
    doCRC (ch, &crc);                   /* Include in CRC */
    *pValue = ch;                       /* Store LSB byte */
    if (debug) {
        Serial.print("After LSB: CRC is 0x");
        Serial.println(crc, HEX);
    }
    ch = sReadByte (NOACK);             /* Read msg CRC, don't send ACK */
    revCRC = 0;
    for (ix = 0; ix < 8; ix++) {
      if ((0x80 >> ix) & ch)
        revCRC |= (1 << ix);
    }
    if (debug) {
        Serial.print("After Checksum: CRC is 0x");
        Serial.print(crc, HEX);
        Serial.print(", received value was 0x");
        Serial.println(revCRC, HEX);
    }
    if (crc != revCRC) {
        Serial.print("CRC error in reply (command was 0x");
        Serial.print(command, HEX);
        Serial.print("CRC is 0x");
        Serial.print(crc, HEX);
        Serial.print(", received value was 0x");
        Serial.println(revCRC, HEX);
        Serial.println(") - resetting SHT1x connection");
        sConnectionReset();
    }
    return error;
}

/**
 * calcTempHumid
 *
 *      Routine to calculate the "true" temperature and humidity based upon
 *      the "tick" values read from the SHT1x.  The SHT1x is set to operate in
 *      12-bit mode for humidity, and 14-bit mode for temperature.  The
 *      conversion constants are taken from the SHT1x datasheet, assuming a
 *      supply voltage of 5.0V.
 *
 * Parameters:
 *      @pHumidity      pointer to humidity value
 *      @pTemperature   pointer to temperature value
 *
 * Returns:
 *      Input values of temperature and humidity are overwritten with their
 *      calculated "true" values.
 */
void calcTempHumid (float *pHumidity, float *pTemperature)
{
    /* Constants for conversion of reading to relative humidity */
#define         C1      -4.0
#define         C2      +0.0405
#define         C3      -0.0000028
    /* Constants for temperature-compensated relative humidity */
#define         T1      +0.01
#define         T2      +0.00008
    /* Constants for conversion of temperature reading to Centigrade */
#define         D1      -40.00
#define         D2      +0.01

    float rh = *pHumidity;      /* relative humidity (input value) */
    float rhLin;                /* Linear value of humidity */
    float rhTrue;               /* Temperature-compensated humidity value */
    float t = *pTemperature;    /* input value for temperature */
    float tC;                   /* Temperature converted to Celsius */

    tC = D1 + (t * D2);         /* Linear conversion of temperature */
    rhLin = (C3 * rh * rh) + (C2 * rh) + C1;    /* "ticks" to relative H */
    rhTrue = (tC - 25) * (T1 + (T2 * rh)) + rhLin;
    /* Assure our relative humidity isn't out of range (> 100% or < 0.1%) */
    if (rhTrue > 100.0)
        rhTrue = 100.0;
    else if (rhTrue < 0.1)
        rhTrue = 0.1;
    /* Finally, return the calculated values */
    *pTemperature = tC;
    *pHumidity = rhTrue;
}

/**
 * calcDewPoint
 *
 *      Routine to calculate the dew point based upon relative humidity
 *      and temperature.  I have no idea what it's doing, but since it
 * comes from the Sensirion literature, it's probably correct :-).
 *
 * Parameters:
 *      @humidity       value of relative humidity
 *      @temperature    value of temperature
 *
 * Returns:
 *      Calculated dew point
 */
float calcDewPoint (float humidity, float temperature)
{
    float logEx;
    logEx = 0.66077 + (7.5 * temperature) / (237.3 + temperature)
            + (log10(humidity) - 2);
    return (logEx - 0.66077) * 237.3 / (0.66077 + 7.5 - logEx);
}

/**
 * splitFloat
 *
 *      This routine takes a float as input and returns the integer part and
 * the fractional part, to the number of decimal places specified.  The
 * only reason I wrote it is because I couldn't find any existing routine
 * to print out a float in a reasonable format (with decimal places)
 *
 * Parameters:
 *      @fNum        The floating point number to be dissected
 *      @pInt        Pointer to an integer to contain the integer part
 *      @pFrac       Pointer to a string to contain the fraction
 *                   Note: the caller must assure the string is large enough
 *      @decPlaces   Number of decimal places for the operation
 *
 * Returns:
 *      The values calculated are placed in the locations specified.
 */
void splitFloat (float *fNum, int *pInt, char *pFrac, int decPlaces) {

     int ix;
     int frac;
     float fVal;

     /* Round the input according to precision, plus fudge for noise */
     fVal = *fNum + (0.5 * pow(10.0, (float)(-decPlaces))) + 0.00001;
     *pInt = fVal;          /* Return truncated integer value */     
     /*
      * Now isolate just the fractional part of the original number
      */
     fVal = fVal - (float)(*pInt);  /* Remove the integral part */
     /* Convert the fraction into a simple integer */
     frac = fVal * pow (10.0, (float)decPlaces);
     /* Now format it as a leading-zero string of digits */
     pFrac += decPlaces;    /* point to string terminator position */
     *pFrac-- = 0;          /* put in terminator */
     for (ix = 0; ix < decPlaces; ix++) {
         *pFrac-- = (frac % 10) | 0x30;  /* put in digits in reverse order */
         frac /= 10;
     }
}


/**
 * printReading
 *
 *      Routine to print out the value of caculated data, using a common format
 *      of {label} {int value}.{single digit fraction}{suffix}
 *
 * Parameters:
 *      @label          string for starting label
 *      @pVal           pointer to float value to display
 *      @suffix         string to append to value
 *
 * Returns:
 *      nothing
 */
void printReading (char *label, float *pVal, char *suffix) {
    int num;
    char str[10];
    
    splitFloat (pVal, &num, str, 1);
    Serial.print(label);
    Serial.print(num, DEC);
    Serial.print(".");
    Serial.print(str);
    Serial.println(suffix);
}


void loop2()
{
    char cmd;                   /* command input by user */
    int humidVal;               /* humidity value read from SHT1x */
    int tempVal;                /* temperature value from SHT1x */
    unsigned char statusVal;    /* contents of status register */
    float fHumidity;            /* working value for humidity calculation */
    float fTemperature;         /* working value for temperature calculation */
    float dewPoint;             /* calculated Dew Point value */
    unsigned char error;        /* return value for routine calls */

    while (Serial.available() > 0) {  /* when a serial connection exists */
        cmd = Serial.read();    /* Read user comand */
        error = 0;
        switch (cmd) {
            case 'r':
            case 'R':
                /* Read request - read in temperature and humidity */
                error += sMeasure ((unsigned char *)&humidVal, MEASURE_HUMI, 0);
                if (debug) {
                    Serial.print("In main loop: humidVal is ");
                    Serial.print(humidVal, HEX);
                    Serial.print(" and return value is ");
                    Serial.println(error, DEC);
                }
                error += sMeasure ((unsigned char *)&tempVal, MEASURE_TEMP, 0);
                if (debug) {
                    Serial.print("In main loop: tempVal is ");
                    Serial.print(tempVal, HEX);
                    Serial.print(" and return value is ");
                    Serial.println(error, DEC);
                }
                if (error)
                    sConnectionReset();
                else {
                    fHumidity = float(humidVal);
                    fTemperature = float(tempVal);
                    calcTempHumid (&fHumidity, &fTemperature);
                    dewPoint = calcDewPoint (fHumidity, fTemperature);
                    printReading ("Temperature is ", &fTemperature, "\xb0 Celsius");
                    printReading ("Humidity is ", &fHumidity, "%");
                    printReading ("Dew point is ", &dewPoint, "\xb0 Celsius");
                }
                break;
            case 'd':            /* "Toggle" debug flag for printing */
            case 'D':            /* intermediate data */
                debug ^= 1;
                if (debug)
                    Serial.println("**** Debugging enabled ****");
                else
                    Serial.println("**** Debugging disabled ****");
                break;
            case 's':            /* Read status register */
            case 'S':
                /* Read request - read in temperature and humidity */
                error += sMeasure ((unsigned char *)&statusVal, STATUS_REG_R, 1);
                Serial.print("Status register contains 0x");
                Serial.println(statusVal, HEX);
                break;
            default:
                Serial.println("Unrecognized command.");
        }
    }
}


void toggleAlertLed(){
  if (alert && alertledstate == HIGH)
    alertledstate = LOW;
  else
    alertledstate = HIGH;
}


void toggleOkLed(){
  if (okledstate == HIGH) 
    okledstate = LOW;
  else
    okledstate = HIGH;
}


void greeting(){
  /* interface led greeting */
  pinMode(OKLED, OUTPUT);
  digitalWrite(OKLED,LOW);
  delay(250);                /* green show */
  digitalWrite(OKLED,HIGH);  

  delay(500);
  pinMode(ALERTLED, OUTPUT);
  digitalWrite(ALERTLED,LOW);
  delay(250);                /* red show */
  digitalWrite(ALERTLED,HIGH);

  delay(500);
  digitalWrite(OKLED,LOW);
  digitalWrite(ALERTLED,LOW);
  delay(500);                /* amber show */

  digitalWrite(ALERTLED,HIGH);
                             /* set green */
}



void setup()
{
  okcnt = OKBLINK;
  alertcnt = ALERTBLINK;
  okledstate = LOW;           /* led is on when state is LOW */
  alertledstate = HIGH;       /* led is off when state is high */
  
  Serial.begin(9600);         /* Open Arduino serial communications */
  Serial.println("Setup.");
  pinMode (CLOCK, OUTPUT);    /* set pins for SHT-11 */
  pinMode (DATA, OUTPUT);     /* set pins for SHT-11 */
  sConnectionReset ();        /* Reset the SHT11 device */
  
  Ethernet.begin(mac, ip);    
  server.begin();
  
  greeting();                 /* flash led interface */
}


void loop()
{
  char cmd;                   /* command input by user */
  int humidVal;               /* humidity value read from SHT1x */
  int tempVal;                /* temperature value from SHT1x */
  unsigned char statusVal;    /* contents of status register */
  float fHumidity;            /* working value for humidity calculation */
  float fTemperature;         /* working value for temperature calculation */
  float dewPoint;             /* calculated Dew Point value */
  unsigned char error;        /* return value for routine calls */
  int num;
  char str[10];  
  
  Client client = server.available();
  if (client) {
    // an http request ends with a blank line
    boolean current_line_is_blank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        // if we've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so we can send a reply
        if (c == '\n' && current_line_is_blank) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println();
          Serial.println("Serving page.");
          client.println("

TomWare tombox v1 (oct 2010)

"); client.println("Arduino sensing and monitoring box "); client.println("mgua@tomware.it "); client.println("digital pin 2,3: bicolor Led red/green cathodes "); client.println("digital pin 4: reed magnetic sensor "); client.println("digital pins 5,6: clk,data of SHT-11 temp and humidity sensor "); client.println("Analog pin 0: smoke sensor "); client.println("
"); client.println("The 0-5 (6) analog (0-1023) lines "); for (i = 0; i < 6; i++) { client.print("analog input "); client.print(i); client.print(" is "); client.print(analogRead(i)); client.println(" "); } client.println("
"); client.println("The 2-4 digital (0-1) lines "); for (i = 2; i < 5; i++) { client.print("digital input "); client.print(i); client.print(" is "); client.print(digitalRead(i)); client.println(" "); } client.println("The 7-9 digital (0-1) lines "); for (i = 7; i < 10; i++) { client.print("digital input "); client.print(i); client.print(" is "); client.print(digitalRead(i)); client.println(" "); } client.println("
"); // ---------Temp & Humid readings ------------------------------ error = 0; error += sMeasure ((unsigned char *)&humidVal, MEASURE_HUMI, 0); error += sMeasure ((unsigned char *)&tempVal, MEASURE_TEMP, 0); if (error) { client.println(" SHT11 error, resetting... "); sConnectionReset(); } else { fHumidity = float(humidVal); fTemperature = float(tempVal); calcTempHumid (&fHumidity, &fTemperature); dewPoint = calcDewPoint (fHumidity, fTemperature); client.print("Temperature: "); splitFloat (&fTemperature, &num, str, 1); client.print(num, DEC); client.print("."); client.print(str); client.println("\xb0 Celsius"); client.println(" "); /* temperature alert threshold */ if (num > 30) { Serial.println("temp threshold exceeded."); alert = true; } else { alert = false; } /* --------------------------- */ client.print("Humidity: "); splitFloat (&fHumidity, &num, str, 1); client.print(num, DEC); client.print("."); client.print(str); client.println("%"); client.println(" "); client.print("Dew point: "); splitFloat (&dewPoint, &num, str, 1); client.print(num, DEC); client.print("."); client.print(str); client.println("\xb0 Celsius"); client.println(" "); } /* Notify on green led that page serving is in progress inverting its state */ toggleOkLed(); pinMode(OKLED, OUTPUT); digitalWrite(OKLED,okledstate); /* client.println("
The 2-4 (3) briefly output digital (0-1) lines "); for (int i = 2; i < 5; i++) { pinMode(i, OUTPUT); digitalWrite(i,LOW); delay(10); pinMode(i, INPUT); delay(10); } */ break; } if (c == '\n') { // we're starting a new line current_line_is_blank = true; } else if (c != '\r') { // we've gotten a character on the current line current_line_is_blank = false; } } } // give the web browser time to receive the data delay(1); client.stop(); } // --------- Led Blinking ------------------------------ okcnt--; if (okcnt < 1) { okcnt = OKBLINK; toggleOkLed(); pinMode(OKLED, OUTPUT); digitalWrite(OKLED,okledstate); } alertcnt--; if (alertcnt < 1) { alertcnt = ALERTBLINK; toggleAlertLed(); pinMode(ALERTLED, OUTPUT); digitalWrite(ALERTLED,alertledstate); } // --------- Led Blinking end ------------------------------ }




Marco ( @mgua on twitter)




.

11 comments:

Unknown said...

Great idea! I work in a data center. I will be looking into such solution as the current environment boards for our aging CRAC units are over $500 each. They are also multi-loop RS485 which is such a PITA. The boards don't even tell me if the compressors are running either just if the system is calling for them. Big waste of money IMO. The monitoring software to go with them costs thousands also. Building automation control is a very expensive business, yet you always feel cheated somehow. Then the LonWorks vs. BacNet flame war too. Although If you build something using OTS/Arduino. It might not be a bad idea to implement BacNET just to utilize existing software. Unless you can design similar environment monitoring software. The kind where you can design your own view, add gauges and texts which link to data points. Check out RC-Studio from reliablecontrols.com or Foreseer from Eaton to get an idea what I mean.

Bryan

Unknown said...

Totally missed what you said about using Zabbix. Sounds like a easy solution. Sure would be breaking the mold of building automation.

Marco Guardigli said...

Bryan,

My project is underway. Code is quite simple. I am still working for a cheap and reliable solution for humidity sensing.
Already did it with non too cheap sensors.

Interfacing the Arduino box with RS485 should not be a problem.

Zabbix is the monitoring software I am using. There are scripts constantly polling the Arduino boxes.

Zabbix conveniently stores the collected data in RRD databases, and allows graphs to be easily created.

Jon said...

I just stumbled across your posting and was quite curious about it's progression. I had the idea to do something very similar to this and was wondering if you ever found a good sensor solution for the flood?

Marco Guardigli said...

Jon,
This Project is almost complete, and I have just to find the time for updating it adding all the implementation details. We made the sensors for temperature and humidity, smoke, flooding, and door opening.

I will update the page in the following weeks.

There are many options for flood sensors, and we use a sensor which appears like a rope with embedded metallic wires. Resistance changes when the rope gets damp.
check the following:

http://www.alarms247.com/water-rope-sensor.aspx

http://www.carel.com/carelcom/web/eng/catalogo/prodotto_dett.jsp?id_gamma=104&id_prodotto=139&id_mercato=4

Pardamean said...

Hi Marco, this is a great project, can you share how zabbix can interpret the value from http query and put it into zabbix data?

Pardamean said...

Hi Marco, this is a great project. May you share or point to direction on what you do on zabbix to make it able to get value from web query?

Marco Guardigli said...

@Pardamean it is quite simple. A shell script is periodically launched by zabbix, against each of the arduino sensor boxes.
Script polls arduino box with wget. A small perl script parses output and leaves on stdout the temperature readings.

Another completely asynchronous script, launched by cron, periodically polls the boxes to read the other data, and injects values in RRD databases (see Tobias Oetiker amazing RRD tools).

RRD graphs are rendered in custom webpages.

@mgua

Pardamean said...

Marco, that is very clever. Have you ever tried to write a simple zabbix agent for arduino, that send traps in JSON format or send update command to zabbix? The goal is to save a little bit bandwidth because zabbix doesn't need to send request. I am trying to run several sensor over satellite link where the bandwidth cost is high so bandwidth saving is important. I currently trying to do that, but the references are not yet enough for me.
And thanks for sharing.

Marco Guardigli said...

@Pardamean, I now undertand better.

If you are interested, I could develop a very bandwidth effective udp one way transmission from tombox to a remote zabbix system. I also have experience of managing low bandwidth sat links.

Send me some more details by mail: mgua00 at gmail dot com

mgua

shobnaamkoly said...

Awesome Artical Really i have searching this type of valuable information From a lot of days i found satisfaction when Read your blog Thanks for giving this type blog