WiFi - Embedded Remote Supervisory System ? ERSS

Embedded Remote Supervisory System – ERSS Sistema Supervisorio Remoto Embarcado - SSRE

Hello all, I present you my graduation project to get my Engineering of Control and Automation degree last Dec. 09, 2010.

Uploaded with ImageShack.us[/img]

It's a supervisory system based in an Arduino Blackwidow (WiFi module on board) were you can get full control of a simulated process (temperature in my case). The main specs of my ERSS are: ? Change process variables values online ? Change PID setup online (set point, P, I & D action) ? Telemetry ? No software installed in the remote device *? PID controller software working at same time. *

The ERSS is a supervisory system that you can access it from a remote device (a Laptop, Netbook or Smart phone?) and no software pre installed is needed. When you access the ERSS via Blackwidow IP address, it send you an web page with the control panel, there you will have all fields to change and just press “Enviar” (“Send” in English) to update the values. You can see my control panel on figure below. For our prototype, we use a temperature control were a LM35 is my temp sensor, a second temp meter work side by side but without connection with de ERSS. On first figure you can see an overview of my prototype.

For the power source we used a 60W lamp droved by a simple dimmer, the funny thing here is that I removed the dimmer knob and put a gear from on old Epson printer, attached with this dimmer we have std servo with another gear from the same printer. Why a mechanic method to drive the Lamp? someone can ask. The answer is because working that way we had a visual position of how much power was delivered to the lamp. You can see that arrangement in the figure below

To connect all the system we need an WiFi router like showed below, here again you can see in my laptop the control panel web page

It was a good project to implement a WiFi capacities for an Arduino, and it worked! Fell free to ask? Thanks for your visit.

Jose Augusto Santaella gutosan@terra.com.br phone: +55 51 81221807

Source Code Part #1

/*************************************************************************
 
 Filename:    SSRE.pde
 
 Description: SSRE (Sistema Supervisorio Remoto Embarcado), Embedded Remote System
 
 ************************************************************************
  Demonstrates the use of Arduino and WiShield controlling temperature by PID 
 
 Contact Information:
  <edson@solsoft.com.br>  Edson Gaspar
  <gutosan@terra.com.br>  Jose Augusto Santaella
  
   Author               Date        Comment
  ---------------------------------------------------------------
   Gaspar & Guto            10/15/2010      Initial version
   
 **************************************************************************/
 
#include <WiServer.h>
#include <PID_Beta6.h>
#include <Servo.h>
#define WIRELESS_MODE_INFRA      1
#define WIRELESS_MODE_ADHOC      2

// Wireless configuration parameters
unsigned char local_ip[] = {192,168,0,111};      // IP address of WiShield
unsigned char gateway_ip[] = {192,168,0,1};      // router or gateway IP address
unsigned char subnet_mask[] = {255,255,255,0};      // subnet mask for the local network
const prog_char ssid[] PROGMEM = {"SSRE"};            // max 32 bytes

unsigned char security_type = 0;      // 0 - open; 1 - WEP; 2 - WPA; 3 - WPA2

// WPA/WPA2 passphrase
const prog_char security_passphrase[] PROGMEM = {"12345678"};      // max 64 characters

// WEP 128-bit keys
// sample HEX keys
prog_uchar wep_keys[] PROGMEM = {      0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,      // Key 0
                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      0x00,      // Key 1
                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      0x00,      // Key 2
                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      0x00      // Key 3
                                                };

// setup the wireless mode
// infrastructure - connect to AP
// adhoc - connect to another WiFi device
unsigned char wireless_mode = WIRELESS_MODE_INFRA;

unsigned char ssid_len;
unsigned char security_passphrase_len;


//Pin configurations
#define PIN_INPUT_LM35 0
#define PIN_OUTPUT_PWM_LIGHT 3
#define PIN_OUTPUT_PWM_FAN 5

//global definitions
#define _USE_SERIAL 1
#define N_LOOP_FOR_SERIAL 200000

//Global Variables
  int nLoopCount = N_LOOP_FOR_SERIAL;
  int fan_speed = 0;

//PID variables
  double dSetPointTemperature, dInputTemperature, dOutputTemperature;
  double lightKc = 1.0;      
  double lightTauI = 2.0;      
  double lightTauD = 0.5;
  Servo temperatureServo;
  PID temperaturePID(&dInputTemperature, &dOutputTemperature, &dSetPointTemperature, lightKc, lightTauI, lightTauD);

//function to write input edit in html page
void writeInputHTM(char* title, char* type, char* name, const char* value, boolean bChecked = false)
{
  WiServer.print("<tr><td>");
  WiServer.print(title);
  WiServer.print("</td><td><input type=\"");
  WiServer.print(type);
  WiServer.print("\" name=\"");
  WiServer.print(name);
  WiServer.print("\" size=\"4\" value=\"");
  WiServer.print(value);
  WiServer.print("\"");
  if( bChecked ) WiServer.print("checked=\"checked\"");
  WiServer.println("></td></tr>"); 
}

