Display Light Levels with Canvas

Here is another example of a sensor affecting an an HTML element's attrubute on a web page.  This time, the shade over a light sensor controls the shade of a drawing canvas.  

Reminder: If you followed the instructions in the Join Another Wi-Fi Network section, you’ll have to replace 192.168.4.1 with your IP address.

  • Go to Sense Light and follow the instructions for building and testing the circuit.
  • Go to your Wi-Fi module’s Files page at http://192.168.4.1/update-ffs.html.
  • Use the Choose file button to upload light-controls-canvas.html to your Wi-Fi module.
  • In your web browser, open 192.168.4.1/files/light-controls-canvas.html
  • Open Page Light Controls Canvas Host.side with SimpleIDE, and then click the Run with Terminal button.
  • If you don't see any light measurements, click the browser's refresh button to reload the web page.
  • Try casting different levels of shade over the sensors.
  • Verify that the light = value displays larger numbers with more shade, and smaller numbers with less shade.
  • Try adjusting the values in the Light range text input fields to get a good response to light/dark in your ambient light conditions.

 

How it Works - The Propeller C Code

This Propeller C program responds to GET requests from a browser that have a path of "/light" with a string that represents the decimal value of its light sensor measurement.  It's very similar to the ones in Page Requests Info from Propeller and Display Pushbutton States, so try those two pages to find out more about the mechanics of the program.  The main differences are the name of the id variable (lightId), the name of the path "/light", and the sensor code.  The sensor code uses rc_time to set the lightVal variable based on light the phototransistor senses.  For more info on that, go back to the Sense Light activity.  As usual, wifi_print responds to the GET request, in this case with a decimal text representation of the lightVal variable's value.

#include "simpletools.h"
#include "wifi.h"

int event, id, handle;
int lightId;
int lightVal;

int main()
{
  wifi_start(31, 30, 115200, WX_ALL_COM);

  lightId = wifi_listen(HTTP, "/light");
  print("getFromPageId = %d\n", lightId);

  while(1)
  {
    wifi_poll(&event, &id, &handle);
    print("event = %c, id = %d, handle = %d\r",
                event,      id,      handle);

    if(event == 'G')
    {
      if(id == lightId)
      {
        high(5);
        pause(1);
        lightVal = rc_time(5, 1);
        
        wifi_print(GET, handle, "%d\n", lightVal);
        print("Reqply to GET request:%d\n", lightVal);
      }        
    }
    pause(500);
  }    
}

 

How it Works - HTML & JavaScript

The JavaScript in this page makes the browser repeatedly send light level GET requests to the Propeller through the wi-Fi module. 

The HTML part of the page has:

  • A 300 x 150 pixel drawing canvas with the id "myCanvas" for depicting light/shade levels.
  • A paragraph for displaying the light measurement as light = 123 (where 123 could be any value from 0 to 4000).
  • Two text inputs with "lightHigh" and "lightLow" IDs.  Their onchange event settings cause the setMaxMin JavaScript function to run when either of their values get changed.
<body>

  <canvas
    id="myCanvas" width="300" height="150"
    style="border:1px solid gray;">
  </canvas>

  <p id="textLightVal" style="width: 300px; text-align: center">
   light = (waiting...)</p>
 
  <input type = "text" id = "lightHigh" maxlength = "5" value = "0"
   onchange = "setMaxMin()" style="width: 145px">
  <input type = "text" id = "lightLow" maxlength = "5" value = "4000"  
   onchange = "setMaxMin()"  style="width: 145px; text-align: right"> Light range

The var myTimer = setInterval(getFromMcu, 1000) makes the browser execute the getFromMcu function once every second.  Variables named lightMax, lightMin, and myRange are declared and initialized, and then the grayScaleCanvas function is called.

<script>

var myTimer = setInterval(getFromMcu, 1000);

var lightMax = 4000, lightMin = 0, myRange = lightMax - lightMin;
grayScaleCanvas("position");

