Showing posts with label Arduino UNO. Show all posts
Showing posts with label Arduino UNO. Show all posts

Saturday, June 7, 2014

RTC Connection Code - Arduino Web Logger

Add the protocol to check and modify RTC time.

Get RTC Time

When you send "RTC0", it replies with T=<time stamp>
<time stamp> is only the sec part of POSIX time_t.

Set RTC Time

When you send "RTC<time stamp>", it replies with T=<time stamp>


Original Protocol

Get Last Data Block Number

When you send "GET0", it replies with N=<Data Block Number>

<Data Block Number>: "N=1" - "N=4294967295" unsigned 32 bit integer.
Of course, it will not get this big.

In the Data Log Code - Arduino Web Logger posting source code, SD_BASE_BLOCK becomes 0.

Get Data Block

When you send "GETn", it replies with
  • N = <Data Block Number>
  • T = <time stamp>
  • D = <data>
<time stamp> is only the sec part of POSIX time_t.  Do *1000 since we don't use millisecond part.  Of course it will be very difficult to see "T=4294967295" in our lifetime.

<data> is in +dd.ddd format.
  • "D=+12.345"
  • "D=" no data
  • "D=-99.999" termination.  That certain Data Block send is completely finished.

Digital Out

When turning on "DOn=ON",
when turning off "DOn=OFF".

n is channel number between 0 to 7.  There is no reply.

Digital In

When you send "DI", it replies with "bbbbbbbb".
b represents each input channel, and it becomes either '0' or '1'.

Analog In

When you send "AIn", it replies with "+dd.ddd".
n is channel number between 0 and 7, and "+dd.ddd" is from -10.000 to +10.000.


When you click [GetRTC] button display RTC time.



The incorrect RTC time gets set to current time when [SetRTC] button is clicked.




Now let's look at modified code.  ( Look at link below for complete code )

Fixed code from Arduino

#include <SPI.h>
#include <Ethernet.h>
#include <WebSocket.h>
#include <SoftwareSerial.h>
#include "Sd2Card.h"
#include <minWire.h> // use the modified library code from RTC Preparation posting
#include <minRTClib.h>

// ... OMIT ...

//-------------------------------------DS-1307
RTC_DS1307 rtc;

// ... OMIT ...

