Showing posts with label Web. Show all posts
Showing posts with label Web. Show all posts

Wednesday, June 4, 2014

Web SQL Database - Arduino Web Logger

The most basic HTML5.0 - Web SQL Database Tutorial.
http://www.tutorialspoint.com/html5/html5_web_sql.htm

1. Set DB's Name and Size.

    var db = openDatabase('WebLogger8', '1.0', 'Web Logger', 512 * 1024);

DB's name is WebLogger8 and the size is 512K bytes.

2. Create 2 DB Table. One with Index and the other with Data.

    db.transaction(function(tx) {
        tx.executeSql('CREATE TABLE IF NOT EXISTS LogIndex(block, timeStamp)');
        tx.executeSql('CREATE TABLE IF NOT EXISTS LogData(block, seq, data)');
    });

  • LogIndex: this is an index table with 2 columns
    1. block number &
    2. timeStamp
  • LogData: this is an data table
    1. block number,
    2. index within the block (500 ms = index 0, 1000 ms = index 1...)
    3. actual data value

3. Add Data Block to DB Table.

    db.transaction(function(tx) {
        for (i = 0; i < rcvCnt; i++) {
            tx.executeSql('INSERT INTO LogData VALUES(?, ?, ?)', [blockNo, i, data[i]]);
        }
    });

Append the data table.  You must write to DB after you have received entire Data Block.  If you try to write data values every time you receive it, you will lose a lot of data.  The confusing thing is that you receive correct number of data; however, you don't see the actual values anywhere.

We don't define data type for each column.  This makes things simpler and confusing at the same time.  When you actually write to DB the type gets defined.  I am assuming that block becomes String, seq becomes Number, and data becomes String.

    db.transaction(function(tx) {
        tx.executeSql('INSERT INTO LogIndex VALUES(?, ?)', [blockNo, timeStamp]);
    });

Lastly append the index.
block and timeStamp are both String type.