As with previous examples, getFromMcu is called repeatedly, which triggers a call to httpGet, this time with a path of "/light".  When the microcontroller responds with a light value, the callback calls the useMcuReply function.  This function has been adjusted to update the value displayed by the light = ... paragraph, displaying the number it received.  It also calls the grayScaleCanvas function to set the shade of the canvas.  For more about now these functions work, read Page Requests Info from Propeller.

function useMcuReply(response)
{
  document.getElementById("textLightVal").innerHTML =
  "light = " + response;
  var light = Number(response);
  grayScaleCanvas(light);
}

function getFromMcu()
{
  httpGet("/light", useMcuReply);
}

function httpGet(path, callback)
{
  var req = new XMLHttpRequest();
  req.open("GET", path, true);
  req.onreadystatechange = function()
  {
    if (req.readyState == 4)
    if(req.status == 200)
      callback(req.responseText);
    else
      callback("Waiting...");
  }
  req.send(null);
}

The grayScaleCanvas function starts with a number from the Propeller in the lightMin to lightMax range.  By default, that's 0 (very bright light) to 4000 (very dark shade).  In the end, it has to assign the canvas.style.background property a value of "#000" (black), "#111" (very dark gray), and so on up through hexadecimal "#eee" (very light gray), "#fff" (white).  Assuming the default values, it has to map a light sensor value, in the 0...4000 (bright...dark) range to its equivalent position in the hexadecimal f...0 range (decimal 15...0).  Some examples: The smallest light sensor values (brightest light) will be close to 0, which map to hexadecimal f (decimal 15).  The largest light values close to 4000 would map to 0.  If the light sensor value was 3000 in a scale of 0 to 4000, the equivalent value in a scale of f to 0 would be 3, again on a scale of 15...0 or hexadecimal f...0. 

function grayScaleCanvas(lightLevel)

{
  var canvas = document.getElementById("myCanvas");
  console.log("lightLevel (before) = ", lightLevel);
  if(lightLevel < lightMin) lightLevel = lightMin;
  if(lightLevel > lightMax) lightLevel = lightMax;
  console.log("lightLevel (trimmed) = ", lightLevel);
  lightLevel -= lightMin;
  lightLevel = myRange - lightLevel;
  console.log("lightLevel (after) = ", lightLevel);
  lightLevel *= 15;
  lightLevel = lightLevel / myRange;
  lightLevel = Math.round(lightLevel);
  console.log("myRange = ", myRange);
  var colorStr = lightLevel.toString(16);
  var colorStr = "#" + colorStr + colorStr + colorStr;
  canvas.style.background = colorStr;  
  console.log(colorStr);
}

After the lightLevel variable has been mapped from a scale of 0 to 4000 to its equivalent position in a scale of 15 to 0, var colorstr = lightLevel.toString(16) uses the toString method to convert it to a string that describes the value lightLevel stores in hexadecimal digits.  The result could be "0", "1", "2", and so on up through "e", and "f".  Then, var colorStr = "#" + colorStr + colorStr + colorStr makes a colorStr result that could be "#000", "#111", "#222", and so on up through "#eee", and "#fff".  Last but not least, canvas.style.background = colorstr uses the colorStr to set the canvas' background property to black, a shade of gray, or white that corresponds to the light measurement the Propeller took and relayed.

The setMaxMin function gets called whenever the values in the text inputs are changed by the user.  In other words, whenever you type something new into one of those "light range" fields, it triggers an onchange event that causes the setMaxMin function to get called.  This function takes the text values from the lightHigh and lightLow fields, and calculates the lightMax, lightMin, and myRange values.  The grayScaleCanvas function uses these values to map to the lightLevel value it receives from the Propeller to those "#000", "#111", "#222", ... , "#eee", "#fff" range of values.

function setMaxMin()
{
  lightMin = Number(document.getElementById("lightHigh").value);
  lightMax = Number(document.getElementById("lightLow").value);
  console.log(lightMin);
  console.log(typeof lightMin);
  myRange = lightMax - lightMin;
}