(Mostly) Successful project controlling multi-color LED over Internet

After some struggle up the Yun learning curve (and the learning curves for HTML5, CSS3, and javascript) I have (mostly) successfully completed a project using a simple web interface (with sliders!) to adjust a multi-color LED that contains red, green, and blue LEDs with a common anode. I say “mostly” successful because while the web interface works fine (sends appropriate commands) in Chrome, Safari, and Firefox, it doesn’t send any commands at all using IE (versions 10 or 11 - haven’t tried other versions). I am researching the IE issues separately, but wanted to share my code with anyone who might be interested.

HTML:

     <!DOCTYPE HTML>
     <head>
     <title>Yun LED Control</title>
     <script type="text/javascript" src="zepto.min.js"></script> 
     <link rel="stylesheet" type="text/css" href="styles.css" media="screen">
     </head>
     <body>


     <h3>Adjust LED Levels:</h3> 


     <form method = "GET" name="LEDform">

       <label>Red:</label>
       <input id="redSlider" type=range min=0 max=100 step=5 name="redLevel" onInput="return sendRedVal()"/>

       

       


       <label>Green:</label>
       <input id="greenSlider" type=range min=0 max=100 step=5 name="greenLevel" onInput="return sendGreenVal()"/>

       

       


       <label>Blue:</label>
       <input id="blueSlider" type=range min=0 max=100 step=5 name="blueLevel" onInput="return sendBlueVal()"/>

       

       



       <input type=button name="resetButton" value="Reset LED Levels" onClick="return sendReset()"/>

     </form>


     <script type="text/javascript">   
     
       function sendRedVal() {    
         $.get('/arduino/' + 'red/' + document.LEDform.redLevel.value + '/');
         return false;
       }
     </script>

     <script type="text/javascript">   
       
       function sendGreenVal() {    
         $.get('/arduino/' + 'green/' + document.LEDform.greenLevel.value + '/');
         return false;
       }
     </script>


     <script type="text/javascript">   
     
       function sendBlueVal() {    
         $.get('/arduino/' + 'blue/' + document.LEDform.blueLevel.value + '/');
         return false;

       }
     </script>

     <script type="text/javascript">   
     
       function sendReset() {    
         $.get('/arduino/' + 'reset/' + " " + '/');
         document.LEDform.redSlider.value = 50;
         document.LEDform.greenSlider.value = 50;
         document.LEDform.blueSlider.value = 50;
       return false;

       }
     </script>

     </body>

CSS:

     body {
       font-family:Arial,Helvettica,sans-serif;
       background-color:#ffffff;
       font-size: 1.2em;
     }


     label {
       float: left;
       width: 100px;
       text-align: right;
       margin-right: 10px;
     }

     input {
       width: 200px;
       height: 20px;
     }

sketch:

     // YunLED
     //
     // This sketch uses the Arduino Yun to control a multicolor LED. 
     // Examples of possible commands are listed here:
     //
     // "red/10"     -> set red LED to 10% brightness
     // "green/40"   -> set green LED to 40% brightness
     // "blue/25"    -> set blue LED to 25% brightness
     // "reset"      -> set all LEDs to 50% brightness

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

     int brightness;

     YunServer server;

     void setup() {


       pinMode(3,OUTPUT); //red LED is connected to I/O pin 3
       pinMode(6,OUTPUT); //green LED is connected to I/O pin 6
       pinMode(9,OUTPUT); //blue LED is connected to I/O pin 9

       // Bridge startup
       pinMode(13,OUTPUT); //LED13 is used for confirming Bridge initialization
       digitalWrite(13, LOW);
       Bridge.begin();
       digitalWrite(13, HIGH);
  
       //Console is used for debugging
       Console.begin();
  
       resetLEDs(); //sets all LEDs to 50% brightness
 
       // Listen for incoming connection only from localhost
       // (no one from the external network could connect)
       server.listenOnLocalhost();
       server.begin();
     }

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

       // Is there a new client?
       if (client) {
         // Process request
         process(client);

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

       delay(50); // Wait 50ms before polling again
     }

     void process(YunClient client) {
       // read the command
       String command = client.readStringUntil('/');
       Console.print("command = ");
       Console.println(command);
  
       if (command == "reset") {
         resetLEDs();
       }

       // is "red" , "green", or "blue" command?
       if (command == "red" || command == "green" || command == "blue" ) {
         String color = command;
         // Read brightness
         brightness = client.parseInt();
         Console.print("value = ");
         Console.println(brightness);
         adjustColor(color, brightness);
       }
     }

     void resetLEDs() {
       adjustColor("red", 50); //set red initially to 50% brightness
       adjustColor("green", 50); //set green initially to 50% brightness
       adjustColor("blue", 50); //set blue initially to 50% brightness
     }

     void adjustColor(String color, int intensity) {
       int pin, value;
  
       if (color == "red") pin = 3;
       else if (color == "green") pin = 6;
       else if (color == "blue") pin = 9; 

       //The multicolor LED has red, green, and blue LEDs anodes
       //connected together, so we attached the commonanode to +5V,
       //and connected the red, green, and blue cathodes to digital I/O
       //pins 3, 6, and 9 (through an appropriate series resistance to
       //limit LED current). To turn a given LED on, we therefore use 
       //negative logic and set its I/O pin low; to turn it off, we set 
       //the pin high. 
  
       //This sketch uses PWM to adjust the brightness of the individual
       //LEDs. Intensity is a value from 0 to 100, representing percentage of
       //maximum brightness. To convert to 8-bit PWM value, we first
       //scale intensity by multiplying it by 255 (maximum PWM value)
       //and then dividing by 100 (for percent). 
  
       value = 255*intensity/100;
  
       //As noted above, we are using negative logic, but Arduino's
       //PWM assumes we will use positive logic. To get around this, we
       //need to invert the PWM signal. This could be done with extra
       //hardware, but software offers a simpler solution: since PWM on the 
       //Arduino Yun uses an 8-bit unsigned integer (values from 0 min
       //to 255 max) to represent the range of fully off to fully on, we
       //simply subtract the desired value from 255 to generate an
       //appropriate PWM value.   

       value = 255 - value;
       analogWrite(pin, value);
     }