//format value to string with 2 decimals
void sresprintf (char* ch, double valor)
{
  int valor1 = (valor - (int)valor)*100;
  sprintf ( ch, "%0d.%d", (int)valor, valor1);
}

//constants strings stored in flash memory to save RAM memory 
const prog_char htmlreader[]         PROGMEM = {"<!doctype html><html><head><title>Sistema SSRE v1.1a</title>" };
const prog_char ht_endhead[]         PROGMEM = {"</head>"};
const prog_char ht_body[]            PROGMEM = {"<body onload=\"timed();\"><b>Sistema SSRE v1.1a</b>
"};
const prog_char ht_credits[]         PROGMEM = {"Gaspar & Guto
edson@solsoft.com.br
gutosan@terra.com.br"};
const prog_char ht_script[]          PROGMEM = {"<script type=\"text/javascript\">"\
"var req;function timed(){"\
"if(window.XMLHttpRequest){req = new XMLHttpRequest();} else if (window.ActiveXObject) {req = new ActiveXObject('MSXML2.XMLHTTP.3.0'); }"\
"if(req != undefined){req.onreadystatechange=function(){statusDone();};"\
"req.open(\"GET\", \"/status.htm\", true);"\
"req.send(\"\");}"\
"}"\
"function statusDone(){"\
"if (req.readyState == 4) {if (req.status == 200|| req.status == 304) {document.getElementById('status').innerHTML = req.responseText;}t=setTimeout(\"timed()\",1500);}"\
"}"\
"</script>"};
const prog_char ht_endbody[]          PROGMEM = {"</body>"};
const prog_char ht_endhtml[]          PROGMEM = {"</html>"};
const prog_char ht_br[]               PROGMEM = {"
"};
const prog_char ht_form[]             PROGMEM = {"<form>"};
const prog_char ht_endform[]          PROGMEM = {"</form>"};
const prog_char ht_table_gray[]       PROGMEM = {"<table bgcolor=\"#cccccc\">"};
const prog_char ht_table_green[]      PROGMEM = {"<table border=\"1\" bgcolor=\"#ccffcc\">"};
const prog_char ht_endtable[]         PROGMEM = {"</table>"};
const prog_char ht_table_header[]     PROGMEM = {"<th>"};
const prog_char ht_table_endheader[]  PROGMEM = {"</th>"};
const prog_char ht_table_row[]        PROGMEM = {"<tr>"};
const prog_char ht_table_endrow[]     PROGMEM = {"</tr>"};
const prog_char ht_table_col[]        PROGMEM = {"<td>"};
const prog_char ht_table_endcol[]     PROGMEM = {"</td>"};
const prog_char ht_painel[]           PROGMEM = {"Painel de Controle"};
const prog_char ht_temp_var[]         PROGMEM = {"SetPointTemperature"};
const prog_char ht_pid_header[]       PROGMEM = {"Controlador PID"};
const prog_char ht_status_header[]    PROGMEM = {"Monitor"};
const prog_char ht_submit[]           PROGMEM = {"<tr><td colspan=\"2\"><input type=\"submit\" name=\"option\" value=\"Enviar\"/></td></tr>"};

Source Code Part #2

//draw status table in html
void draw_status()
{
    WiServer.print_P(ht_table_green);
    WiServer.print_P(ht_table_row); WiServer.print("<caption>"); WiServer.print_P(ht_status_header); WiServer.print_P("</caption>"); WiServer.print_P(ht_table_endrow);
    WiServer.print ("<tr><td>Temperatura atual(&deg;C)</td><td>"); WiServer.print (dInputTemperature); WiServer.print_P(ht_table_endcol); WiServer.print_P(ht_table_endrow);      
    WiServer.print ("<tr><td>Cooler(0 a 255)</td><td>"); WiServer.print (fan_speed);  WiServer.print_P(ht_table_endcol); WiServer.print_P(ht_table_endrow);
    WiServer.print_P(ht_endtable);
}

//draw the main page in html
void drawMainPage()
{  
    char buffer[50];
   
    WiServer.print_P(htmlreader);
    WiServer.print_P(ht_script);
    WiServer.print_P(ht_endhead);
    WiServer.print_P(ht_body);
    WiServer.print_P(ht_form);
    WiServer.print_P(ht_table_gray);
    WiServer.print_P(ht_table_row); WiServer.print_P(ht_table_header); WiServer.print_P(ht_painel); WiServer.print_P(ht_table_endheader); WiServer.print_P(ht_table_endrow);
    sresprintf(buffer, dSetPointTemperature);
    writeInputHTM("Temperatura(&deg;C)", "text", "SetPointTemperature", buffer );
    WiServer.print_P(ht_table_row); WiServer.print_P(ht_table_header); WiServer.print_P(ht_pid_header); WiServer.print_P(ht_table_endheader); WiServer.print_P(ht_table_endrow);
    sresprintf(buffer, lightKc); writeInputHTM("Proporcional", "text", "KcLight", buffer);      
    sresprintf(buffer, lightTauI); writeInputHTM("Integral", "text", "TauILight", buffer);      
    sresprintf(buffer, lightTauD); writeInputHTM("Derivativo", "text", "TauDLight", buffer);          
    WiServer.print_P(ht_submit);
    WiServer.print_P(ht_endtable);
    WiServer.print_P(ht_endform);
  
    WiServer.print_P(ht_form);
    WiServer.print("<div id=\"status\">");
    draw_status();
    WiServer.print("</div>");
    WiServer.print_P(ht_endform);

    WiServer.print_P(ht_credits);
    WiServer.print_P(ht_endbody);
    WiServer.print_P(ht_endhtml);
}

//get the html parameter value by name 
boolean getDoubleParam(char* szParam, char* szName, double &output)
{
  char * pos;
  pos = strstr(szParam, szName);
  if( pos )
  {
    int name_len = strlen(szName);
    char * p_sep = strstr(pos, "&");
    char buffer[255];
    pos += name_len + 1;
    int value_len = p_sep - pos;
    strncpy(buffer, pos, value_len);
    buffer[value_len] = 0;     
    output = atof(buffer);
    return true;
  } 
  return false;
}

//called by http request, draw the main page and/or get the parameters
boolean sendSSREPage(char* URL)
{
  if( strcmp(URL, "/") == 0)
  {
      //if this is root call, call main page
      drawMainPage();
    return true;
  } else {
    if( strcmp(URL, "/status.htm") == 0)
    {
        draw_status();  
    }
    else
    {
            //detect vars and values
        double dValue;
      
            if (getDoubleParam(URL, "SetPointTemperature", dValue) dSetPointTemperature = dValue;
        if (getDoubleParam(URL, "KcLight", dValue))   lightKc   = dValue;      
        if (getDoubleParam(URL, "TauILight", dValue)) lightTauI = dValue;      
        if (getDoubleParam(URL, "TauDLight", dValue)) lightTauD = dValue;
            
        temperaturePID.SetTunings(lightKc, lightTauI, lightTauD);

        drawMainPage();
    }
    return true;
  }
 return false;  
}

//Startup funcion
void setup()
{  
#ifdef _USE_SERIAL
  Serial.begin(9600);
#endif //_USE_SERIAL 

  WiServer.init(sendSSREPage);

  //PID controls
  dSetPointTemperature = 45;
  
  temperatureServo.attach( PIN_OUTPUT_PWM_LIGHT );
  temperaturePID.SetMode(AUTO);
  temperaturePID.SetInputLimits(0, 100); //set input to temperature range
  temperaturePID.SetOutputLimits(0,255);
  temperaturePID.SetSampleTime(500);
  temperaturePID.SetTunings(lightKc, lightTauI, lightTauD);
 }

 //main loop
void loop()
{
    WiServer.server_task();

//read temperature from LM35
    int temperature = analogRead(PIN_INPUT_LM35);
    
    //convert from sensor range to Celcius
    double dTempCelcius = map(temperature, 0,1023, 0, 50000);
    dInputTemperature = (dTempCelcius /100.00)+2.6;
    //run PIDs
    temperaturePID.Compute();
    
    //set pwm value to temperature ligth
    int out_servo = map(dOutputTemperature, 0, 255, 0, 180);
    temperatureServo.write(out_servo);
    
    //set pwm value to fan speed
    double dOutputFan = dInputTemperature - dSetPointTemperature;
    fan_speed = 0;
    if (dOutputFan>2)
    {
      fan_speed = map(dOutputFan, 2, 6, 100, 255);
      fan_speed = constrain(fan_speed, 100, 255);
    }
    else
       fan_speed = 0;
    analogWrite( PIN_OUTPUT_PWM_FAN, fan_speed );
      
    if (--nLoopCount == 0);
    { 
#ifdef _USE_SERIAL      
      Serial.print("A/D temp.="); Serial.print(temperature);
      Serial.print("temp="); Serial.print(dInputTemperature);
      Serial.print(" Setpoint="); Serial.print(dSetPointTemperature);
      Serial.print(" light position="); Serial.print(out_servo);
      Serial.print(" fan speed="); Serial.println(fan_speed);
#endif //_USE_SERIAL
      nLoopCount = N_LOOP_FOR_SERIAL;
    }   
}

This is a really nice implementation of PID temperature control, coupled with control via WiFi. The layout and presentation of the project is well thought out and described. The code is clean, and well commented so that just about anyone could pick it up and understand it.

Well done, and very impressive!

:)

Thanks Buddy cr0sh,

Your opinion is very important to us...

If anyone would like to use the code or part of that, we could help as well...