The Parallax WX Wi-Fi module opens up a whole new world of interactive Internet and web features to your microcontroller and robotics projects. The 3.3 V DIP package (32420D) works well with the Propeller Activity Board WX.
The following tutorials will introduce you the Parallax WX Wi-Fi Module, and get it ready to work with your Propeller Activity Board (WX is strongly recommended). Then you will create web interfaces for interacting with simple Propeller-controlled pushbutton, LED, and servo circuits. New to HTML and/or JavaScript? No problem. We'll take you through fundamentals so that you can learn to create and optimize your own custom webpages. Use the navigation below when you're ready to get started.
This guide assumes you’ve already got some Propeller C + circuit building experience. If not, here are some nice getting started tutorials for Propeller C.
Always use the latest versions of the module firmware and an up-to-date example code set.
Follow the checkmarks below to obtain the latest files and make note of where you unzip them. We will be updating your firmware a bit later on in these tutorials.
NOTE: Google’s Chrome web browser is recommended for these Module + Propeller C tutorial activities. It can be downloaded and installed from https://www.google.com/chrome/browser/.
STOP: Complete each check mark in order below! Do not connect the module until you reach the appropriate step, or you may damage your Wi-Fi Module.
With the Propeller Activity Board, you can connect the WX Wi-Fi module to the WX Socket. If you have an Activity Board WX, you can also connect an SEL socket to control what kind of data flows through the Wi-Fi and USB ports.
Before connecting the Wi-Fi module to the Propeller Activity Board, let's make sure the Propeller isn't running an application that might interfere with the Wi-Fi module's communication.
/* Hello Message.c Display a hello message in the serial terminal. http://learn.parallax.com/propeller-c-start-simple/simple-hello-message */ #include "simpletools.h" // Include simpletools header int main() // main function { print("Hello!!!"); // Display a message }
The wifi library you'll be working with allows you to use jumper wires to choose which port (USB or Wi-Fi) certain data (program, terminal, Wi-Fi application) pass through.
This setting runs all program, terminal, and Wi-Fi application data through the Wi-Fi module. This setting should not be used until you can see the Wi-Fi module in Simple IDE's COM port dropdown. Make sure to use the wifi_start function call shown here at the beginning of any program you run while the SEL socket is connected to 3.3 V.
wifi_start(31, 30, 115200, WX_ALL_COM);
The 10 k resistor can be tied to GND for programming through USB or 3.3 V for programming through Wi-Fi. Terminal data passes through USB and Wi-Fi application data passes through the Wi-Fi module. An optional pause can be added to give time for loading the program after power-on or reset.
pause(2000); wifi_start(31, 30, 115200, 7);
This setting is for programming through USB and Wi-Fi app data through I/O pins (P9 and P8). SimpleIDE terminal data will go through USB if you use USB_PGM_TERM, or through the Wi-Fi module if you use USB_PGM.
wifi_start(9, 8, 115200, USB_PGM_TERM); --- or --- wifi_start(9, 8, 115200, USB_PGM);
The non-WX version of the Activity Board does not have an SEL socket. The program will have to be loaded through USB, and you can select whether terminal data goes through USB or the Wi-Fi module. Use USB_PGM_TERM to send terminal data through the USB port, or USB_PGM to send terminal data through the Wi-Fi module.
wifi_start(9, 8, 115200, USB_PGM_TERM); --- or --- wifi_start(9, 8, 115200, USB_PGM);
Now we need our computer to join our module's network:
#include "simpletools.h" #include "wifi.h" int main() { wifi_start(9, 8, 115200, USB_PGM_TERM); print("Leave a Network\r"); // Leaves network where it was a station and sets // the Wi-Fi module's mode to AP. wifi_leave(AP); // Verify mode after leaving the network. int mode = wifi_mode(CHECK); switch(mode) { case STA: //0xf4: print("mode=STA\r"); break; case AP: //0xf3 print("mode=AP\r"); break; case STA_AP: //0xf2 print("mode=STA+AP"); break; } }
The page should display "Updating…" for a few seconds, then "Updating done. Rebooting…" When it returns to the original "Please select an .ota file to load…", it means the update was successfully completed.
Click the Settings button, and make the following adjustments:
If you have a Propeller Activity Board WX and your computer has either the Windows or Mac operating system, you can program your propeller over Wi-Fi. This is an especially useful feature for the ActivityBot [15] and mobile robots, and other projects that are not convenient to keep tethered.
Now it's time to load a test webpage into your module. Locate the HTML files you downloaded with the firmware in the beginning of this tutorial series, and then follow the steps below:
Next, we'll load some test code into your Activity Board that will allow you to interact with this webpage. Do not close it.
First let's load the test program into your Propeller.
Now we'll check the connection between your Propeller and the web page we had you open in the previous page. Make sure you have the page open and visible.
The Propeller program counts upwards at about 4x per second. Every time you click the web page’s Enter button, the Propeller should send an updated (and higher) value.
In addition to an updated number in the web page, your SimpleIDE Terminal should display that it sent a number each time you click the button:
You might have noticed that your computer cannot access the internet while it is part of the Wi-Fi module’s network. So, it’s better to have your computer and the Wi-Fi module both join another network that is able to access the Internet. After you do this, you’ll still be able to do all the same activities with the Wi-Fi module. The only difference will be instead of 192.168.4.1, you will have to use the IP address that the Wi-Fi network assigns your Wi-Fi module.
You may need to wait for a minute or two for the module to check for and display available networks.
The Wi-Fi module has 3 modes: AP, STA, and STA+AP. AP stands for access point, and that’s when the Wi-Fi module provides its own access point. In this mode, you can make your computer or phone join the Wi-Fi module's network and access its web pages. STA is short for station mode, and that’s when the Wi-Fi module has joined another access point and is a station on its Wi-Fi network. The other access point will assign the Wi-Fi module an IP address. From that point forward, you’ll use the IP address the module was assigned to access its web pages (instead of 192.168.4.1). STA+AP mode allows it to function as both an access point and a station. This mode should only be used for a brief period of time to search access points and join one.
Stay Secure! Once you have made the Wi-Fi module join another access point, you should switch to station mode (STA) immediately after you have noted the station IP address the Wi-Fi module was assigned by the network. This way, you can minimize the likelihood that hackers will try to use the module as a bridge into another network.There may be many networks available; scrolling up and down on the page will help you locate the network you want to join.
After the Wi-Fi module joins another network, it will show you the IP address the network assigned it. Make sure to record the IP address the Wi-Fi access point assigns your Wi-Fi Module. From here on out, you can use that in place of 192.168.4.1.
IMPORTANT: After the Wi-Fi Module has joined a network, you should note the Station IP Address, and then immediately switch to STA mode.
After the Wi-Fi module is in station mode, its Wi-Fi access point will no longer be available. So, make sure to connect your computer to the same Wi-Fi network you just connected the Wi-Fi module to. Then, make sure to always use the IP address the Wi-Fi module was assigned.
This primer introduces just enough HTML to help get you started with creating web pages that can interact with your Propeller + Wi-Fi Module. To help keep the focus on the code, we’ll only include the parts you’ll need to make the web page communicate with a microcontroller, and leave out any additional features.
The recommended software for these exercises includes the Google Chrome web browser and the text editor provided by your operating system: Windows Notepad, Mac TextEdit, Ubuntu (Linux) gedit. If you have a Chromebook, find the Caret app in the Chrome Web Store and add to Chrome.
Here is HTML code that displays text in a web page.
<html> Hello, this is a web page. </html>
Now that your HTML file has been saved, let’s view it in Chrome.
<html> <h1>Page Heading</h1> <p>Paragraph text.</p> </html>
This is an opening html tag <html>. This is a closing html tag </html>. The content between those two tags can be in hypertext markup language, which is abbreviated html. In html, the <h1>...</h1> opening and closing tags make the browser display text between them as a level 1 heading. For paragraph text, you can use <p>...</p>.
You can do lots of great things with HTML, and here are some tutorials that start simple and cover the topics you’ll need to make your pages awesome:
Like the HTML primer, this one introduces just enough JavaScript to help get you started and familiarize you with another component of webpage design for your module. The w3schools.com site will have lots more examples for you to try.
Html also has features for creating interface elements like buttons and checkboxes. Here is an example that creates a button with the <button>…</button> tags. The button doesn’t actually do anything yet. For that, we’ll need JavaScript.
<html> <p>This button doesn't do anything yet.</p> <p>The next program will make it do something.</p> <button>Button</button> </html>
JavaScript is code you can embed in an HTML document to make it do things automatically. Here is an example that makes an alert box pop up when you click the button.
Some browsers will give you an option to prevent a site from displaying popup alert windows. For the purpose of these lessons, you need to allow your browser to show popup alerts – at least temporarily.
<html> <p>Click button to see alert popup message.</p> <button onclick = "showAlert()">Alert Popup</button> <script> function showAlert() { alert('This is an alert!'); } </script> </html>
A JavaScript function named showAlert was added between <script>...</script> tags. The code in this function is contained by curly braces { }. Inside this function, there’s one JavaScript method call - alert(‘This is an alert!’);
Changing <button> to <button onclick = “showAlert()”> adds an onclick event to the button tag to make it call the showAlert function when the button is clicked.
You can do even more great things with a combination of JavaScript and HTML. So, here are some JavaScript tutorials that also start simple and cover the topics you’ll need to make your pages and IoT applications awesome:
One common task for Internet of Things (IoT) applications is for a page to send information to a “thing” on the internet. Whether it’s a web-controlled robot, a remote-controlled alarm system, or whatever else you can imagine, the principle is pretty much the same: the JavaScript code has to be able to accept an input, and send information to the Internet thing about the input it received.
Before sending info to your Activity Board's Propeller microcontroller through the Wi-Fi module, let’s start with using JavaScript to change text in a web page.
<html> <button onclick = "changeText()">Change Text</button> <p id = "myText">Click button to change this text.</p> <script> function changeText() { document.getElementById("myText").innerHTML = "Hey, look, the text changed!!!"; } </script> </html>
In this example, the paragraph <p> tag was expanded to <p id = “myText”>. With this identifier, the JavaScript can modify this paragraph element using document.getElementById(“myText”).innerHTML = “Hey, look, the text changed!!!”;
In JavaScript, document is an example of an object. Objects have methods, which are actions that can be performed on their data, like getElementById(“myText”), which finds the element with the myText ID. Objects also have properties, and innerHTML is an example of one of the document object’s properties. By setting that innerHTML equal to “Hey, look, the text has changed!!!”, it replaces the existing innerHTML - Click button to change this text.”
We’ve given HTML button and paragraph elements IDs so that JavaScript can access and modify them. In this example, JavaScript uses a text input’s ID to get information, adds some text to it, and then displays that text in the web page.
<html> <p>Type something, then click Enter: </p> <input type = "text" id = "textField"> <button onclick = "changeText()">Enter</button> <p id = "myText">Waiting...</p> <script> function changeText() { var x = document.getElementById("textField").value; document.getElementById("myText").innerHTML = "You typed: " + x; } </script> </html>
This creates the text input and gives it an ID <input type = "text" id = "textField">. The button and paragraph also have IDs: <button onclick = "changeText()">Enter</button> and <p id = "myText">Waiting...</p>.
Inside the changeText function, var x = document.getElementById("textField").value declares a variable named x that receives the value contained in the text input. Next, document.getElementById("myText").innerHTML = "You typed: " + x; adds “You typed: “ to whatever you typed in the text input and puts it in the paragraph with the myText ID.
This application of client server communication through JavaScript is called AJAX, and there’s some great tutorials on it too:
The wifi library examples make liberal use of wifi_print and wifi_scan. They behave similarly to print and scan, so let's have a quick review on how those work. The scan function can be used to pick characters, values, and strings from characters you type in the terminal and place them in variables. Conversely, the print function can be used to display the values of characters, values and strings in the terminal.
Let's say that you are expecting to type something like io=pin26state1 to control the Propeller Activity Board's P26 and P27 LEDs. The goal is for your program to load the number (26 or 27) into a variable named pin, and another number (1 or 0) into a variable named state. You could use this to go capture the two values with the %d formatting flag and put them in the variables.
scan("%d%d", &pin, &state);
Don't forget to use the & with the variables. The scan function needs variable addresses, not variable values.
Then, if you want to print the variable values to the terminal, you can use the print function:
print("pin = %d, state = %d", pin, state);
Unlike the scan function, the print function requires variable values in most cases, so the pin and state variables do not have the & character.
Let's try this in a program that controls LEDs.
#include "simpletools.h" int pin, state; int main() { set_direction(26, 1); // 1 -> output set_direction(27, 1); print("Type a 26/27 1/0 variations on:\r"); print("io=26state=1\r"); print("Then, press Enter\r\r"); while(1) { scan("%d%d", &pin, &state); print("pin = %d, state = %d\r\r", pin, state); set_output(pin, state); } }
There are lots more examples of using print and scan with format flags in Propeller C - Start Simple [24].
Now that you can use a program to modify and display information to a page, you can also modify and send information to the Propeller.
Remember, if you followed the instructions in the Join Another Wi-Fi Network [25] section, you’ll have to replace 192.168.4.1 with the Wi-Fi module’s station IP address.
This is going to be similar to what you did in the Load a Test Page into the Module section.
Again, if your Wi-Fi module has joined another network, use the IP address you noted instead of 192.168.4.1.
Want to use the Enter key as a shortcut to clicking Send? Change this line:
<input type = "text" id = "textField" maxlength = "5">
...to:
<input type = "text" id = "textField" maxlength = "5" onchange = "changeText()">
Remember to upload the modified html file to your Wi-Fi module. Then, you can reload the page in your browser using http://<wi-fi-module-ip>/files/text-page-to-micro.html. <wi-fi-module-ip> is either 192.168.4.1 or the IP your module was assigned when you had it join another network.
One way that a web page can transmit information like button clicks and text to a server is through an HTTP POST request. An example of this you might be familiar with is when you submit a web form, and something like ?formdata=WhatYouTyped appears after the web address. Some pages do not show that in response to a search because they’ll send the text invisibly through a part of the HTTP POST called the post body, which is what this example does.
Most of the html and JavaScript works the same way that it did in the Relay Information with JavaScript [26]section you just finished. One of the parts that changed is that now, if you type “Hello” and click the button, the changeText function:
This is a name-value pair that the Propeller C program is expecting in an HTTP POST request that contains the path “/fptm”. So, the line with httpPost(“/fptm”, msg); passes “/fptm” and the msg variable, which is currently storing “txt=Hi!”. The httpPost function receives that path and parameter, and sends them to the Parallax Wi-Fi module web server as a post request. The "/fptm" path is just an abbreviation for "from page to microcontroller". The path can be any name you choose, so long as the C code is listening for posts along the path you use in the JavaScript.
The text highlighting in this code example is provided by Notepad++, available from notepad++.org [27].
After including the simpletools and wifi library header files, the code declares three variables: event, id, and handle. These will store information about events reported by the wifi_poll function. The postFromPageId variable will store the an ID that a call to wifi_listen returns. In the main function, the the wi-fi_start function is set up assuming the Activity Board's SEL socket has been connected to 3.3 V, which passes program downloads, terminal data, and Wi-Fi application data through the Wi-Fi module. If you have connected SEL differently, make sure to update the I/O pins and WX_ALL_COM values.
The last step before entering the program's main loop is to set up a listener for posts to the "/fptm" path, and get an ID for posts along that path from the Wi-Fi module. That's what postFromPageId = wifi_listen(HTTP, "/fptm") does. It tells the Wi-Fi module to accept HTTP requests (typically POST and GET) with a path of "/fptm", and the function returns a listener ID, which gets stored in the postFromPageId int variable.
#include "simpletools.h" #include "wifi.h" int event, id, handle; int postFromPageId; int main() { wifi_start(31, 30, 115200, WX_ALL_COM); postFromPageId = wifi_listen(HTTP, "/fptm"); print("postFromPageId = %d\n", postFromPageId); while(1) { wifi_poll(&event, &id, &handle); print("event = %c, id = %d, handle = %d\r", event, id, handle); if(event == 'P') { if(id == postFromPageId) { print("Incoming POST request\r"); char s[6]; wifi_scan(POST, handle, "txt%s", &s); print("text = %s\n", s); } } pause(500); } }
Inside the while(1) loop, wifi_poll is called with the addresses of three variables: event, id, and handle. If there's nothing to report, wifi_poll stores 'N' (for none) in event, and 0s in id and handle. When a POST request along the "/fptm" path comes through, it'll store 'P' (for POST request) in event, the ID number of the listener in id, and an event handle reference number in handle. The conditions if(event =='P') and if(id == postFromPageId) check to find out if it was a POST request with the ID number that matches the listener we have for HTTP events with the "/fptm" path.
This program could probably get away with processing the post request with a simple condition of if(event != 'N'). As applications get more involved, the program might need to check listener ID number as well as the actual contents of the path. For example, the listener might be set up to look for paths that start with "/fptm" using the "*" wildcard, for "/fptm*". Your program would then have to make some string comparisons to find out what the path actually contained before deciding how to process the information.
When the if(event =='P') and if(id == postFromPageId) conditions are met, the program displays a message about an incoming post, sets up a character array to receive the string, and then calls wi-fi scan. The wifi_scan function can be set up to filter out the "name=" part of "name=value", and optionally put parts of the value string in variables of your choosing. In this example, wifi_scan(POST, handle, "txt%s", &s) is going to:
So, if the post body contains "txt=Hello", the scan function will store "Hello" in the s character array. Then, print("text = %s\n") displays text = hello in the SimpleIDE terminal.
The information below shows the the transmitter (P1 or WX) along with a brief description of the message, the actual base-16 hexadecimal values, and the constant names in the wifi.h file. To find out more about each of the serial values and what they mean, check the Parallax Wi-Fi Module Serial API [28].
Message: P1: Break condition
Hex: [0][0][0][0][0]
Constants: [0][0][0][0][0]
Message: P1: Listen for /fptm path ; WX: Success, listener ID 1
Hex: [fe][e7][f7]/fptm[0d] ; [fe]=S,1[0d]
Constants: [CMD][LISTEN][HTTP]/fptm[CR] ; [CMD]=S,1[CR]
Message: P1: Poll listener IDs ; WX: No requests
Hex: [fe][ec][0d] ; [fe]=N,0,0[0d]
Constants: [CMD][POLL][CR] ; [CMD]=N,0,0[CR]
Message: P1: Poll listener IDs ; WX: POST request handle 5, ID 1
Hex: [fe][ec][0d] ; [fe]=P,5,1[0d] ; [fe][e6]5,txt[0d]
Constants: [CMD][POLL][CR] ; [CMD]=P,5,1[CR] ; [CMD][ARG]5,txt[CR]
Message: P1: Ask for txt in name ; WX: Success, txt=Hello
Hex: [fe]=S,Hi![0d] ; [fe][e5]5,200,2[0d]OK ; [fe]=S,0[0d]
Constants: [CMD]=S,Hi![CR] ; [CMD][REPLY]5,200,2[CR]OK ; [CMD]=S,0[CR]
Message: P1: Handle 5 200 OK (success)* ; WX: Success reply processed
Hex: [fe][ec][0d] ; [fe]=S,5,0[0d]
Constants: [CMD][POLL][CR] ; [CMD]=S,5,0[CR]
Message: P1: Poll listener IDs ; WX: Success, handle 5 confirmed
Hex: [fe][ec][0d] ; [fe]=N,0,0[0d]
Constants: [CMD][POLL][CR] ; [CMD]=N,0,0[CR]
Message: P1: Poll listener IDs ; WX: No requests
Hex: N/A
Constants: N/A
* Reply to handle 5 with the value 200 (success) and 2 characters of text “OK” which can be displayed in the web page’s developer console.
An HTML page that can send “hello” or “bye” to the Propeller can just as easily control circuits. For example, with the LEDs connected to P26 and P27 as shown, the web page can send io=pin26state1 to make an LED connected to P26 turn on. The other three possible combinations are: io=pin27state1, io=pin26state0, io=pin27state0.
Instead of a text field, this example gets its user input from textboxes. Click the checkbox once, and a checkmark appears and the light turns on. Click it again, and the checkbox disappears and the light turns off. With each click of a checkbox, the message the page sends the Propeller (like io=pin26,state1) gets displayed in the page too.
Keep in mind: If you followed the instructions in the Join Another Wi-Fi Network [25] section, you’ll have to replace 192.168.4.1 with the Wi-Fi module’s station IP address.
The HTML and JavaScript is very similar to the previous example. Aside from some variable and function name changes, the main differences are in how the checkboxes are handled.
When a given checkbox is clicked, the onclick event calls the ledVals function, passing the button’s id -either 26 or 27. The paragraph with the ledval ID will hold a copy of the text that gets sent to the Propeller through the Wi-Fi module.
The C program running in the Propeller will have a listener filtering for HTTP POSTs with a path named “/leds”. It also has code that expects a message like “io=pin26state1” to turn the P26 LED on, “io=pin26state0” to turn it off, and so on. So the ledVals function has to assemble a string with the io=pin#state# information, and pass that and the “/leds” path to the httpPost function.
When you click one of the checkboxes, the browser detects the onclick event and calls ledVals function, passing the html's checkbox id (either 26 or 27) to the ledVals pinId parameter. Then, var state = document.getElementById(pinId).checked copies the state of that checkbox (true or false) into a variable named state. Next, var nameVal = “io=pin” + pinId “ “state” + Number(state) uses all that information to put together the “io=pin#state#”. After printing the nameVal string to the ledval paragraph with document.getElementById…, the httpPost(“/leds”, nameVal) function call passes the “/leds” path and the nameVal variable to the httpPost function. Remember, the nameVal variable is the one that contains a string that’s something like “io=pin26state1”.
The checked property is Boolean, that’s true or false. Number(led) converts it to a 1 or 0. The JavaScript responds to the fact that strings are being added together by automatically converting to “1” or “0” before appending the string.Here’s the httpPost function. It transmits the “/leds” path and the param string, which will contain a string like “io=pin26state1” to the Parallax Wi-Fi module that’s hosting the web page, and the module in turn relays that information to the Propeller.
The string “io=pin26state1” is considered a post parameter that consists of a name-value pair. The name is “io” and the value is “pin26state1”.
The fourth line is where this program starts to differ from the previous example. Instead of just declaring postFromPageId, this program instead declares a variable named ledId, along with variables named pin and state. The main function starts off the same, calling wifi_start. In the previous program, the second line in the main function was postFromPageId = wifi_listen(HTTP, "/fptm"). That's because the previous page's JavaScript sent its POST requests with the "/fptm" path. The JavaScript in this example sends its POST requests to a different path: "/leds". So, this program uses ledId = wifi_listen(HTTP, "/leds") to:
After that, it uses simpletools library set_direction function calls to set P26 and P27 to outputs (since the main loop will need to turn those lights on/off).
#include "simpletools.h" #include "wifi.h" int event, id, handle; int ledId, pin, state; int main() { wifi_start(31, 30, 115200, WX_ALL_COM); ledId = wifi_listen(HTTP, "/leds"); print("ledId = %d\n", ledId); set_direction(26, 1); set_direction(27, 1); while(1) { wifi_poll(&event, &id, &handle); print("event = %c, id = %d, handle = %d\r", event, id, handle); if(event == 'P') { if(id == ledId) { print("Incoming POST request\r"); wifi_scan(POST, handle, "io%d%d", &pin, &state); print("pin=%d, state=%d\n", pin, state); set_output(pin, state); } } pause(500); } }
Every time through the while(1) loop, wifi_poll(&event, &id, &handle) checks if any listeners reported events. If there are no events, the event variable will store 'N', and handle & id will both store 0. When a POST request from the browser comes through with a path of "/leds", wifi_poll will set event to 'P', id to whatever value ledId stored during the initialization, and handle to a value the program will use to retrieve the post body. When event stores 'P' and id matches ledId, the program prints the incoming POST request message, then calls wifi_scan.
Instead of the previous example's wifi_scan(POST, handle, "txt%s", &s), this program uses wifi_scan(POST, handle, "io%d%d, &pin &state). The io tells the Wi-Fi module to reply with the value part of a POST that contain something like io=pin26state1. In this example, the Wi-Fi module sends "pin26state1". The %d%d places the first two text strings that represent integer values (like 26 and 1) into the two variable parameters in the list that follows the format string: &pin and &state. This happens in about the same way that the scan statement from Print and Scan Function Review [29] captured that string when you typed it into the SimpleIDE terminal.
With the pin and state variables containing meaningful values, print("pin = %d, state = %d\r", pin, state) displays those values in the terminal. Then, set_output(pin, state) from the simpletools library uses those values to turn P26 or P27 on or off. The pause(500) before repeating the while(1) loop mainly prevents sending data to the SimpleIDE Terminal faster than a human could follow along.
With just a few small tweaks to the JavaScript and Propeller C code, that light control with checkboxes app can be transformed into slider control of servo position.
Keep in mind: 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.
This is the HTML that displays the slider and the angle. When the slider is adjusted, the onchage=”servoPosition(id)” passes the id of the slider to the servoPosition function’s sliderID parameter.
<H2>Servo</H2> Position: 150 <input type="range" id="position" min="30" max="150" value="90" onchange="servoPosition(id)"> 30
The servoAngle variable gets the slider’s value with document.getElementById(sliderId).value. The slider actually counts from 30 to 150, so servoAngle = (150 - servoAngle) + 30 calculates what the angle we really want is. For example, when the slider is ⅓ of the way from left to right, the range type returns 70, but we really want that to be 110. With servoAngle = (150 - servoAngle) + 30 we get 110 = 150 - 70 + 30. After that, nameVal = “angle=” + servoAngle creates the name value pair parameter to send to the httpPost function. For example, with the slider set to ⅓ of the way from left to right, it might send “angle=110”. The Propeller C program has a listener set up to monitor HTTP POST requests with the “/servo” path, so the httpPost call is httpPost(“/servo”, nameVal).
function servoPosition(sliderId) { var servoAngle = document.getElementById(sliderId).value; servoAngle = (150 - servoAngle) + 30; var nameVal = "angle=" + servoAngle; document.getElementById("servoval").innerHTML = nameVal; httpPost("/servo", nameVal); }
As with the text message and LED light control examples, the information about where the web page wants to position the servo will come through as HTTP POST requests. The main changes are:
#include "simpletools.h" #include "servo.h" #include "wifi.h" int event, id, handle; int servoId, angle; int main() { wifi_start(31, 30, 115200, WX_ALL_COM); servoId = wifi_listen(HTTP, "/servo"); print("ledId = %d\n", servoId);
The main loop checks if there are any POST requests coming from with the “/servo” path. When one comes through, event should store 'P', id should match servoId, and handle will have a reference . When these conditions are met, handle will contain the identifier wifi_scan needs to retrieve the POST body's value.
while(1) { wifi_poll(&event, &id, &handle); print("event = %c, id = %d, handle = %d\r", event, id, handle); if(event == 'P') { if(id == servoId) { print("Incoming POST request\r"); wifi_scan(POST, handle, "angle%d\r", &angle); print("servoAngle: %d\n", angle); servo_angle(16, angle * 10); } } pause(500); } }
The name in the name/value pair is "angle", so if the angle is 45 degrees, we expect the POST body to contain "angle=45". With that info, we can make the format string in our wifi_scan function be "angle%d". Adding "angle" makes the Wi-Fi module only transmit the characters that represent the value, like the characters '4' and '5' in the case of 45 degrees. The %d in the format string makes the wifi_scan function put the value "45" corresponds to in the angle variable. So, our wifi_scan function should look like this: wifi_scan(POST, handle, "angle%d", &angle).
The value wifi_scan stores in the angle variable is terms of degrees, but the servo_angle function needs 10ths of degrees. So, instead of servo_angle(16, 45), we'd need servo_angle(450). That's why there's a * 10 in servo_angle(16, angle * 10). It multiplies degrees in the angle variable by 10 to give it to servo_angle in terms of 10ths of degrees.
Up to this point, your page has been sending information to the microcontroller host with HTTP POST requests. There’s also a way for the page to ask the microcontroller for information with HTTP GET requests. Here is an example where each time you click the Update button, the JavaScript in the page makes the browser ask for, get, and display a new value from the Propeller.
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.
Since the Propeller C program counts upwards at several times per second, the number should be higher each time you click the button.
Don’t want to have to click the button to see the value updated? The page can do it automatically. Here’s an example that updates every 3 seconds.
Note: it’s probably not a good idea to try to see how fast you can make this go, because it can end up making the application unresponsive. We have WebSockets (introduced soon) to establish a communication line that’s much more streamlined than HTTP requests.
The HTML has a button with an onclick event to call a function in the JavaScript called getFromMCU. The JavaScript updates the paragraph with the value ID as soon as it gets the reply from the microcontroller.
Using this JavaScript is pretty easy. In getFromMcu, you give it the path, “/tpfm” in this case, which is an abbreviation of “to page from microcontroller." The reply from the Propeller will appear inside the response parameter of useMcuReply(response) when it’s ready. The “when it’s ready” part depends on network traffic and also how long the Propeller takes to get around to answering. For more info on how this code works, there’s an Advanced Topic after an explanation of the Propeller C code.
Keep this in mind: The path name you choose for the GET request has to be the same as the listener the Propeller C code sets up.
The declarations and initializations are about the same as they were with the HTTP POST examples. The getFromPageId variable is unique, and val is a counting variable that stores values that will be sent to the browser every time the button is clicked. After wifi_start, the getFromPageId = wifi_listen(HTTP, "/tpfm") call sets up an HTTP listener. Since POST and GET requests are both part of the same (HTTP) protocol, the same type of listener will hold either POST or GET requests with the listener's path.
#include "simpletools.h" #include "wifi.h" int event, id, handle; int getFromPageId; int val; int main() { wifi_start(31, 30, 115200, WX_ALL_COM); getFromPageId = wifi_listen(HTTP, "/tpfm"); print("getFromPageId = %d\n", getFromPageId);
Inside the main loop, the code adds 1 to a variable named val every 1/2 second. Like all the examples to this point, the main loop also calls the wifi_poll function. Instead of checking for a 'P' (for POST request) in the event variable, this loop checks for a 'G' (for GET request). When event stores 'G' and id matches getFromPageId, it's time to respond to the GET request. Just as wifi_scan loads data from a POST request it receives into variables, wifi_print assembles variable values into a string of characters and sends it to the browser as a response string. In wifi_print(GET, handle, "%d", val), note that there's no name before "d". It's not necessary to respond with a name=value pair, the JavaScript code is only looking for the value.
while(1) { val++; wifi_poll(&event, &id, &handle); print("event = %c, id = %d, handle = %d\r", event, id, handle); if(event == 'G') { if(id == getFromPageId) { print("Incoming GET request, sending %d\r", val); wifi_print(GET, handle, "%d", val); } } pause(500); } }
The true inside req.open(“GET, path, true) means that the request will get processed asynchronously. Processing asynchronously means that the the httpGet function won’t wait till everything is ready because other parts of the page and/or JavaScript might need attention in the meantime. In other words, asynchronous processing is what prevents web pages from becoming unresponsive. The main thread actually executes everything inside the httpGet function right away and returns. However, after the httpGet function is done, req.onreadystatechange contains a function that gets run each time the readyState property advances through its five states.
According to AJAX - The onreadystatechange Event [31], the readyState property advances through five states: (0) request not yet initialized, (1) server connection established, (2) request received, (3) processing request, (4) request finished and response ready. There’s also a status property that’s 200 for “OK” or 404 for “page not found”.
The important thing to keep in mind is that each time the readyState changes, the function inside onreadystatechange automatically executes, checking if readyState has reached state 4 and a status of 200. When both of those are true, the response is ready.
When the response IS finally ready, the callback parameter contains useMcuReply. That’s the function the httpGet(“/tpfm”, useMcuReply) passed when it called httpGet, with the understanding that it would “call back” that usMcuReply function at some later time (when readyState is 4 and status is 200). At that point, callback(req.responseText) becomes useMcuReply(req.responseText).
If the Propeller's response was “42”, that’s what req.responseText will contain, and also what gets passed to the useMcuReply function’s response parameter. Inside useMcuReply, there’s JavaScript that would display “Value: 42” in the paragraph with the value label.
Now that we have a way for the page to make the browser ask the Propeller questions, let's make the page ask for sensor states. In this example, the page makes the browser ask the Propeller about pushbutton states every 3 seconds and updates the states of radio buttons and text displaying the button states.
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.
To really speed things up, we’ll use WebSockets, but that’s in a later activity. For the time being, we can speed it up a little without risking data loss due to unpredictable times it takes for HTTP requests to complete.
Note: it’s probably not a good idea to try to see how fast you can make this go, because it can end up making the application unresponsive. We have WebSockets (introduced soon) to establish a communication line that’s much more streamlined than HTTP requests.
The HTML part of the page is just a couple of radio buttons. You might have seen radio buttons from online quizzes and questionnaires. You normally click them to select your answer or reply. In this example, the JavaScript updates their selected/not selected states according to whether the BASIC Stamp sends a 1 (pressed) or 0 (not pressed) for a given pushbutton. As with other elements, the radio buttons have IDs so that the JavaScript can act on one when a given button is pressed on the Propeller Activity Board.
<body> <H2>Pushbutton States</H2> <p>P4: <input type="radio" id="P4"></p> <p>P3: <input type="radio" id="P3"></p> <p id="value">Waiting...</p> <script>
The var myTimer = setInterval(getFromMcu, 2000) is one way to tell a web page to repeatedly call a function. There’s a lot more to this than meets the eye, but for now, just remember that this line causes the browser to automatically call the getFromMCU function every 2 seconds (2000 ms).
var myTimer = setInterval(getFromMcu, 2000);
The getFromMcu function, which now gets called every 2 seconds, calls the httpGet function. Remember from the How it Works section in Page Requests Info from Propeller [33] that the Propeller’s C program has code that detects when a GET request comes through with a path of “/tpfm” (to page from microcontroller). Remember also that the GET request will take an unknown amount of time and is designed to take care of that in the background while the main thread moves on, and that its second argument is the function to call when it’s done.
function getFromMcu() { httpGet("/btns", useMcuReply); }
This is the httpGet function, which sends the GET request and then eventually reaches a readyState of 4 and calls the callback function, which in your program is useMcuReply. For more info on how this works, revisit the Page Requests Info from Propeller [33]activity’s Advanced Topic section.
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 useMcuReply function is the one that gets called when the httpGet request is done and is really the only function that has changed since Page Requests Info from Propeller. The response parameter is going to contain a reply from the Propeller, which is running Page Displays Buttons Host.c. That program replies with a string that contains the characters representing two binary digits. The four different strings that might come across are “11”, “10”, “01”, and “00”. The left digit is 1 if the P4 pushbutton is pressed, or 0 if it’s not. Likewise, the right digit is 1 if the P3 pushbutton is pressed, and 0 if it’s not. Keep in mind that’s what response contains.
function useMcuReply(response) { var val = document.getElementById("value"); val.innerHTML = "Value: " + response; if(response.charAt(1) == "1") P3.checked = true; else P3.checked = false; if(response.charAt(0) == "1") P4.checked = true; else P4.checked = false; }
The first thing useMcuReply does is change the HTML paragraph with the value ID to something like Value = 01. (That would be if the P3 button is pressed and held down.) The two lines that do that are var val = document.getElementById(“value”); and val.innerHTML = “Value: “ + response. Assuming the response contained “01” that would cause Value = 01 to be displayed.
Next, a couple of if...else statements set the states of the radio buttons. In the case of “01”, the response.charAt(0) gets the leftmost 0th character from the string. Since it’s a “0”, the code sets the radio button with the P4 ID to false (unchecked or un-selected). The second if condition is true since charAt(1), which is the second character in the string, really is “1”. So it sets the checked property in the radio button object with the P3 ID to true, causing it to appear to be checked or selected.
The setup in Page Displays Buttons Host.c is very similar to Val from Micro Host.c from the Page Requests Info from Propeller tutorial. When an HTTP GET request comes through requesting the resource from the “/btns” path, the wifi_poll function stores a value in the id variable that matches getsId. When those two conditions are true, the buttonP4 and buttonP3 variables get values from input function calls that return 1 if a button is pressed or 0 if it is not. Then, wifi_print(GET, handle, "%d%d\r", buttonP4, buttonP3) sends a string with the characters that represent the two binary states followed by a carriage return. The first %d the will be "1" if buttonP4 is 1, or "0" if buttonP4 is 0. The second %d will also be a "1" if buttonP3 is 1, or a "0" if buttonP3 is 0. The result again is "11\r", "10\r", "01\r", or "00\r". The Propeller transmits that to the Wi-Fi module, which relays it to the browser executing the JavaScript.
#include "simpletools.h" #include "wifi.h" int event, id, handle; int buttonId; int buttonP3, buttonP4; int main() { wifi_start(31, 30, 115200, WX_ALL_COM); buttonId = wifi_listen(HTTP, "/btns"); print("getFromPageId = %d\n", buttonId); while(1) { wifi_poll(&event, &id, &handle); print("event = %c, id = %d, handle = %d\r", event, id, handle); if(event == 'G') { if(id == buttonId) { buttonP4 = input(4); buttonP3 = input(3); wifi_print(GET, handle, "%d%d\r", buttonP4, buttonP3); print("Incoming GET request, sending %d%d\r", buttonP3, buttonP4); } } pause(500); } }
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.
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 [33] and Display Pushbutton States [36], 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 [34] 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); } }
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:
<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. [33]
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; }
Links
[1] https://www.parallax.com/product/32420d
[2] https://www.parallax.com/product/32912
[3] http://learn.parallax.com/tutorials/language/propeller-c/propeller-c-simple-circuits
[4] http://learn.parallax.com/tutorials/language/propeller-c/propeller-c-simple-devices
[5] http://learn.parallax.com/tutorials/robot/activitybot/activitybot
[6] http://learn.parallax.com/tutorials/robot/activitybot/activitybot/navigate-infrared-flashlights/build-ir-sensor-circuits
[7] https://www.parallax.com/product/32500
[8] https://www.parallax.com/product/130-32950
[9] http://learn.parallax.com/tutorials/propeller-c
[10] https://drive.google.com/file/d/0B0Hlguyerv4QeEt4bTBZVlNKMVk/view
[11] https://www.parallax.com/downloads/parallax-wi-fi-module-firmware-and-example-files
[12] https://learn.parallax.com/sites/default/files/content/propeller-c-tutorials/Wi-Fi/wiring-Wifi-ABWX.png
[13] http://learn.parallax.com/tutorials/language/propeller-c/parallax-wx-wi-fi-module-prop-c/connect-wx-wi-fi-module-your
[14] http://192.168.4.1/
[15] http://learn.parallax.com/tutorials/activitybot
[16] http://learn.parallax.com/tutorials/language/propeller-c/propeller-c-set-simpleide
[17] http://192.168.4.1/files/val-from-micro.html
[18] http://www.w3schools.com/html/default.asp
[19] http://www.tutorialspoint.com/html5/index.htm
[20] http://www.w3schools.com/js/default.asp
[21] http://www.tutorialspoint.com/javascript/javascript_quick_guide.htm
[22] http://www.w3schools.com/ajax/default.asp
[23] http://www.tutorialspoint.com/ajax/index.htm
[24] http://learn.parallax.com/tutorials/language/propeller-c/propeller-c-start-simple
[25] http://learn.parallax.com/tutorials/language/propeller-c/parallax-wx-wi-fi-module-prop-c/join-another-wi-fi-network
[26] http://learn.parallax.com/tutorials/language/propeller-c/parallax-wx-wi-fi-module-prop-c/relay-information-javascript
[27] http://notepad++.org
[28] https://www.parallax.com/downloads/parallax-wx-wi-fi-module-firmware-guide
[29] http://learn.parallax.com/tutorials/parallax-wx-wi-fi-module-prop-c/print-and-scan-function-review
[30] http://learn.parallax.com/tutorials/language/propeller-c/propeller-c-simple-devices/standard-servo
[31] http://www.w3schools.com/ajax/ajax_xmlhttprequest_onreadystatechange.asp
[32] http://learn.parallax.com/tutorials/language/propeller-c/propeller-c-simple-circuits/check-pushbuttons
[33] http://learn.parallax.com/tutorials/language/propeller-c/parallax-wx-wi-fi-module-prop-c/page-requests-info-propeller
[34] http://learn.parallax.com/tutorials/language/propeller-c/propeller-c-simple-circuits/sense-light
[35] http://192.168.4.1/update-ffs.html
[36] http://learn.parallax.com/tutorials/language/propeller-c/parallax-wx-wi-fi-module-prop-c/display-pushbutton-states