Go Down

Topic: [SOLVED] How do I access bridge data (value for a given key) in HTML/JavaScript? (Read 6697 times) previous topic - next topic

jverive

Mar 19, 2015, 05:57 pm Last Edit: Mar 26, 2015, 03:37 pm by jverive Reason: Solution found!
In order to start using the bridge to transfer key/value data, I've been playing with the Bridge Tutorial (which I've modified slightly to assign digital ports 3, 6, and 9 explicitly to the red, green, and blue LEDs respectively).  The tutorial works fine, and I am able to control the LEDs as expected, but I'm not getting what I was expecting (hoping for) as returned data using javascript. My sketch and HTML are given below:

Bridge Tutorial sketch:

Code: [Select]


// Possible commands are listed here:
//
// "digital/13"     -> digitalRead(13)
// "digital/13/1"   -> digitalWrite(13, HIGH)
// "toggle/13"      -> toggles pin 13
// "analog/2/123"   -> analogWrite(2, 123)
// "analog/2"       -> analogRead(2)
// "mode/13/input"  -> pinMode(13, INPUT)
// "mode/13/output" -> pinMode(13, OUTPUT)

#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>

// Listen on default port 5555, the webserver on the Yun
// will forward there all the HTTP requests for us.
YunServer server;

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

  // Bridge startup
  pinMode(13,OUTPUT);
  digitalWrite(13, LOW);
  Bridge.begin();
  digitalWrite(13, HIGH);

  // Listen for incoming connection only from localhost
  // (no one from the external network could connect)
  server.listenOnLocalhost();
  server.begin();
}

void loop() {
  // Get clients coming from server
  YunClient client = server.accept();

  // There is a new client?
  if (client) {
    // Process request
    process(client);

    // Close connection and free resources.
    client.stop();
  }

  delay(50); // Poll every 50ms
}

void process(YunClient client) {
  // read the command
  String command = client.readStringUntil('/');

  // is "digital" command?
  if (command == "digital") {
    digitalCommand(client);
  }

  // is "toggle" command?
  if (command == "toggle") {
    toggleCommand(client);
  }

  // is "red" command?
  if (command == "red") {
    redCommand(client);
  }

  // is "green" command?
  if (command == "green") {
    greenCommand(client);
  }
  
    // is "blue" command?
  if (command == "blue") {
    blueCommand(client);
  }

  // is "analog" command?
  if (command == "analog") {
    analogCommand(client);
  }

  // is "mode" command?
  if (command == "mode") {
    modeCommand(client);
  }
}

void toggleCommand(YunClient client) {
  int pin, value;

  // Read pin number
  pin = client.parseInt();
  value = !digitalRead(pin); //read pin value and invert it
  digitalWrite(pin, value);  //write this inverted value to the pin

  // Send feedback to client
  client.print(F("Pin D"));
  client.print(pin);
  client.print(F(" set to "));
  client.println(value);

  // Update datastore key with the current pin value
  String key = "D";
  key += pin;
  Bridge.put(key, String(value));
}

void redCommand(YunClient client) {
  int pin = 3;
  int value;
  float brightness;

  // Read brightness
  brightness = client.parseInt();
  value = 255 - 255*(brightness/100);
  pinMode(pin, OUTPUT);
  analogWrite(pin, value);

  // Send feedback to client
  client.print(F("Pin D"));
  client.print(pin);
  client.print(F(" set to analog "));
  client.println(value);

  // Update datastore key with the current pin value
  String key = "D";
  key += pin;
  Bridge.put(key, String(value));
}

void greenCommand(YunClient client) {
  int pin = 6;
  int value;
  float brightness;

  // Read brightness
  brightness = client.parseInt();
  value = 255 - 255*(brightness/100);
  pinMode(pin, OUTPUT);
  analogWrite(pin, value);

  // Send feedback to client
  client.print(F("Pin D"));
  client.print(pin);
  client.print(F(" set to analog "));
  client.println(value);

  // Update datastore key with the current pin value
  String key = "D";
  key += pin;
  Bridge.put(key, String(value));
}