//------------------------------------------------ setup()
void setup() {
 Serial.begin(9600);
 Wire.begin();
 rtc.begin();

 // ... OMIT ...

//-----------------------------------------------
// Create DATA Logging Block
//-----------------------------------------------
void makeLogBlock(void) {
 byte i;
 byte *pB;
 float fdata;
 DateTime now;

 // ... OMIT ...

 //------------------------- buffer is full
 if(LogBlockSize >= LOG_BLOCK_SIZE) {
  pB = sdBuffer;
  *((uint32_t*)pB) = USED_MARK;  //Used Flag
  pB += 4;
  now = rtc.now();
  *((uint32_t*)pB) = now.unixtime(); //Time-stamp

  // ... OMIT ...

void printTOD(void) {
 DateTime now = rtc.now();
 Serial.print(now.year(), DEC);
 printProg(DASH_MSG);
 Serial.print(now.month(), DEC);
 printProg(DASH_MSG);
 Serial.print(now.day(), DEC);
 printProg(SPACE_MSG);
 Serial.print(now.hour(), DEC);
 printProg(COMMA_MSG);
 Serial.print(now.minute(), DEC);
 printProg(COMMA_MSG);
 Serial.println(now.second(), DEC);
}

// ... OMIT ...

void onData(WebSocket &socket, char* rxLn, byte rxSz) {
 uint32_t para;
 DateTime now;

 //-----------------------------------
 // GET
 //-----------------------------------
 if((rxLn[0] == 'G') && (rxLn[1] == 'E') && (rxLn[2] == 'T')) {
  *(rxLn + rxSz) = 0x00;
  para = atol(rxLn + 3);
  
  if(para) 
   upload_DataBlock(para + SD_BASE_BLOCK);
  else 
   upload_lastBlockNo();
 }

 //-----------------------------------
 // RTC
 //-----------------------------------
 else if((rxLn[0] == 'R') && (rxLn[1] == 'T') && (rxLn[2] == 'C')) {
  *(rxLn + rxSz) = 0x00;
  para = atol(rxLn + 3);

  // set RTC
  if(para) {
   rtc.adjust(DateTime(para));
   delay(100);
  }

  // send current RTC
  printLn[0] = 'T';
  printLn[1] = '=';
  now = rtc.now();
  ltoa((long int)(now.unixtime()), printLn+2, 10);
  WSsend(printLn, strlen(printLn));
 }
}


Fixed code from HTML/JS

        // ... OMIT ...
        
        // receive Time Stamp
        if(ar[0] == 'T') {
            timeStamp = ar[1];
            // Locale standard is GMT. 
            // In order to view as Korean time, we need to -9 hrs.
            tod.setTime((timeStamp - (9 * 3600)) * 1000); 
            
    // ... OMIT ...
    
    function getRTC() {
        clearDspPara();
        ws.send('RTC0');
    }
    
    function setRTC() {
        clearDspPara();
        var t = new Date();
        // GMT problem. Ths time +9 hrs.
        ws.send('RTC' + (t / 1000 + 9 * 3600));
    }
    
// ... OMIT ...

<button type="button" onclick="getRTC()">Get RTC</button>
<button type="button" onclick="setRTC()">Set RTC</button>

// ... OMIT ...



Full codes can be found in following links





Tuesday, June 3, 2014

Data Upload from Arduino - Arduino Web Logger

The Arduino WebSocket we used limits the size of packet we can send.  We are unable to receive entire 512 bytes Block, even if we divided it into 3, from the WebSocket.

Thus we are going to change the protocol so we are sending each Data item one-by-one.

Get Last Data Block Number

If we send "GET0", we receive N = <Data Block Number>.


<Data Block Number> 

is unsigned 32 bit integer ranging from "N = 1" to "N = 4294967295"
Of course it doesn't get that big.
In the source code from Data Log Code posting, SD_BASE_BLOCK (1024) is equal to 0.


Get Data Block

If we send "GETn", we receive
  • N = <Data Block Number>
  • T = <time stamp>
  • D = <data>
<time stamp> uses sec part from POSIX time_t.
Because we don't use the millisecond part, we need to *1000.
"T = 4294967295". Of course it will be hard to see this number in your lifetime.

<data> has +dd.ddd format.
  • "D=+12.345"
  • "D=": when there is no data
  • "D=-99.999": termination. Data Block has finished.

Original Protocol

Digital Out

  • Turning On: "DOn=ON"
  • Turning Off: "DOn=OFF"
n is equal to Channel number which is between 0 and 7, and there is no reply.

Digital In

When you send "DI" it replies with "bbbbbbbb".  b is equal to each input channel and it is either '0' or '1'.

Analog In

When you send "AIn" it replies with "+dd.ddd". n is equal to channel number which is between 0 and 7, and "+dd.ddd" ranges from -10.000 to +10.000.

Below I am only excerpting codes related to uploading.  Refer to the link at the bottom for entire code.
// -------------------------------- Data Uploading Function
void upload_lastBlockNo(void);
void upload_DataBlock(uint32_t reqBlock);
byte *setTerminator(byte *pD);

/*
 ----------------------------------------------------------
 D A T A  U P L O A D I N G  Function
 ----------------------------------------------------------
*/

/*
 0         1
 012345678901 last data block number 12 bytes send
 N=4294967295

 0         1
 012345678901 time stamp 12 bytes send
 T=4294967295

 012345678 Data 9 bytes send
 D=+99.999
 D=        empty
 D=-99.999 terminator
*/
void xsfBlockNo(uint32_t block) {
 ltoa(block - SD_BASE_BLOCK, printLn + 2, 10);
 printLn[0] = 'N';
 printLn[1] = '=';
 WSsend(printLn, strlen(printLn));
}

void upload_loastBlockNo(void) {
 xsfBlockNo(currBlock - 1);
}

void upload_DataBlock(uint32_t reqBlock) {
 byte *pS;
 int cnt;
 float data, *pF;

 // Store the currently logging Block into SD.
 // We might lose packets that ADAM-4017 is receiving.
 sdCard.writeBlock(currBlock, sdBuffer);

 printLn[1] = '=';

 if(sdCard.readBlock(reqBlock, sdBuffer)) {
  pS = sdBuffer;

  if(*((uint32_t*)pS) == USED_MARK) {
   pS += 4;

   // block number
   xsfBlockNo(reqBlock);

   // time stamp (milliseconds)
   ltoa(*((uint32_t*)pS), printLn + 2, 10);
   printLn[0] = 'T';
   WSsend(printLn, strlen(printLn));
   pS += 4;

   // data
   printLn[0] = 'D';
   pF = (float*)pS;
   cnt = 120;

   while(cnt--) {
    data = *pF++ / 10.0;

    if(data != -99.999)
     dtostrf(data, 7, 3, printLn + 2);
    else
     printLn[2] = 0x00;

    WSsend(printLn, strlen(printLn));
   }
  }

  printLn[0] = 'D';
  setTerminator((byte*)(printLn + 2));
  WSsend(printLn, strlen(printLn));
 }

 // Reload the block that we have stored.
 sdCard.readBlock(currBlock, sdBuffer);
}

//---------------------------------------------------------
byte *setTerminator(byte *pD) {
 *pD++ = '-';
 *pD++ = '9';
 *pD++ = '9';
 *pD++ = '.';
 *pD++ = '9';
 *pD++ = '9';
 *pD++ = '9';
 *pD++ = 0x00;
 return pD;
}

/*
  ---------------------------------------------------------
  W E B S O C K E T   h a n d l e r
  ---------------------------------------------------------
*/

void onData(WebSocket &socket, char* rxLn, byte rxSz) {
 uint32_t block;

 if((rxLn[0] == 'G') && (rxLn[1] == 'E') && (rxLn[2] == 'T')) {
  *(rxLn + rxSz) = 0x00;
  block = atol(rxLn + 3);
  
  if(block)
   upload_DataBlock(block + SD_BASE_BLOCK);
  else
   upload_lastBlockNo();
 }
}

HTML/JS code for Upload Testing



<!DOCTYPE html>
<html>
<head>
    <title>WebLogger 5.0</title>

    <script type="text/javascript" src="jquery-2.0.3.min.js"></script>
    <script>
        var ws;
        var rcvCnt;

        $(document).ready(function() {
            WebSocketConnect();
        });

        function WebSocketConnect() {
            var ar = new Array();
            var arSz, i;

            try {
                ws = new WebSocket('ws://192.168.219.16:80/');
                ws.onopen = function() {
                    status('Connected...');
                }

                ws.onclose = function() { status('Closed...'); }
                ws.onerror = function(evt) { status('Error ' + evt.data); }
                ws.onmessage = function(evt) {
                    ar = evt.data.split('=');

                    // receive Last Data Block Number  
                    if (ar[0] == 'N') {
                        $("#lastBlockNo").empty();
                        $("#lastBlockNo").append('Last Block Number:' + ar[1]);
                    }

                    // receive Time Stamp
                    if (ar[0] == 'T') {
                        $("#timestamp").empty();
                        $("#timestamp").append('TimeStamp(milliseconds):' + ar[1]);
                    }

                    // receive Data Block
                    if (ar[0] == 'D') {
                        $("#RcvLn").append('<p>' + ar[1] + '<p>');

                        if (ar[1] == '-99.999') {
                            $("#recordCnt").empty();
                            $("#recordCnt").append('Record Count:' + rcvCnt);
                        }

                        rcvCnt++;
                    }
                }
            } catch (exception) { status('Exception' + exception); }
        }

        function upload_lastBlockNo() {
            ws.send("GET0");
        }

        function upload_DataBlock() {
            var recNo = document.getElementById("getRecordNo").value;
            ws.send("GET" + recNo);
            rcvCnt = 0;
            $("#timestamp").empty();
            $("#recordCnt").empty();
            $("#RcvLn").empty();
        }

        function status(str) {
            $("#status").empty();
            $("#status").append(str);
        }

    </script>

</head>
<body>
    <p id="status"></p>
    <button type="button" onclick="upload_lastBlockNo()">Get Last Data Block Number</button>
    
    <br />
    
    <input type="text" id="getRecordNo" value="1">
    <button type="button" onclick="upload_DataBlock()">Get Data Block</button>
    
    <p id="lastBlockNo"></p>
    <p id="timestamp"></p>
    <p id="recordCnt"></p>
    <p id="RcvLn"></p>
</body>
</html>

Full codes can be found in following links





Monday, June 2, 2014

Data Log Code - Arduino Web Logger

What does this code do...?


  1. Reads ADAM-4017's channel 0 every 500 ms.
  2. Store the data as float in buffer.
  3. After repeating 60 * 2 = 120 times, a minute long Data Block has been created.  Write the Data Block to SD.

Can we use the SD library...?

In order to use SD FAT32 we absolutely have to use SD Library.  However, do we have enough memory?

Let's check the size of Arduino UNO's RAM size with using just following code.
void setup() {}
void loop() {}

RAM: 2,048

9#include <SPI.h>
71#include <SPI.h> <Ethernet.h>
262#include <SPI.h> <Ethernet.h> <WebSocket.h>
330#include <SPI.h> <Ethernet.h> <WebSocket.h> <SoftwareSerial.h>
330#include <SPI.h> <Ethernet.h> <WebSocket.h> <SoftwareSerial.h> <Time.h>
1,109#include <SPI.h> <Ethernet.h> <WebSocket.h> <SoftwareSerial.h> <Time.h> <SD.h>

It may seem as if there might not be any problem, but let's check what really happens in actual code from Arduino code - 4017 Arduino Gateway posting.

1,126#include <SPI.h> <Ethernet.h> <WebSocket.h> <SoftwareSerial.h> <Time.h> excluding <SD.h>

If we include setTime() to actually use <Time.h>, the memory becomes 1,163

And then we include <SD.h>...1,769
Open a file...1,863

We only have 212 bytes left.  Now we don't even have 512 bytes to create a Data Block.  Even if we don't create Data Block, how are we going to manage Stack & Heap with this memory?  What memory are we going to use for additional codes?
It might work sometimes... ( doesn't actually run on Arduino )

Therefore, we are going to give up on FAT32.


Now, are we going to use Arduino with bigger RAM?
http://arduino.cc/en/Main/ArduinoBoardMega2560
The biggest they have is 8K anyways.
If we have to improve on hardware continuously, why use Arduino?
Thus alternatively we are going to leave out FAT32 file system.

We are going to use 512 bytes block without using File.  Why do we need File if we are only going to log?

In Arduino library copy the following 4 files into the current sketch folder.
\arduino-1.5.6-r2\libraries\SD\src\utility\Sd2Card.cpp
Sd2Card.h
Sd2PinMap.h
SdInfo.h

Now we are only going to use the following 3 functions.
  • init()
  • readBlock()
  • writeBlock()

Current Time

Eventually we are going to use RTC; however, for this code we are using System Clock.
Library is available on the following site: http://www.pjrc.com/teensy/td_libs_Time.html
Download the Time.zip file unzip it and then Sketch > Import Library > Add Library.

You can also find it in Arduino Cookbook pg. 404.

Source Code

This is only part of the code.  I will post the link to the full code at the bottom.
#include <SPI.h>
#include <Ethernet.h>
#include <WebSocket.h>
#include <SoftwareSerial.h>
#include "Sd2Card.h"
#include <Time.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0F, 0x25, 0xC4 };
byte ip[] = { 192, 168, 219, 16 };

WebSocket wsServer;

// ------------------------------------------- SD Card
#define SD_BASE_BLOCK  1024 // 1024 is not a special #
#define LOG_BLOCK_SIZE 120

byte sdBuffer[512]; // SD card only writes in 512 bytes block.
                    // Thus we have to have 512 bytes.
byte sdStatus = 0;
byte LogblockSize = 0;
uint32_t currBlock;
long lastPollTime = 0;

Sd2Card sdCard;

void SDbegin(void);
void makeLogBlock(void);
void update_currBlock(void);

void setup() {
 Serial.begin(9600);

 Soft485.begin(9600);
 pinMode(DE485, OUTPUT);
 ADAM4017_config();

 Ethernet.begin(mac, ip);
 wsServer.registerConnectCallback(&onConnect);
 wsServer.registerDataCallback(&onData);
 wsServer.registerDisconnectCallback(&onDisconnect);
 wsServer.begin();
 delay(100);

 rx485ix = -1;

 // setTime(hours, minutes, seconds, days, months, years);
 // Time is not going to be right until we add RTC
 setTime(12, 0, 0, 11, 5, 14);
                               
 SDbegin();
 if(!sdStatus)
  printProgln(SD_FAIL_MSG);
}

void loop() {
 wsServer.listen();

 if(sdStatus) {
  if((millis() - lastPollTime) >= 500) {
   lastPollTime = millis();
   AdamPoll('0');
  }
 }

 AdamReceiver();
}

/*
 -----------------------------------------------------
 D A T A  B L O C K  M A K I N G
 -----------------------------------------------------
*/

// ---------------------------------------------------
// SD Card Initialize
// ---------------------------------------------------
void SDbegin(void) {
 byte *pB;

 sdStatus = 0;
 pinMode(10, OUTPUT); // SS

 if(!(sdCard.init(SPI_HALF_SPEED, 4)))
  return; // SD Card Initialization Failed

 if(!(sdCard.readBlock(SD_BASE_BLOCK, sdBuffer)))
  return; // SD Card Read Failed

 pB = sdBuffer;

 // No Used Flag. New SD Card!!
 if(*((uint32_t*)pB) != 0xaa55a55a) {
  printProgln(NEW_SD_MSG);
  *((uint32_t*)pB) = 0xaa55a55a;
  currBlock = SD_BASE_BLOCK + 1;
  *((uint32_t*)(pB + 4)) = currBlock;

  if(!(sdCard.writeBlock(SD_BASE_BLOCK, sdBuffer)))
   return; // SD Card Write Failed
 }

 else
  currBlock = *((uint32_t*)(pB + 4)); // First Free Block

 LogBlockSize = 0;
 sdStatus++;
 printCurrBlock(); // debugging purpose
}

// ---------------------------------------------------
// Create DATA Logging Block
// ---------------------------------------------------
void makeLogBlock(void) {
 byte i;
 byte *pB;
 float fdata;

 // --------- Reset buffer if it is the first time.
 if(!LogBlockSize) {
  pB = sdBuffer + 8; // insert Used Flag &
                     // time-stamp when finished
  fdata = -99.999 * 10.; // -99.999 means that there is no
                         // data. When we read them we do
                         // /10 thus we have to do *10.
  i = 120; // fill it in 120 times
  
  while(i--) {
   *((float*)pB) = fdata;
   pB += 4;
  }

  i = 24; // 24 bytes padding
  
  while(i--) {
   *pB = 0x00;
  }
 }
 /*
  ADAM-4017 returns +dd.ddd
  It uses fixed 3 decimal points.

  To convert decimal string to float:
  double atof(const char* nptr)
  http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga689c9d3c4c04463aa31d329937789d06

  But there is a problem. It only works with 2 decimal points.
  It rounds up third decimal point.
  Thus atof("12.345") will become 12.35.

  As a solution we do *10 then convert it to float.
  0123456    0123456
  +98.765 -> +987.65
 */

 // ----------------------- Insert Data into buffer
 //             0123456
 // rx485Ln[] ">+dd.ddd" <cr>
 rx485Ln[3] = rx485Ln[4];
 rx485Ln[4] = '.'; // *10

 pB = sdBuffer + 8 + (LogBlockSize * 4);
 *((float*)pB) = atof(rx485Ln);

 LogBlockSize++;

 // -------------------------------- buffer is full
 if(LogBlockSize >= LOG_BLOCK_SIZE) {
  pB = sdBuffer;
  *((uint32_t*)pB) = 0xaa55a55a; // Used Flag
  pB += 4;
  *((time_t*)pB) = now(); // Time Stamp

  sdCard.writeBlock(currBlock, sdBuffer);
  currBlock++;
  LogBlockSize = 0;
  update_currBlock(); // Fix the next free block location
 }
}

void update_currBlock(void) {
 byte *pB;
 byte status = 0;

 if(sdCard.readBlock(SD_BASE_BLOCK, sdBuffer)) {
  pB = sdBuffer;

  if(*((uint32_t*)pB) == 0xaa55a55a) {
   *((uint32_t*)(pB + 4)) = currBlock;

   if(sdCard.writeBlock(SD_BASE_BLOCK, sdBuffer)) {
    status++;
    printTOD(); // display time stamp
    printCurrBlock(); // display next free block
   }
  }
 }

 if(!status)
  // something is going terribly wrong
  printProgln(BLOCK_UPDATE_FAIL_MSG);
}

void printTOD(void) {
 tmElements_t tm;

 breakTime(now(), tm);

 Serial.print(tm.Year - 30); // size 1970
 printProg(DASH_MSG);
 Serial.print(tm.Month);
 printProg(DASH_MSG);
 Serial.print(tm.Day);
 printProg(SPACE_MSG);
 Serial.print(tm.Hour);
 printProg(COMMA_MSG);
 Serial.print(tm.Minute);
 printProg(COMMA_MSG);
 Serial.print(tm.Second);
}

void AdamReceiver(void) {
 char rxChr;

 if(Soft485.available()) {
  rxChr = Soft485.read();

  if(rxChr == '>')
   rx485ix = 0;
  
  else if(rx485ix >= 0) {
   if(rxChr == 0x0d) { //     0123456
    if(rx485ix == 7) { // >+dd.ddd<cr>
     wsServer.send(rx485Ln, 7);
     
     if(sdStatus)
      // when AD read value arrives from
      // ADAM-4017, log it
      makeLogBlock();
    }
    rx485ix = -1;
   }

   else {
    rx485Ln[rx485ix++] = rxChr;

    if(rx485ix > 7)
     rx485ix = -1;
   }
  }
 }
}

Full codes can be found in following links





Saturday, May 31, 2014

Components - Arduino Web Logger

Diagram of Arduino Web Logger Component

Let's log ADAM-4017's AD value every 500 ms.
We are going to make 512 bytes long block every 1 minute and write that block to the SD card in Ethernet Shield.

Data Block Component.

  • Used Flag: 4 bytes
    Set it to 0xaa55a55a.
  • Time Stamp: 4 bytes
    This is only the sec part of POSIX time_t value.  We are not going to use the millisecond, thus we are going to multiply the value by 1000 (*1000).  It is 32 bit unsigned integer.
  • Data: 4 bytes float value.  It is going to be sampled twice every second.
    1 minute measurement: 2 * 60 * 4 = 480 bytes
    Second is going to be offset.
    For example, The offset for second measurement in 27 second will be 8 + 27 * 8 + 4 = 228.
    Set the unmeasured values to -99.999.  When initializing the block set everything to -99.999.
  • Backup: 24 bytes, set it to Null.

Data File.

Logging is going to be one File.

In FAT32, the biggest file size is 512 * 232 = 241 = 2,199,023,255,552 = 2GB.  Even the smallest SD card is bigger than 2GB.  With 2GB, we can log for 2,199,023,255,552 / 512 / 60 / 24 / 365 = 8,217 years.  Thus memory is going to be full approximately after 8,000 years so we don't have to worry about that.

Data inside File are going to be in time order, which will allow us to retrieve Data Block from certain time very quickly using the simplest Binary Search.  However, adjusting the RTC clock due to whatever reasons will screw this up.  ( Getting deeper into this is too much for this posting, so I am going to skip it. )

Web DB.

Arduino cannot hold all this log data like Browser might hope.  If the browser wants to look through a year long data "very quickly" it's impossible.  Thus, browser is going to keep log data in its database and once in a while connect with Arduino to get the most recent log and update its DB.





Friday, May 30, 2014

Testing: Temporary Test Board(volume) + ADAM-4017 + RS485 + Arduino UNO + Ethernet Shield

Temporary Test Board + ADAM 4017 + RS485 + Arduino UNO + Ethernet Shield

I tested connecting volume to CH0 of ADAM-4017.
I used same source voltage.
Because input range is set to ±10V, if voltage is over +10V Arduino code shows it as +10V.
I didn't cut the board because I was too lazy it is easier to turn the knob with the board like that.

Schematic for volume attaching

I am attaching schematic for volume attaching just in case.
CH0 - CH5 on ADAM-4017 is differential, and CH6 - CH7 is single ended.
Thus, Vin0- has to be set to GND.





Monday, May 26, 2014

Input Polling from Arduino - Web/Arduino/Adam

Let's poll continuously from ADAM-4050 from Arduino using RS-485.
If there is an input change from ADAM, we send it to the browser.

We are going to add or modify the following from WSAdam.ino.

omit...
char rx485Ln[8];
char preAdamPollRcv[2] = { 0, 0 };
int rx485ix;
char bitMap[8];
byte wsConnect;
long lastPollTime;

void XsfAdamPollData(void);
void AdamPoll(void);
void AdamReceiver(void);
void hexToBit(char hex, int p);

omit...
void setup() {
 Serial.begin(9600);
 Soft485.begin(9600);
 pinMode(DE485, OUTPUT);
 wsConnect = 0;
 lastPollTime = 0;
 Ethernet.begin(mac, ip);

omit...
void loop() {
 wsServer.listen();
 if((millis() - lastPollTime) >= 500)
  AdamPoll(); // Poll every 0.5 seconds
 AdamReceiver();
}

omit...
void onConnect(WebSocket &socket) {
 Serial.println("OnConnect called");
 wsConnect++;
}

void onDisconnect(WebSocket &socket) {
 Serial.println("onDisconnect called");
 wsConnect = 0;
}

omit...
 //else if(rxLn[1] == 'I') {
  //digitalWrite(DE485, HIGH);
  //Soft485.write("$006");
  //Soft485.write(0x0D);
  //digitalWrite(DE485, LOW);
 //}
 else if(rxLn[1] == 'I')
  XsfAdamPollData();
}

void XsfAdamPollData(void) {
 hexToBit(preAdamPollRcv[0], 0);
 hexToBit(preAdamPollRcv[1], 4);
 wsServer.send(bitMap, 8);
}

void AdamPoll(void) {
 digitalWrite(DE485, HIGH);
 Soft485.write("$006");
 Soft485.write(0x0D);
 digitalWrite(DE485, LOW);

 lastPollTime = millis();
}

omit...
 if(rx485ix == 6) { // !ooii00<cr>
  //hexToBit(rx485Ln[2], 0);
  //hexToBit(rx485Ln[3], 4);
  //wsServer.send(bitMap, 8);

  // send only if previous input and current input is different.
  if((preAdamPollRcv[0] != rx485Ln[2]) || (preAdamPollRcv[1] != rx485Ln[3])) {
   preAdamPollRcv[0] = rx485Ln[2];
   preAdamPollRcv[1] = rx485Ln[3];
   if(wsConnect)
    XsfAdamPollData();
  }
 }

We are going to modify HTML/JS a little bit.
Use the following source when modifying: https://github.com/michelleseo/Arduino_Web/blob/master/WSAdam/WSAdam1_0.html

omit...
//        ws.onopen = function() { status('Connected...'); }
        ws.onopen = function() {
            status('Connected...');
            DI(); // Read Input once when connected.
        }

omit...
    <!--We do not need Update Button anymore.-->
    <!--<button type="button" onclick="DI()">IN Refresh</button>-->

Full codes can be found in following links







Sunday, May 25, 2014

Input Polling from a Browser - Web/Arduino/Adam

When Input Polling from a Browser...


The previous code updated input condition when [IN Refresh] button was clicked. Now we are going to change it so that it updates continuously.

We are going to fix the following parts:

omit...
<script>
    var ws;
    var poll = 0;
    var intervalTimer;
    var InLamp = new Array();
    for (var i = 0; i < 8; i++)
        InLamp[i] = '#DI' + (7 - i) + '_c';

omit...
//    ws.onopen = function() { status('Connected...'); }
    ws.onopen = function() {
        status('Connected...');
        // When WebSocket connects, polling starts.
        DI_pollEnable();
    }

omit...
//    function DI() { ws.send("DI"); }
    
    function DI_poll() {
        ws.send("DI");
        // Polls every 500 ms.
        intervalTimer = setTimeout(function() { DI_poll() }, 500);
    }
    
    function DI_pollEnable() { // Start polling
        if (!poll) {
            poll++;
            DI_poll();
        }
    }
    
    function DI_pollDisable() { // End polling
        clearTimeout(intervalTimer);
        poll = 0;
    }

omit...
    <!--<button type="button" onclick="DI()">IN Refresh</button>-->
    <button type="button" onclick="DI_pollEnable()">Input Refresh ON</button>
    <button type="button" onclick="DI_pollDisable()">OFF</button>
</body>

Full codes can be found in following links





Saturday, May 24, 2014

HTML / JavaScript - Web/Arduino/Adam

Screen Capture of WSAdam1_0.html

Let's simplify the HTML code.

Create
ws = new WebSocket('ws://192.168.219.16:80/');

Method
ws.send("DI"); // sends message
ws.close(); // Not used this time

Event
ws.onopen = function() { status('Connected...'); }
ws.onclose = function() { status('Closed...'); }
ws.onerror = function(evt) { status('Error ' + evt.data); }
ws.onmessage = function(evt) { // function that receives message

Reference: http://www.tutorialspoint.com/html5/html5_websocket.htm


<!DOCTYPE html>
<html>
    <head>
        <title>ADAM-4050 1.0</title>
        <!--It seemed to be a waste to download it everytime, 
        so I included the file.-->
        <script type="text/javascript" src="jquery.js"></script>
        <script>
            var ws;
            var InLamp = new Array();

            for (var i = 1; i < 8; i++)
                InLamp[i] = '#DI' + (7 - i) + '_c';

            // This is the code that browser calls when done
            // loading HTML/JS code.
            $(document).ready(function() {
                var i = 0;
                var html = 'ON <br />';

                // This is the code for making ON buttons in HTML.
                // The same code is repeated.
                for (i = 0; i < 8; i++)
                    html += '<button type="button" onclick="DO_On(' + i + ')">DO ' + i + '</button>';
                $('#OutONbutton').append(html);

                // This is the code for making OFF buttons in
                // HTML.
                html = 'OFF<br/>';
                for (i = 0; i < 8; i++)
                    html += '<button type="button" onclick="DO_Off(' + i + ')">DO ' + i + '</button>';
                $('#OutOFFbutton').append(html);

                // This is the code for drawing Input circles in
                // HTML.
                html = '<table><tr align="center">';
                for (i = 0; i < 7; i++)
                    html += '<td><svg height="50" width="50"><circle id="DI' + i + '_c" cx="25" cy="25" r="20" stroke="black" fill="white"/></svg></td>';
                html += '</tr><tr align="center"><td>DI 0</td> <td>DI 1</td> <td>DI 2</td> <td>DI 3</td> <td>DI 4</td> <td>DI 5</td> <td>DI 6</td></tr></table>';
                $('#InputLamp').append(html);

                // Now connect the WebSocket.
                WebSocketConnect();
            });

            function WebSocketConnect() {
                try {
                    ws = new WebSocket('ws://192.168.219.16:80/');

                    ws.onopen = function() {
                        status('Connected...');
                    }
                    ws.onclose = function() {
                        status('Closed...'); 
                    }
                    ws.onerror = function(evt) {
                        status('Error ' + evt.data); 
                    }

                    ws.onmessage = function(evt) {
                        for (var i = 1; i < 8; i++) {
                            if (evt.data[i] == '0')
                                $(InLamp[i]).css("fill", "white");
                            else
                                $(InLamp[i]).css("fill", "red");
                        }
                    };
                }
                catch (exception) {
                    status('Exception ' + exception);
                }
            }

            function DO_On(chn) { ws.send('DO' + chn + '_ON'); }
            function DO_Off(chn) { ws.send('DO' + chn + '_OFF'); }
            function DI() { ws.send("DI"); }
            function status(str) { $("#status").append(str); }
            
        </script>
    </head>
    <body>
        <p id="status">Connection Status: </p>
        <p id="OutONbutton"></p>
        <p id="OutOFFbutton"></p>
        <p id="InputLamp"></p>
        <button type="button" onclick="DI()">IN Refresh</button>
    </body>
</html>


Full codes can be found in following links









Friday, May 23, 2014

Arduino Code(2) - Web/Arduino/Adam

Let's make a very simple protocol to use for WebSocket.

For Digital Out:
     Turning on: "DOn=ON"
     Turning off: "DOn=OFF"
     n: channel number 0-7

For Digital In:
     If you send "DI" it returns "bbbbbbbb".
     'b' is each input channel. It either becomes '0' or '1'.


#include <SPI.h>
#include <Ethernet.h>
#include <WebSocket.h>
#include <SoftwareSerial.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0F, 0x25, 0xC4 };
byte ip[] = { 192, 168, 219, 16 };
WebSocket wsServer;

int DE485 = 2;
SoftwareSerial Soft485(3, 4);

char rx485Ln[8];
int rx485ix;
char bitMap[8];

void AdamReceiver(void);
void hexToBit(char hex, int p);

void setup() {
 Serial.begin(9600); // Arduino Sketch Serial Monitor

 Soft485.begin(9600);
 pinMode(DE485, OUTPUT);

 Ethernet.begin(mac, ip);
 wsServer.registerConnectCallback(&onConnect);
 wsServer.registerDataCallback(&onData);
 wsServer.registerDisconnectCallback(&onDisconnect);
 wsServer.begin();
 delay(100);

 rx485ix = -1;
}

void loop() {
 wsServer.listen();
 AdamReceiver();
}

void onConnect(WebSocket &socket) {
 Serial.println("onConnect called");
}

void onDisconnect(WebSocket &socket) {
 Serial.println("onDisconnect called");
}

void onData(WebSocket &socket, char* rxLn, byte rxSz) {
 // Digital Out
 // It is either "DOn=ON" or "DOn=OFF". n = "0" - "7"
 // It would really be nice if we can parse "DO", "=ON",
 // "=OFF"; however, we are not focusing on this.
 // In "DO" we are only checking for "O" and for "=ON" & 
 // "=OFF", we will only check for "N".
 if(rxLn[1] == 'O') {
  // RS-485 will be in TX mode when DE is high
  digitalWrite(DE485, HIGH);
  // We send to ADAM-4050 "#001n01"<cr> if ON or
  // "#001n00"<cr> if OFF.
  Soft485.write("#001");
  Soft485.write(rxLn[2]); // n = "0" - "7"
  Soft485.write('0');
  if(rxLn[5] == 'N')
   Soft485.write('1');
  else
   Soft485.write('0');
  Soft485.write(0x0D);
  digitalWrite(DE485, LOW); // When finished, DE must be low
 }

 // Digital In
 // We are only looking for "I" in "DI"
 else if(rxLn[1] == 'I') {
  digitalWrite(DE485, HIGH);
  Soft485.write("$006"); // We send "$AA6"<cr> to ADAM-4050
  Soft485.write(0x0D);
  digitalWrite(DE485, LOW);
 }
}

// ADAM-4050 sends "!ooii00"<cr> as reply.
// The message starts with "!" and ends in <cr>, and there are 6
// bytes in between. The input data are the "ii". For more 
// information look at ADAM - Web/Arduino/Adam posting.
void AdamReceiver(void) {
 char rxChr;

 if(Soft485.available()) {
  rxChar = Soft485.read();
  if(rxChr == '!')
   rx485ix = 0; // message starts with "!"
  else if(rx485ix >= 0) {
   if(rxChr == 0x0d) { // message ends with <cr>
    //!ooii00<cr> Between "!" and <cr> there needs to be
    // 6 bytes
    if(rx485ix == 6) {
     hexToBit(rx485Ln[2], 0);
     hexToBit(rx485Ln[3], 4);
     wsServer.send(bitMap, 8);
    }
    rx485ix = -1; // it needs to be sync to "!" again
   }
   else {
    rx485Ln[rx485ix++] = rxChr;
    if(rx485ix > 6)
     // it cannot be greater than 6 bytes.
     // Re-sync to "!".
     rx485ix = -1;
   }
  }
 }
}

// We send "bbbbbbbb" to WebSocket. b is equal to each input channel, and it becomes either '0' or '1'.
void hexToBit(char hex, int p) {
 int i;

 if(hex >= 'A')
  hex = hex - 0x41 + 10;
 else
  hex -= '0';
 hex ^= 0x0f;

 for(i = 0; i < 4; i++) {
  if(hex & 0x08)
   bitMap[p + i] = '1';
  else
   bitMap[p + i] = '0';
  hex <<= 1;
 }
}



The code can be downloaded from:
https://github.com/michelleseo/Arduino_Web/blob/master/WSAdam/WSAdam.ino





Thursday, May 22, 2014

Arduino Code(1) - Web/Arduino/Adam

The basic tutorial for WebSocket:
http://www.tutorialspoint.com/html5/html5_websocket.htm

The code that I used for WebSocket is from the following site:
http://jlectronique.org/WebsocketsEN.htm

Now back to the original TOPIC.

Code for Arduino WebSocket


include
#include <SPI.h>
#include <Ethernet.h>
#include <WebSocket.h> 

Ones that must be defined
/* Mac Address
  Each Ethernet Shield has different mac address. 
  Check your board.
*/
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0F, 0x25, 0xC4 };

/* IP Address
  Arduino becomes the server, and it needs fixed IP.
  It doesn't necessarily need global IP, it just needs fixed IP.
  Most LAN connections use DHCP, so use an Local IP address that 
  DHCP doesn't use.
  HOW?
     Open command promp and type ipconfig /all
     Check the IP Address for the current PC. 
     Keeping the same Subnet Mask, try pinging IP addresses.
     If you find the IP address that doesn't answer the ping,
     use that IP address.
     If someone around you gets pissed off because their internet 
     doesn't work, change the IP address quickly and quietly before
     he notices you.
*/
byte ip[] = { 192, 168, 100, 11 };

Create WebSocket Callback
/*
  There are 3 Callbacks in WebSocket.h.
  You should be able to guess what they are for just by looking at
  there names.

    typedef void DataCallback(WebSocket &socket, char* socketString, byte frameLength);
    typedef void Callback(WebSocket &socket);

    void registerDataCallback(DataCallback *callback);
      // Called when received Data
    void registerConnectCallback(Callback *callback);
      // when WebSocket is connected
    void registerDisconnectCallback(Callback *callback);
      // when WebSocket is disconnected
*/
WebSocket wsServer;
void onData(WebSocket &socket, char* socketString, byte frameLength) { }
void onConnect(WebSocket &socket) { }
void onDisconnect(WebSocket &socket) { }

Code inside setup()
void setup() {
 Ehternet.begin(mac, ip);
 wsServer.registerConnectCallback(&onConnect);
 wsServer.registerDataCallback(&onData);
 wsServer.registerDisconnectCallback(&onDisconnect);
 wsServer.bein();
 // Give Ethernet Shield some time to get ready.
 delay(100);
}

Code inside loop()
void loop() {
 wsServer.listen();

 if(wsServer.isConnected()) {
  wsServer.send("abc123", 6);
 }
}

/*
  listen() needs to be called continuously.
  If data is received onData() gets called.
  If you want to send data send(char *str, byte length);
*/

RS-485 Connection Code


include
#include <SoftwareSerial.h>

Ones that must be defined
SoftwareSerial Soft485(3, 4);
int DE485 = 2;

/*
  Pin 3: RX
  Pin 4: TX
  Pin 2: DE This was declared in 
      RS485 Shield - Web/Arduino/Adam posting.
*/

Code inside setup()
void setup() {
 Soft485.begin(9600);
 pinMode(DE485, OUTPUT);
}

/*
  ADAM-4050 is 9600 bps. (ADAM - Web/Arduino/Adam posting)
*/






Wednesday, May 21, 2014

RS485 Shield - Web/Arduino/Adam

I have purchased following RS-485 Shield.
https://www.tindie.com/products/Conceptinetics/rs485-rs422-shield-for-arduino/





RS-485 Shield






































There was no manual ( just like I expected :( ), and shockingly schematic was not included.
Furthermore I couldn't even find the information about the manufacturer.  I sent an email to the address on the PCB ( info@conceptinetics.com.hk ), and got the following:



RS-485 Schematic

This was unexpected.  Guess I should be thankful that they replied.





Anyways...

Although it is not shown on the schematic, there is a Jumper to allow the use of Soft Serial Port.  There are 3 Jumpers; however, Arduino Uno only has one Serial Port so we are going to use Soft Serial Port.

     EN
     TX-io
     RX-io    Set them for Jumper



Pin Settings:

  • Pin 3: RX
  • Pin 4: TX
  • Pin 2: DE


DE is only set to 'H' when transmitting, and at other times ( receiving ) DE is set to 'L'.  At other times Soft Serial is same as general serial, thus we don't have to care.

In Ethernet Shield, Pin 4 is used as SD_CS; however for now we are not using SD thus we don't have to worry about it.  If you really want to use SD, you have to move TX to somewhere around Pin 5 which involves cutting and soldering.  Very annoying...





Tuesday, May 20, 2014

UNO, Ethernet Shield - Web/Arduino/Adam

UNO

I used UNO, the most basic Arduino model.

The one I purchased is from the following Korean site, and it is not the original version.
http://www.nulsom.com/
You could purchase it from other sites ( a lot of them sell it ).
http://eleparts.co.kr/EPX7R9BJ
http://devicemart.co.kr/goods/view.php?seq=38296

This machine gives off a lot of heat ( enough heat to make me worry about the PCB ).

Arduino UNO from http://www.nulsom.com/





Ethernet Shield


This is an original, I purchased it from plughouse (Korean site).
http://www.plughouse.co.kr/shop/goods/Goods_view.php?G_code=102705472330

Ethernet Shield





UNO + Ethernet Shield + RS-485 Shield

Let's stack all 3.

Arduino UNO + Ethernet Shield + RS-485 Shield





Photo with ADAM!

Arduino UNO + Ethernet Shield + RS-485 Shield, Adam-4050, LED & Switches