Aventuras y Desventuras de un hobbyist

Aventuras y Desventuras de un hobbyist....

Arduino + WebSockets II

Please read the first part of this serie: http://yopero-tech.blogspot.com/2012/02/arduino-websockets.html

This project describes how to use WebSockets to display data  taken from Arduino and broadcast it to any Browser with WebSocket support. Test your browser here: http://websocket.org/echo.html

First of all we need to decide what data to display and  what to control in Arduino from the  web page .

In this example I am going to control 3 remote controlled relays that you can buy at your hardware store and I want to display the values from 2 temperature sensors.(DS18S20)

This project is composed out of 3 main parts of software apart from the hardware(Arduino Board):


  1. WebSocket  Server:
    1. Python
    2. Autobahn
      1. Twisted
        1. PySerial
  2. MCU (Micro Controller Unit)
    1. Arduino Board(Vinciduino in my case).
    2. Arduino IDE or AVR studio.
  3. Client:
    1. Any web server, I use xampp or python to test as localhost.
             

The WebSocket server I setup is run under Windows XP:

First thing is to install Python in my case I have used v 2.7

On Python I have installed the following packages:

If you have installed all of the above, download the files of the project from here:
Serial2WS.rar
Now that we have the WS server I will jump to the Arduino side of the project, as said above I will control 3 lights interactivity and I will get real time data from 2 temperature sensors.
To accomplish that I have hacked the remote control and connected it to the Arduino
You can see my setup in the video at the end of the post.


The Arduino sketch for my setup at the end of this entry( too long to put  in the middle of the entry)

This sketch sends the values of the 2 temperature sensors with id = 1 or 2 in format JSON
Id  \t value
I would say that this is the most important part of the project, send JSON formated data from arduino to python and then python to digest it and broadcast via WS. 
   celsius = (float)raw / 16.0;
   
   //Sending JSON 
    Serial.print(id);
    Serial.print("\t");
    printFloat(celsius , 0);    
    Serial.println();
    //finish sending JSON
later this data is read by the WebSocket server and dispatch to the browser.
def lineReceived(self, line):
      try:
         ## parse data received from MCU
         ##
         data = [int(x) for x in line.split()]

         ## construct PubSub event from raw data
         ##
         evt = {'id': data[0], 'value': data[1]}

         ## publish event to all clients subscribed to topic
         ##
         self.wsMcuFactory._dispatchEvent("http://example.com/mcu#analog-value", evt)

         log.msg("Analog value: %s" % str(evt));
      except ValueError:
         log.err('Unable to parse value %s' % line)


In order to display the values given by Arduino and received by the Websocket server I am  using Smoothie Charts; is a really small charting library designed for live streaming data.( http://smoothiecharts.org/)






The final results as you see in the video and screenshots.






In the future I will try to load the WebSocket(C++)client directly to a Arduino with an ethernet shield to avoid the use of a PC, but I am not sure how it will impact the infrastructure side of my LAN(opening ports).


Click "Mas informacíon" to preview the Arduino code





#include 
int id = 1;
// OneWire DS18S20, DS18B20, DS1822 Temperature Example
//
// http://www.pjrc.com/teensy/td_libs_OneWire.html
//
// The DallasTemperature library can do all this work for you!
// http://milesburton.com/Dallas_Temperature_Control_Library

OneWire  ds(12);  // on pin 10

void setup(void) {
  Serial.begin(9600);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
}

void loop(void) {
  if (Serial.available()) {
      
    //read serial as a character
    char ser = Serial.read();

    //NOTE because the serial is read as "char" and not "int", the read value must be compared to character numbers
    //hence the quotes around the numbers in the case statement
    switch (ser) {
      case '0':
        digitalWrite(2, HIGH);
        digitalWrite(5, HIGH);
        
        delay(500);
t>        digitalWrite(2, LOW);
        digitalWrite(5, LOW);
        break;
      case '1':
        digitalWrite(2, HIGH);
        digitalWrite(7, HIGH);
        
        delay(500);
        digitalWrite(2, LOW);
        digitalWrite(7, LOW);
        break;
      case '2':
        digitalWrite(2, HIGH);
        digitalWrite(6, HIGH);
        
        delay(500);
        digitalWrite(2, LOW);
        digitalWrite(6, LOW);
        break;
      case '3':
        digitalWrite(2, HIGH);
        digitalWrite(4, HIGH);
        
        delay(500);
        digitalWrite(2, LOW);
        digitalWrite(4, LOW);
        break;
      case '4':
        digitalWrite(3, HIGH);
        digitalWrite(5, HIGH);
        
        delay(500);
        digitalWrite(3, LOW);
        digitalWrite(5, LOW);
        break;
      case '5':
        digitalWrite(3, HIGH);
        digitalWrite(7, HIGH);
        
        delay(500);
        digitalWrite(3, LOW);
        digitalWrite(7, LOW);
        break;   
    }
  }
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  
  if ( !ds.search(addr)) {
    //Serial.println("No more addresses.");
    //Serial.println();
    ds.reset_search();
    delay(25);//250
    return;
  }
  
  //Serial.print("ROM =");
  for( i = 0; i < 8; i++) {
    //Serial.write(' ');
    //Serial.print(addr[i], HEX);
  }

  if (OneWire::crc8(addr, 7) != addr[7]) {
      //Serial.println("CRC is not valid!");
      return;
  }
  //Serial.println();
 
  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:
      //Serial.println("  Chip = DS18S20");  // or old DS1820
      type_s = 1;
      break;
    case 0x28:
      //Serial.println("  Chip = DS18B20");
      type_s = 0;
      break;
    case 0x22:
      //Serial.println("  Chip = DS1822");
      type_s = 0;
      break;
    default:
      //Serial.println("Device is not a DS18x20 family device.");
      return;
  } 

  ds.reset();
  ds.select(addr);
  ds.write(0x44,1);         // start conversion, with parasite power on at the end
  
  delay(750);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.
  
  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad

  //Serial.print("  Data = ");
  //Serial.print(present,HEX);
  //Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    //Serial.print(data[i], HEX);
    //Serial.print(" ");
  }
  //Serial.print(" CRC=");
  //Serial.print(OneWire::crc8(data, 8), HEX);
  //Serial.println();

  // convert the data to actual temperature

  unsigned int raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw << 3; // 9 bit resolution default
    if (data[7] == 0x10) {
      // count remain gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    if (cfg == 0x00) raw = raw << 3;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw << 2; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw << 1; // 11 bit res, 375 ms
    // default is 12 bit resolution, 750 ms conversion time
  }
  
  
   celsius = (float)raw / 16.0;
   
   //Sending JSON 
    Serial.print(id);
    Serial.print("\t");
    printFloat(celsius , 0);    
    Serial.println();
    //finish sending JSON
   if (id == 0){
  id = 1;
  }else if(id == 1){
  id = 0;
  }
 
  
  
  }
  