void blueCommand(YunClient client) {
  int pin = 9;
  int value;
  float brightness;

  // Read brightness
  brightness = client.parseInt();
  value = 255 - 255*(brightness/100);
  pinMode(pin, OUTPUT);
  analogWrite(pin, value);

  // Send feedback to client
  client.print(F("Pin D"));
  client.print(pin);
  client.print(F(" set to analog "));
  client.println(value);

  // Update datastore key with the current pin value
  String key = "D";
  key += pin;
  Bridge.put(key, String(value));
}

void digitalCommand(YunClient client) {
  int pin, value;

  // Read pin number
  pin = client.parseInt();

  // If the next character is a '/' it means we have an URL
  // with a value like: "/digital/13/1"
  if (client.read() == '/') {
    value = client.parseInt();
    digitalWrite(pin, value);
  }
  else {
    value = digitalRead(pin);
  }

  // Send feedback to client
  client.print(F("Pin D"));
  client.print(pin);
  client.print(F(" set to "));
  client.println(value);

  // Update datastore key with the current pin value
  String key = "D";
  key += pin;
  Bridge.put(key, String(value));
}

void analogCommand(YunClient client) {
  int pin, value;

  // Read pin number
  pin = client.parseInt();

  // If the next character is a '/' it means we have an URL
  // with a value like: "/analog/5/120"
  if (client.read() == '/') {
    // Read value and execute command
    value = client.parseInt();
    analogWrite(pin, value);

    // Send feedback to client
    client.print(F("Pin D"));
    client.print(pin);
    client.print(F(" set to analog "));
    client.println(value);

    // Update datastore key with the current pin value
    String key = "D";
    key += pin;
    Bridge.put(key, String(value));
  }
  else {
    // Read analog pin
    value = analogRead(pin);

    // Send feedback to client
    client.print(F("Pin A"));
    client.print(pin);
    client.print(F(" reads analog "));
    client.println(value);

    // Update datastore key with the current pin value
    String key = "A";
    key += pin;
    Bridge.put(key, String(value));
  }
}

void modeCommand(YunClient client) {
  int pin;

  // Read pin number
  pin = client.parseInt();

  // If the next character is not a '/' we have a malformed URL
  if (client.read() != '/') {
    client.println(F("error"));
    return;
  }

  String mode = client.readStringUntil('\r');

  if (mode == "input") {
    pinMode(pin, INPUT);
    // Send feedback to client
    client.print(F("Pin D"));
    client.print(pin);
    client.print(F(" configured as INPUT!"));
    return;
  }

  if (mode == "output") {
    pinMode(pin, OUTPUT);
    // Send feedback to client
    client.print(F("Pin D"));
    client.print(pin);
    client.print(F(" configured as OUTPUT!"));
    return;
  }

  client.print(F("error: invalid mode "));
  client.print(mode);
}
 


And the HTML contents:

Code: [Select]

<!DOCTYPE HTML>
<head>
<title>Yun Messages</title>
<script type="text/javascript" src="zepto.min.js"></script>
</head>
<body>
<H1 id="header"> Insert your command: </H1>
<form name="msgform">
<input type="text" id="commandText" name="msg">
<input type="button" value="Send Command" onClick="return sendMsg()" />
<br />
<br />
<input type=button value="Get Returned Key Value" onClick="getKeyValue()" />
<textarea id="keyValue"></textarea>

</form>

<script type="text/javascript">
 function getKeyValue() {
   document.getElementById('header').innerHTML = document.getElementById('commandText').value;
   var myKeyValue = $.get('http://arduinoyun.local/data/get/D3');
   document.getElementById('keyValue').innerHTML = myKeyValue;
 }
</script>


<script type="text/javascript">  
 function sendMsg() {    
   $.get('/arduino/' + document.msgform.msg.value + '/');
   return false;
 }
</script>


</body>


As you can see, I'm making an explicit request for the value of key D3, but not getting the expected value in the "keyValue" text box. Instead, I get "[object XMLHttpRequest]" in the text box (see attached screenshot).


It looks like I may not be reading the key value properly, but I don't know enough of the process to determine what my error is.

Any ideas?

sonnyyu

Code: [Select]
<!DOCTYPE html>
<head>
    <script type="text/javascript" src="http://zeptojs.com/zepto.min.js"></script>
    <script type="text/javascript">
        function refresh() {
            $('#content').load('/data/get/D3');
        }
    </script>
</head>
<body onload="setInterval(refresh, 1000);">
    <span id="content">Waiting for Arduino...</span>
</body>






jverive

Sonnyu,