Caution:
    db.transaction(function(tx) {
        tx.executeSql('INSERT INTO LogData VALUES(?, ?, ?)', [blockNo, i, data[i]]);
        tx.executeSql('INSERT INTO LogIndex VALUES(?, ?', [blockNo, timeStamp]);

Inside one db.transaction, do not append DB twice.  Theoretically this is not a problem; however, if you continue to use that statement it causes error so let's avoid it.

4. Check DB Table.

    db.transaction(function(tx) {
        // Check all index.
        tx.executeSql('SELECT * FROM LogIndex', [], function(tx, results) {
            len = results.rows.length;

            for (i = 0; i < len; i++) {
                // we don't use millisecond part thus do *1000
                tod.setTime(results.rows.item(i).timeStamp * 1000);
                // if you do toLocaleString() to
                // day-time format variable, it returns a string
                // with a language sensitive representation of this
                // date.  There are many others. 
                // http://www.w3schools.com/jsref/jsref_obj_date.asp
                $("#dumpPara").append('' + results.rows.item(i).block + ',' + tod.toLocaleString() + '

');
            }
        }, null);
    });

    db.transaction(function(tx) {
        // search DB with block
        tx.executeSql('SELECT * FROM LogData WHERE block="' + blockNo + '"', [], function(tx, results) {
            len = results.rows.length;

            for (i = 0; i < len; i++) {
                // There is no guarantee that DB will read in order we
                // wrote. Thus we index it inside block.
                data[results.rows.item(i).seq] = results.rows.item(i).data;
            }

            for (i = 0; i < len; i++) {
                $("#dumpPara").append('

' + data[i] + '

');
            }
        }, null);
    });






5. For Less Confusion while Debugging...

  1. When you try to change the name of DB, close all Chrome windows and re-open it.  Otherwise new DB Table does not get created.
  2. If HTML file is not in the same Folder path, close all Chrome windows and re-open it.  If you don't do this WebSocket doesn't get connected.

Get the last SD logged Block Number from Arduino.

Click the [Get Last Data Block Number] button.



Enter the Block Number and Upload that certain Data Block from Arduino SD.

Enter the Block Number and click [Get Data Block] button.

If you upload, it gets inserted into both Index table and Log table.


Check Index Table.

Click [View Index] button.
It is displayed in Block, Time Stamp order.


Enter Data Block number to check Data Table.

Enter Block Number and click [View Data] button.

You will be able to see 120 values from that Block.  Only the data will be shown.


Below is HTML/JS Code

The link to the code is posted in the end.
<!DOCTYPE html>
<html>
    <head>
        <title>WebLogger 6.3</title>
        <script type="text/javascript" src="jquery-2.0.3.min.js"></script>
        <script>
            var ws;
            var blockNo, rcvCnt, timeStamp;
            var data = new Array();
            var tod = new Date();

            var db = openDatabase('WebLogger8', '1.0', 'Web Logger', 512 * 1024);

            $(document).ready(function() {
                db.transaction(function(tx) {
                    tx.executeSql('CREATE TABLE IF NOT EXISTS LogIndex(block, timeStamp)');
                    tx.executeSql('CREATE TABLE IF NOT EXISTS LogData(block, seq, data)');
                });

                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') {
                            blockNo = ar[1];
                            $("#blockNoPara").empty();
                            $("#blockNoPara").append('Block Number:' + blockNo);
                        }

                        // receive Time Stamp
                        if (ar[0] == 'T') {
                            timeStamp = ar[1];
                            tod.setTime(timeStamp * 1000);
                            $("#timestampPara").empty();
                            $("#timestampPara").append(tod.toLocaleString());
                        }

                        // receive Data Block
                        if (ar[0] == 'D') {
                            data[rcvCnt] = ar[1];

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

                                if (blockNo != 0) {
                                    db.transaction(function(tx) {
                                        if (rcvCnt > 120) rcvCnt = 120;
                                        for (i = 0; i < rcvCnt; i++) {
                                            tx.executeSql("INSERT INTO LogData VALUES(?, ?, ?)", [blockNo, i, data[i]]);
                                        }
                                    });

                                    db.transaction(function(tx) {
                                        tx.executeSql("INSERT INTO LogIndex VALUES(?, ?)", [blockNo, timeStamp]);
                                    });
                                }
                            }
                        }
                    }
                } catch (exception) { status('Exception' + exception); }
            }

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

            function upload_DataBlock() {
                blockNo = document.getElementById("DataBlockNo").value;
                ws.send("GET" + blockNo);
                blockNo = 0;
                rcvCnt = 0;
                $("#timestampPara").empty();
                $("#recordCntPara").empty();
                $("#dumpPara").empty();
            }

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

            function viewIndex() {
                var len, i;

                db.transaction(function(tx) {
                    tx.executeSql("SELECT * FROM LogIndex", [], function(tx, results) {
                        len = results.rows.length;
                        clearDspPara();

                        for (i = 0; i < len; i++) {
                            tod.setTime(results.rows.item(i).timeStamp * 1000);
                            $("#dumpPara").append('<p>' + results.rows.item(i).block + ',' + tod.toLocaleString() + '<p>');
                        }
                    }, null);
                });
            }

            function viewData() {
                var len, i;
                blockNo = document.getElementById("DataBlockNo").value;

                db.transaction(function(tx) {
                    tx.executeSql('SELECT * FROM LogData WHERE block="' + blockNo + '"', [], function(tx, results) {
                        len = results.rows.length;

                        if (len > 120) len = 120;

                        clearDspPara();

                        for (i = 0; i < len; i++) {
                            data[results.rows.item(i).seq] = results.rows.item(i).data;
                        }

                        for (i = 0; i < len; i++) {
                            $("#dumpPara").append('<p>' + data[i] + '<p>');
                        }
                    }, null);
                });
            }

            function clearDspPara() {
                $("#blockNoPara").empty();
                $("#timestampPara").empty();
                $("#recordCntPara").empty();
                $("#dumpPara").empty();
            }
        </script>
    </head>

    <body>
        <p id="status"></p>
        <button type="button" onclick="upload_lastBlockNo()">Get Last Data Block Number</button>
        <br />
        
        <input type="text" id="DataBlockNo" value="1">
        <button type="button" onclick="upload_DataBlock()">Get Data Block</button>
        <br />
        
        <button type="button" onclick="viewIndex()">View Index</button>
        <button type="button" onclick="viewData()">View Data</button>
        <p id="blockNoPara"></p>
        <p id="timestampPara"></p>
        <p id="recordCntPara"></p>
        <p id="dumpPara"></p>
        
    </body>
</html>

Full codes can be found in following links





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





Monday, May 19, 2014

ADAM - Web/Arduino/Adam

ADAM-4050

This is the most basic Digital Input / Output ADAM series module. ( also the cheapest* )


Download the manual "ADAM-4000_manual_V20.pdf" from the above link. 
( This manual is for the entire 4000 series modules. ADAM-4050 starts on page 74 )


ADAM has 8 Outputs and 7 Inputs.
I have attached 8 LED and 7 DIP Switches ( BEST for testing ). 

ADAM 4050 module w/ 8 LED & 7 DIP Switch
































Circuit?

Very simple. ( It's in manual pg.75 )
I set INIT* pin to GND because I didn't want to confuse 485 Address. This sets Address = 0, Speed = 9600 bps, and Checksum = disabled. ( manual pg.19 )

Circuit w/ Switch and LED











RS-485 Protocol

For ADAM-4050 command set refer to manual pg.130.
The ones that we actually use are:
  • Digital Data In (pg.241) that begins with '$' and
  • Digital Data Out (pg.243) that begins with '#'


The Confusing Question...

Is this protocol only using visible ASCII ( 0x20 - 0x7f )? or is Binary also being used?
Except for the line termination Cr ( 0x0d ), only Visible ASCII are used.

Digital Data Out

#AABB( data )( cr )
  except for ( cr ), #AABB( data ) is string
  • AA is address for 485
    AA = "00" because we have set INIT* as GND
  • BB is channel address
    If "00", it writes to all 8 channels at the same time.
    ( data ) ranges from "00" to "FF"
    lsb is DO 0, and msb is DO 7.
    ( eg. If you want to turn ON DO 7 and DO 5 and rest OFF, the ( data ) will be "A0" )

    If "10" - "17", it writes to each channel.
    "10" will turn ON DO 0, ... , "17" will turn ON DO 7
    In this case ( data ) is either "00" or "01".
It says as response we receive ">( cr )"; however we are not going to care about this for now.

Digital Data In

$AA6( cr )
  • AA is address for 485
  • 6 is Digital Data in Command
Response: !( dataOutput )( dataInput )00( cr )
  • ( dataOutput ) Loop back from output.
    2 character string. If we get "A0", it means that DO 7 and DO 5 is ON and rest are OFF.
  • ( dataInput )
    2 character string. This value is what we want.
  • "00"
    We don't know what this is for, so let's not worry about this.