void printFloat(float value, int places) {
  // this is used to cast digits and print as ASCII
  int digit;
  float tens = 0.1;
  int tenscount = 0;
  int i;
  float tempfloat = value;

    // make sure we round properly. this could use pow from , but doesn't seem worth the import
  // if this rounding step isn't here, the value  54.321 prints as 54.3209

  // calculate rounding term d:   0.5/pow(10,places)  
  float d = 0.5;
  if (value < 0)
    d *= -1.0;
  // divide by ten for each decimal place
  for (i = 0; i < places; i++)
    d/= 10.0;    
  // this small addition, combined with truncation will round our values properly 
  tempfloat +=  d;

  // first get value tens to be the large power of ten less than value
  // tenscount isn't necessary but it would be useful if you wanted to know after this how many chars the number will take

  if (value < 0)
    tempfloat *= -1.0;
  while ((tens * 10.0) <= tempfloat) {
    tens *= 10.0;
    tenscount += 1;
  }


  // write out the negative if needed
  if (value < 0)
    Serial.print('-');

  if (tenscount == 0)
    Serial.print(0, DEC);

  for (i=0; i< tenscount; i++) {
    digit = (int) (tempfloat/tens);
    Serial.print(digit, DEC);
    tempfloat = tempfloat - ((float)digit * tens);
    tens /= 10.0;
  }

  // if no places after decimal, stop now and return
  if (places <= 0)
    return;

  // otherwise, write the point and continue on
  Serial.print('.');  

  // now write out each decimal place by shifting digits one by one into the ones place and writing the truncated value
  for (i = 0; i < places; i++) {
    tempfloat *= 10.0; 
    digit = (int) tempfloat;
    Serial.print(digit,DEC);  
    // once written, subtract off that digit
    tempfloat = tempfloat - (float) digit; 
  }
}




3 comentarios:

  1. Me ha parecido muy interesante esta entrada, en especial por el conjunto de tecnologías que estás utilizando y el gran rendimiento que permiten.

    Así que he intentado reproducirlo, pero me he encontrado con un problema.

    Cuando ejecuto: sudo python2 serial2ws.py
    todo va bien hasta que se produce un excepción que termina con:
    File "/usr/lib/python2.7/site-packages/Twisted-12.1.0-py2.7-linux-armv6l.egg/twisted/protocols/basic.py", line 564, in dataReceived
    why = self.lineReceived(line)
    File "serial2ws.py", line 91, in lineReceived
    self.wsMcuFactory._dispatchEvent("http://example.com/mcu#analog-value", evt)
    exceptions.AttributeError: WsMcuFactory instance has no attribute '_dispatchEvent'

    y no consigo dar con cual es el problema.

    Muchas gracias.

    ResponderEliminar
  2. Como continuación con el post anterior, he localizado el error como un cambio en el método para publicar los eventos, que ha pasado de ser '_dispatchEvent' a 'dispatch'. Con lo que ya está funcionando correctamente.

    De nuevo muchas gracias por esta gran combinación de hardware y software.

    ResponderEliminar
  3. Anónimo

    Siento mucho no poder haber respondido antes tu pregunta tengo el blog un poco abandonado, me alegro que hayas encontrado la solución.

    Si no te importa coméntanos en que estas utilizando ws+arduino.

    ResponderEliminar