I very much appreciate your reply, but it's not yielding anything (at least that I can see). I've changed my HTML as follows:

Code: [Select]

<!DOCTYPE HTML>
<head>
<title>Yun Messages</title>
<script type="text/javascript" src="zepto.min.js"></script>
</head>
<body>
<h1 id="header"> Insert your command: </h1>
<h2 id="content">Waiting...</h2>
<form name="msgform">
<input type="text" id="commandText" name="msg">
<input type="button" value="Send Command" onClick="return sendMsg()" />
<br />
<br />
<input type=button value="Get Returned Key Value" onClick="getKeyValue()" />
<textarea id="keyValue"></textarea>

</form>

<script type="text/javascript">
  function getKeyValue() {
    document.getElementById('header').innerHTML = document.getElementById('commandText').value;
    $('#content').load('/data/get/D3');
  }
</script>


<script type="text/javascript">   
  function sendMsg() {   
    $.get('/arduino/' + document.msgform.msg.value + '/');
    return false;
  }
</script>


</body>






And here is the modified Bridge Tutorial sketch (stripped down to just respond to setting the red LED to a given brightness level). You'll note that the value for red is saved along with key D3:

Code: [Select]


#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>

// Listen on default port 5555, the webserver on the Yun
// will forward there all the HTTP requests for us.
YunServer server;

void setup() {
  // Bridge startup
  pinMode(13,OUTPUT);
  digitalWrite(13, LOW);
  Bridge.begin();
  digitalWrite(13, HIGH);
  Console.begin();

  // Listen for incoming connection only from localhost
  // (no one from the external network could connect)
  server.listenOnLocalhost();
  server.begin();
}

void loop() {
  // Get clients coming from server
  YunClient client = server.accept();

  // There is a new client?
  if (client) {
    // Process request
    process(client);

    // Close connection and free resources.
    client.stop();
  }

  delay(50); // Poll every 50ms
}

void process(YunClient client) {
  // read the command
  String command = client.readStringUntil('/');

  // is "red" command?
  if (command == "red") {
    redCommand(client);
  }

}


void redCommand(YunClient client) {
  int pin = 3;
  int value;
  float brightness;

  // Read brightness
  brightness = client.parseInt();
  value = 255 - 255*(brightness/100);
  pinMode(pin, OUTPUT);
  analogWrite(pin, value);

  // Send feedback to client
  Console.print(F("Pin D"));
  Console.print(pin);
  Console.print(F(" set to analog "));
  Console.println(value);

  // Update datastore key with the current pin value
  String key = "D";
  key += pin;
  Bridge.put(key, String(value));
  char myData[3];
  Bridge.get("D3", myData, 3);
  Console.print(F("key "));
  Console.print(key);
  Console.print(F(" set to value "));
  Console.println(myData);
}





The first screen capture below shows the Web page after initial load, and it looks as I'd expect. The second screen capture shows the page after sending the red/10 command and clicking on the "Get Returned Key Value" button. The h1 level header with id="header" changes as expected to reflect the command text, but the h2 level header (with id="content") just disappears. This is leading me to believe that the  $('#content').load('/data/get/D3') function call is returning a null (or unprintable) value. The third screen capture is the Console output that clearly shows that the sketch's Bridge.put() and Bridge.get() functions are working properly.

Am I using the  $('#content').load('/data/get/D3') function call incorrectly? Any ideas where to go from here? Thanks!

Jeff


ShapeShifter

What do you get if you manually type http://<address>/data/get/D3 into your browser? Do you get the expected value?

When I try it, I get:
Code: [Select]
{"value":"229","key":"D3","response":"get"}

But when I try your web page, I see the same thing as you, the "content" header goes blank.

I don't know enough about JavaScript, zepto.js, or JQuery.js. But it looks like .load maybe doesn't understand the bridge key response? When used in the TemperatureWebPanel example, the $('#content').load() call is accessing the Yun using the REST API, which returns plain text that is displayed. Maybe there is something in the /data/get response format that .load doesn't like?

Which brings me to my main comment... you are using the REST API to set the value, then making a separate call to /data/get to read the response. Wouldn't it make more sense to simply send the response in the same REST API call that made the request? Take a look at the Bridge Example, how it writes back the status to the client object, much as you are now writing a response to the console object. Then look at the TemperatureWebPanel Example how it is is making the same type of REST API call and displaying the client's response on the page.