(code tags added by moderator - PLEASE USE THEM IN THE FUTURE!)

Looks nice, thanks for sharing!

Looking at it from a configuration management point of view, I would do the reset command differently. The issue is that the meaning of reset is defined in both the HTML and the sketch. If you want to change it in the future, perhaps so that reset turns them all off, you will have to change the code in both places or things will get confused.

Perhaps the reset command should also include the three new values. If that's the case, it might be better to rename the reset command to RGB. For example, it could look like /arduino/RGB/50/50/50 where the three values are red, green, and blue, in that order. Then, in the future, if you want to make reset turn off the LED, you just need to send /arduino/RGB/0/0/0 with no further changes to the sketch.

Or, another option would be to have the ability for the web page to query the status of the LED. That way, it could send the reset command as you have it now, then request the new values from the sketch and then update the sliders with the value that it gets back. That way, only the sketch is in charge of what reset means.

A simple way to return the current status would be to always return the three values on every command. Return it as text that is easy for your javascript to parse. That way, the web page can decide when it wants to look at the return values and when it wants to ignore them.

Nothing wrong with the way you did it, just trying to prod some additional thoughts.

@jverive, Very nice work. Please note, I am Documenting the Yun and I will soon post the webpages to links to projects like this.

I am especially take by the clean formatting and extensive comments. I note this because I often do a project to only come back a year later wonder what I did. This looks good.

On the HTML5, CSS, Javascript side, I would recommend adding ID to all your major components, like the form, it's widgets, and H3. The reason I say this is because I am often going back months later to add color, shading, or spacing to an HTML element - only to put it finally in the CSS file, as a custom attribute for that element.

Jesse

Thanks everybody. These are excellent ideas, and I’ll be improving the project (and my coding) in the near future.

I’m including a screenshot below so you can see what the web interface looks like. It’s quite simple, though the formatting (in CSS) took some time to get right.

YunLED_screenshot.png

A very nice and clean layout! That's not surprising since it matches the obvious care to have neat easy to read code - I wish everyone took such care in formatting. True, the compiler doesn't care, but it sure helps human readability! 8)

One thing I'm contemplating is using Bridge.get() and Bridge.put() methods to read and write key/value pairs over the bridge. On the sketch side the process looks fairly straightforward, but what do I need to do on the linux side? I'd like to stick to javascript if possible, as I am really quite the noob with interactive web programming. Any tips? Do I need to use the "POST" method?

You can read/write the values on the Linux side using a variety of methods, including Python. For example:

#!/usr/bin/python

sys.path.insert(0, '/usr/lib/python2.7/bridge/')
from bridgeclient import BridgeClient as bridgeclient
bc = bridgeclient()

bc.put("TestKey", "TestValue")

This stores "TestValue" under the key "TestKey" such that calling Bridge.get("TestKey") in your sketch will return "TestValue"

But if you are just trying to remotely get/put values, then you don't have to write anything on the Linux side, as it's already done. The same assignment made in the Python code above could be done by accessing:

http://arduino.local/data/put/TestKey/TestValue

This is a call that you would make in exactly the same way as you are making your current http://arduino.local/arduino/red/50 call.

To remotely read a specific key:

http://arduino.local/data/get/TestKey

and to remotely read all keys at once:

http://arduino.local/data/get

There are several pre-defined base URLs that the Linux side understands:

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:

// 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:

<!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()" />




<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?