Wouldn't that be a simpler and more efficient confirmation method?


I do see a bug in your sketch, but I don't think it would cause the problems you are seeing:

Code: [Select]

  Bridge.put(key, String(value));
  char myData[3];
  Bridge.get("D3", myData, 3);

You are putting three characters, and getting up to three characters, but your array is only dimensioned for three characters. It needs to include space for a terminating NULL character. When you request up to three characters, it can place up to three characters plus a terminating NULL in your buffer. Since it is actually returning three character, that NULL is overruning the end of your buffer. You must dimension your buffer at least one character larger than the number of characters you are requesting.

jverive

ShapeShifter - I'm not really intending the JavaScript request to be confirmation, but rather just to test the transfer of data in both directions. The web page code only makes it look like confirmation, but that's because the code is simply designed for debugging.

As it turns out (and as you pointed out) the http://<address>/data/get/D3 requests returns a JSON object, so the right method to use is $.getJSON(). In the JavaScript script below, you'll see that I've used this method to obtain the value for the D3 key (and use the value to change the text of element 'keyValue'):

Code: [Select]


<script type="text/javascript">
  function getKeyValue() {
    $.getJSON("/data/get/D3",
      function(json){
        document.getElementById('keyValue').innerHTML=json.value;
      });
  }
</script.



In my application, I'm using my Arduino Yun as a web server, with the web page stored on the Yun's SD card. In this manner, the Arduino sketch doesn't have to deliver a web page line by line (as is done in many Arduino sketches utilizing Ethernet shields). Eventually - and I'm quite close now - the web page will monitor and control various devices around my home. Using $.getJSON() is ideal for retrieving sensor data from the Arduino side of the Yun over the bridge. Thanks for all your help!

BTW - I realize that the "GET" methods aren't as secure as "POST", but I'm just getting my feet wet with client-server communications. Now that I'm having success with "GET", I suppose it's time to start climbing the "POST" learning curve (I guess you CAN teach an old dog new tricks).

ShapeShifter

so the right method to use is $.getJSON().
That looks like a pretty clean way to do it.

And yes, that is a good mechanism for getting dynamic data into a web page without having to generate the whole page on the fly with some sort of CGI (or writing a sketch that outputs the whole page line by line like so many embedded solutions require.) This method is particularly valuable for showing dynamic data because only the data needs to be fetched each time, not the whole page.

As you work on your project, keep in mind a potential time savings. Instead getting each data element one by one, you can used the url /data/get to get ALL of the key/value pairs in one JSON object: request it once, then parse out the individual elements.

Quote
BTW - I realize that the "GET" methods aren't as secure as "POST", but I'm just getting my feet wet with client-server communications. Now that I'm having success with "GET", I suppose it's time to start climbing the "POST" learning curve (I guess you CAN teach an old dog new tricks).
It that distinction really important in this application? Yes, when sending a GET URL, all of the fields are visible in the URL, whereas a POST sends the same data but in the body of the request, not in the URL. But does that matter? You aren't really going to be copying and pasting the request URL somewhere, are you? And even if you do, are you going to have any secrets like passwords or account numbers in those data items? Moving to POST may significantly complicate things, at least with the /data/get requests. Personally, I wouldn't bother unless you think it's really important. (And if the requests are just moving around on your private home network, I'm hard pressed to think why it would be important.)

jverive

Thanks for the valuable feedback. Indeed, I do plan on playing with the "url/data/get" to grab the whole array of key/value pairs in one fell swoop. I started with getting one key at a time because the parsing seems to be a tad less complicated, but now that getting one value for a specific key is working it's time to learn the process for grabbing and parsing the entire array.

And "learn" is really the operative word here; I don't have a burning need to get this project completed, so I'm primarily doing it to keep up with technology. I'm an electronics hobbyist, and the project is mostly for personal edification. That's also part of the push to learn about POST methods - they're another "mountain to climb". However, since I am interested in using the Arduino Yun as the heart of a home security system, I see a practical application for the security that POST affords.


ShapeShifter

it's time to learn the process for grabbing and parsing the entire array.
Please post the results of your education.  8) I know I would like to see how you do it, and I'm sure others will as well.

Quote
so I'm primarily doing it to keep up with technology. I'm an electronics hobbyist, and the project is mostly for personal edification.
I understand completely. I do embedded programming for a living, but I'm enjoying playing with the Yun as a way of expanding my frontiers into a little web programming and JavaScript so that just like I an try and get a grasp of current technologies. That's why I want to see the result of your parsing investigations, so i can learn it myself.

mart256

Please post the results of your education.  8) I know I would like to see how you do it, and I'm sure others will as well.
I understand completely. I do embedded programming for a living, but I'm enjoying playing with the Yun as a way of expanding my frontiers into a little web programming and JavaScript so that just like I an try and get a grasp of current technologies. That's why I want to see the result of your parsing investigations, so i can learn it myself.
I'd like to learn about your investigations too, here's my topic so you can learn about mine:

http://forum.arduino.cc/index.php?topic=304655.0

I've been learning a bit of Javascript, Bottle (python framework for webservers) and now a bit of Jquery. Arduino Yun can host a webserver on the linux side and do powerfull things, and can communicate with the arduino side using bridge.

sonnyyu

Load unmodified Bridge example into ATmega32u4.

Test
Code: [Select]

http://192.168.0.102/arduino/analog/1/128

 at Browser

Code: [Select]
Pin D1 set to analog 128


Code: [Select]
nano /www/setpin.html

Code: [Select]
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript">
function setpin1(value){
url = "/arduino/analog/1/"+value;
xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", url, false);
xmlhttp.send(null);
document.getElementById("pin1value").innerHTML = xmlhttp.responseText;
}
</script>
<form>
<input type="range" id="pin1"  style="width: 500px; height: 30px;"  min="0" max="255" value="0" step="1"  onchange="setpin1(this.value);" />
</form>
<br>
<span id="pin1value"></span>
</body>
</html>


Use Browser
Code: [Select]
http://192.168.0.102/setpin.html




jverive

#10
Mar 26, 2015, 03:25 am Last Edit: Mar 26, 2015, 03:36 pm by jverive Reason: Found (and shared) the answer
After coming to the realization that http://<arduino addr>/data/get returns a JSON string, I delved into using the jQuery $.getJSON method. It turns out that accessing multiple value:key pairs using this method is very simple. For example, my Arduino Yun sketch stores values for keys D3, D6, D9, and D13, and I call $.getJSON as follows:

Code: [Select]


$.getJSON("/data/get", function(myJSON) {
      document.getElementById('redReturnedValue').innerHTML = '(' + myJSON.value.D3 + ')';
      document.getElementById('redSlider').value = myJSON.value.D3;
      document.getElementById('greenReturnedValue').innerHTML = '(' + myJSON.value.D6 + ')';
      document.getElementById('greenSlider').value = myJSON.value.D6;
      document.getElementById('blueReturnedValue').innerHTML = '(' + myJSON.value.D9 + ')';
      document.getElementById('blueSlider').value = myJSON.value.D9;
});



The "/data/get" gets a JSON string response from the server, for example

   {"value":{"D13":"0","D6":"15","D3":"25","D9":"5"},"response":"get"}

and stores it in the variable myJSON. Inside this JSON string, everything is a group of key:value pairs contained within the curly braces "{}". For this JSON string, the first level of data looks like this:

    myJSON.value = "{"D13":"0","D6":"15","D3":"25","D9":"5"}"
    myJSON.response = "get"

So "value" is the key associated with the data string "{"D13":"0","D6":"15","D3":"25","D9":"5"}", and "response" is the key associated with the data string "get". To access the data in the sketch's key:value pairs, we just have to "drill down" into myJSON.value, by appending ".keyname" to the end of "myJSON.value". To access the value associated with D3, D6, D9, or D13, we use

    myJSON.value.D3
    myJSON.value.D6
    myJSON.value.D9
    myJSON.value.D13

For the JSON string shown above, myJSON.value.D3 takes on the value of "25", myJSON.value.D6 has the value "15", myJSON.value.D9 becomes "5", and myJSON.value.D13 has the value "0". Note that these are all strings, so to use these values as integers, I use the parseInt() method, like this:

    var redValue = parseInt(myJSON.value.D13)

Rules for scope of variables apply as usual, so myJSON data objects only exist within the calling function (i.e. inside the {} in $.getJSON(url, function(myJSON) {}); either operate on them inside the calling function or assign them to global variables.

ShapeShifter


Go Up