Introduction
What is the Electric Imp?
The electric imp is a WiFi radio, ARM Cortex M3 microcontroller, and 6 I/O pins that runs the imp operating system. The OS allows you to build features and services for devices. It works with the imp Cloud and provides seamless and secure connectivity between your devices, software, third party services and external servers. The electric imp uses patent-pending BlinkUp technology.
Approach a solution to a problem by thinking about the ways one device can interact with others, and think about how their relationships could change or improve over time. electricimp.com suggests that "the act of powering down a work computer at the end of the day could turn on a home heating system so the homeowner arrives to a warm house" or "synchronize sprinklers to a weather website to ensure a lawn or garden is no longer watered if the forecast calls for rain later in the day. You could also start conversations between users and their homes by developing products that send a mobile alert to the user or service department when a device isn’t working properly. Conversely, a user can use a mobile app to turn on a device or check in on its status" (4/15/13).
To develop a solution, you need:
- an imp card
- a development board
- USB Mini cable
- breadboard
- a phone
Setting Up
- Create an account at electricimp.com
- Install the electric imp mobile app on your phone. You only need the mobile app to configure your imp, so if you don't have a smartphone or tablet handy, you can use another person's mobile device - just use your Electric Imp account credentials to log into the app.
- On the mobile app set up WIFI or WPS:
Configuration with WPS button (WiFi Protected Setup)
If you have a router which has a WPS button, press the WPS button on the router then press the WPS button in the app to transmit the WPS command to the imp within 2 minutes.
When the imp is performing WPS, the LED will flash green, red, green, off. WPS setup usually takes 10-20 seconds to complete.Configuration with WPS PIN (WiFi Protected Setup)
If your router has WPS but no button, you can use the WPS PIN function to configure WiFi. Often the WPS PIN will be listed on a sticker on the underside of the router.
Enter this PIN into the app.Configuration with a network name and password
For this, you need to know your WiFi network name (sometimes called SSID) and password. If you did not set these yourself, they may be listed on a sticker on the router.
In the app, the WiFi network name your iOS/Android device is connected to is already filled in;
The WiFi password cannot be automatically extracted by the app. You must enter this yourself, but it password can be saved within the electric imp app to make setup of subsequent devices easier.
- Insert the Imp into the board.
- Get your phone ready to blink. Don't press the button yet, but make sure your mobile app is open and ready to go.
- Plug in the April Development board into the computer.
- To blink the imp, you will press the Send BlinkUp button and hold your device as close to the sensor of the imp as possible, shielding the imp from ambient light. Your device will display a series of flashes (this is how the imp gets configured).
- While it is blinking, blink it from your mobile app.
- The imp should blink green and then red. If this does not happen, you may have let the imp sit powered up too long before blinking. Doing the blink-up right away seemed to work.
- Connect an LED between pin 9 and GND
- Log into the IDE
- Click on Active Models to expand the drop down. You should see the device you've BlinkedUp.
- Click on the widget to open the Device Settings window.
If you want, you can give your device a name (*note - this name will be associated with the April board you plugged your imp into, not the imp itself*).
Click on the drop down box below Associated model and type the name for the new model you want to create.
- Click Save Changes.
-
You should see the model you just created show up in the left hand nav bar. If you toggle the model, you should see your device. Clicking on the device will open the code editor.
Alternatively, you can select the model, then target the device in this pulldown:
- paste the following into the device block:
/* create a global variabled called led, and assign pin9 to it This makes it easier to reference the pin in the code:*/ led <- hardware.pin9; /*configure led to be a digital output A pin that is configured to be a DIGITAL_OUT pin will have a voltage of 3.3V when set to 1 and will have a voltage of 0V when we set the pin low with led.write(0).*/ led.configure(DIGITAL_OUT); /* create a global variable to store current state of the LED*/ state <- 0; /*create a function that will change the state of the LED pin every half second. The first thing the function does is flip the value of state:*/ function blink() { state = 1-state; // write current state to led pin led.write(state); // schedule imp to wakeup in .5 seconds and do it again. imp.wakeup(0.5, blink); } // start the loop blink();
- Click Build and Run button to upload the code to your device:
- If all went well, you can now power up (unplug and plug in) the commissioned imp
The Internet of Things
Previously you created a program where an LED blinks on and off. That's great, but what you really want to do is control the LED over the internet.- Here is the Device code. Paste this into the Device window:
/* create a global variabled called led, and assign pin9 to it*/ led <- hardware.pin9; // configure led to be a digital output led.configure(DIGITAL_OUT); // function to turn LED on or off function setLed(ledState) { server.log("Set LED: " + ledState); led.write(ledState); } // register a handler for "led" messages from the agent agent.on("led", setLed);
The agent.on() function registers a listener for messages passed from the agent. Whenever the agent sends a message to the device called "led," the setLed function will be executed.agent.on("led", setLed);
-
Here is the Agent Code. Paste it in the appropriate window:
// Log the URLs we need server.log("Turn LED On: " + http.agenturl() + "?led=1"); server.log("Turn LED Off: " + http.agenturl() + "?led=0"); function requestHandler(request, response) { try { // check if the user sent led as a query parameter if ("led" in request.query) { // if they did, and led=1.. set our variable to 1 if (request.query.led == "1" || request.query.led == "0") { // convert the led query parameter to an integer local ledState = request.query.led.tointeger(); // send "led" message to device, and send ledState as the data device.send("led", ledState); } } // send a response back saying everything was OK. response.send(200, "OK"); } catch (ex) { response.send(500, "Internal Server Error: " + ex); } } // register the HTTP handler http.onrequest(requestHandler);
In the agent code, you are registering an HTTP handler. The HTTP handler will be executed each time an HTTP request is made to the agent URL (which is shown at the top of the agent code window).
In your HTTP handler, you are checking to see if a query parameter called led was supplied, if it was, the program sends a message to your device, along with the value of the led query parameter. The code sends the message to the device using the following line: device.send("led", ledState); When this line executes, it sends a message to the device which results in the execution of setLed(ledState). -
Copy and paste your agentURL into a new browser window, and add and add ?led=1 (to turn the led on) or ?led=0 (to turn the led off). It should look something like this:
https://agent.electricimp.com/your_agentURL?led=1
https://agent.electricimp.com/?your_agentURL?led=0
GPIO
One of the things you can do with your imp is turn things on or off, or monitor whether things are on or off. Any of the imp's pins can be configured as a digital input or output (GPIO , General Purpose Input/Output). But the pins aren’t just for standard general purpose input and output; each one has the ability to work with a range of data protocols (UART, I2P, and SPI), handle analog to digital signal conversion, and manage their power output (or PWM).The imp can source or sink up to 4 mA, no more, and the imp cannot tolerate voltages greater than VDD + 0.3 (Since you're usually running at 3.3V, this means your maximum voltage on a pin is 3.6V). Always take care to protect your hardware from sourcing or sinking excessive current.
Input
For buttons, you should use the internal pullups of the imp when wiring. This means that when the button is not pressed it is high and when it is pressed it is puled LOW to GND. To wire a button on pin 7 you connect pin 7 to GND through the button.Here is the code to configure pin 7 as a digital input with the internal pullup enabled:
button <- hardware.pin7; function buttonPress() { local state = button.read(); if (state == 1) { // when the button is pressed server.log("release"); } else { // when the button is released server.log("press"); } } button.configure(DIGITAL_IN_PULLUP, buttonPress);
Add to the program above code that will turn on an LED when the button is pressed and turn it off when the button is released.
Analog In
Here a potentiometer in connected to pins 2, 3.3 and GND// create pot variable pot <- hardware.pin2; // configure pin pot.configure(ANALOG_IN); function poll() { // read pin and log server.log(pot.read()); // wake up in 0.1 seconds and do it again imp.wakeup(0.1, poll); } // start the loop poll();
Using a Thermistor
- Connect one of the thermistor to 3V3 on the imp.
- Connect the other end of the thermistor to PIN5 on the imp.
- Connect one of the 10kΩ resistor to PIN5 on the imp.
- Connect the other end of the 10kΩ resistor to GND.
// assign hardware.pin5 to a global variable therm <- hardware.pin5; // configure pin5 to be an ANALOG_IN therm.configure(ANALOG_IN); // these constants are particular to the thermistor you're using // check your datasheet for what values you should be using const b_therm = 3977.0; const t0_therm = 298.15; // the resistor in the circuit (10KΩ) const R2 = 10000.0; function GetTemp_F(v) { local Vin = hardware.voltage(); local Vout = Vin * therm.read() / 65535.0; local R_Therm = (R2*Vin / Vout) - R2; local ln_therm = math.log(10000.0 / T_Therm); local temp_K = (t0_therm * b_therm) / (b_therm - t0_therm*ln_therm); local temp_C = temp_K - 273.15; local temp_F = temp_C * 9.0 / 5.0 + 32.0; return temp_F; } function poll() { // Get and log Fahrenheit temperature local temp = GetTemp_F(); server.log(temp + " F"); // wakeup in 5 seconds and read the value again: imp.wakeup(5.0, poll); } poll()
Before you start, you will need two values from the thermistor's datasheet (this example uses a 10kΩ NTCLE100E3 thermistor)
B-Value (b_therm): 3977
Nominal Temperature (t0): 25 Celsius = 298.15 Kelvin
OUTPUT
PWM to control brightness
You can control the brightness of an LED with Pulse Width Modulation (PWM). Pulse Width Modulation means sending a series of pulses to the output pin. By varying the length of the pulses with pin.write you can affect how much power the LED gets, which will change how bright it is.Connect an LED between PIN9 and GND
The configure function for a PWM_OUT takes 2 extra parameters. The first parameter is the Period (the inverse of the frequency). You want to drive LEDs at around 400Hz, so divide 1.0 by 400.0 to get the period. The second parameter is the initial Duty Cycle, the proportion of the 'on' time in the period. Modify the duty cycle with a pin.write, essentially the initial value you want to write to the pin. Since the example starts with the LED off, it should be set to 0.
Once the LED is configured, you can control it's brightness with a hardware.pin.write(). Pass a float (between 0.0 and 1.0) that corresponds to how bright you want the LED. If you wanted to drive the LED at 50% brightness, you would use: led.write(0.5);
led <- hardware.pin9; led.configure(PWM_OUT, 1.0/400.0, 0.0); ledState <- 0.0; ledChange <- 0.01; function pulse() { // write value to pin led.write(ledState); // change the value ledState = ledState + ledChange; // Check if we're out of bounds if (ledState >= 1.0 || ledState <= 0.0) { // flip ledChange if we are ledChange = ledChange * -1.0; } // schedule the loop to run again: imp.wakeup(0.09, pulse); } pulse();
Servo-Using PWM
You can connect a servo to your imp, and controll its position with Pulse Width Modulation (PWM).- Connect the servo's yellow (control) wire to PIN7
- Connect the servo's red (power) wire to Vin- it needs to go to Vin and not 3.3, because it needs more than 3.3V to run.
- Connect the servo's black (ground) wire to GND
// These values may be different for your servo const SERVO_MIN = 0.03; const SERVO_MAX = 0.1; // create global variable for servo and configure servo <- hardware.pin7; servo.configure(PWM_OUT, 0.02, SERVO_MIN); // expects a value between 0.0 and 1.0 function SetServo(value) { local scaledValue = value * (SERVO_MAX-SERVO_MIN) + SERVO_MIN; servo.write(scaledValue); } // expects a value between -80.0 and 80.0 function SetServoDegrees(value) { local scaledValue = (value + 81) / 161.0 * (SERVO_MAX-SERVO_MIN) + SERVO_MIN; servo.write(scaledValue); } // current position (we'll flip between 0 and 1) position <- 0; function Sweep() { // write the position SetServo(position); // invert the position position = 1.0 - position; // do it again in half a second: imp.wakeup(1.0, Sweep); } Sweep()
// expects a value between -80 and 80 function SetServoDegrees(value) { local scaledValue = (value + 81) / 181.0 * (SERVO_MAX-SERVO_MIN) + SERVO_MIN; servo.write(scaledValue); }
Add an agent so that by passing it a number between -80 and 80 you control the servo position.
Another Servo Example
This code is from Tom Allen's Projects- Device code:
//Servo controller turns http calls into servo movement. g_minDutyCycle <- 0.03 g_maxDutyCycle <- 0.10 g_position <- 0.0; // Convert a position value between 0.0 and 1.0 to a duty cycle for the PWM. function pos2dc( value ){ return g_minDutyCycle + (value*(g_maxDutyCycle-g_minDutyCycle)); } function move_to( pos ){ g_position = pos; hardware.pin1.write( pos2dc(g_position) ); server.log(format("Servo moved to %.2f, duty cycle=%.3f", g_position, pos2dc(g_position))); } function sweep(){ move_to(0.0); imp.sleep(0.5); move_to(1.0); imp.sleep(0.5); move_to(0.0); imp.sleep(0.5); move_to(0.5); imp.sleep(0.25); move_to(0.6); imp.sleep(0.25); move_to(0.4); imp.sleep(0.25); move_to(0.5); } // Handlers for messages from the Agent. agent.on("move", function (data) { server.log("Device received position request: "+data.pos); if( hardware.getimpeeid() == data.uid ){ move_to(data.pos); }else{ server.log("Mismatched Impee UID between move request and hardware.") } }); agent.on("sweep", function (unused_param) { server.log("Device received sweep request"); sweep(); }); function configureHardware(){ // Configure hardware: // the servos used in this example have ~170 degrees of range. Each servo has three pins: power, ground, and pulse in // The width of the pulse determines the position of the servo // The servo expects a pulse every 20 to 30 ms // 0.03 ms pulse -> fully counterclockwise // 0.10 ms pulse -> fully clockwise // set up PWM on pin1 at a period of 20 ms, and initialize the duty cycle hardware.pin1.configure(PWM_OUT, 0.02, pos2dc(g_position)); server.log("Hardware Configured"); } // imp.configure registers us with the Imp Service // The first parameter Sets the text shown on the top line of the node in the planner - i.e., what this node is // The second parameter is a list of input ports in square brackets // The third parameter is a list of output ports in square brackets imp.configure("Location Clock", [], []); configureHardware();
- Agent code:
// HTTP Request handlers expect two parameters: // request: the incoming request // response: the response we send back to whoever made the request function requestHandler(request, response) { try { server.log("request.body: "+request.body); // decode the json - if it's invalid json an error will be thrown local data = http.jsondecode(request.body); server.log("jsondecoded data: "+data); // Check if the query is valid (contains 'uid' and 'pos' and uid matches the hardware) if( !("pos" in data) ) { server.log("Error: Missing \"pos\" element. Data = "+data); response.send(500, "Error: Missing \"pos\" element."); } else if( !("uid" in data) ) { server.log("Error: Missing \"uid\" element. Data = "+data); response.send(500, "Error: Missing \"uid\" element."); } else { server.log("HTTP input: pos="+data.pos+", uid="+data.uid); // if it was, send the data to the device device.send("move", data); // send a response back to whoever made the request response.send(200, "OK - Servo moved to position "+data.pos); } } catch (ex) { // if an error occured, send a 500 Internal Server Error server.log("Internal Server Error: " + ex); response.send(500, "Internal Server Error: " + ex); } } // your agent code should only ever have ONE http.onrequest call. http.onrequest(requestHandler); device.onconnect(function() { server.log("Device connected to Agent"); device.send("sweep",null); }); device.ondisconnect(function() { server.log("Device disconnected from Agent"); });
- HTML code:
<html> <head> <script type="text/javascript"> function moveTo(pos,form) { var url = "https://api.electricimp.com/v1/.../..." var uid = form.uid.value; var value = "{ \"pos\": " + pos + ", \"uid\": \"" + uid + "\" " + " }" //check support for the XMLHttpRequest object if (window.XMLHttpRequest) { var httpRequest = new XMLHttpRequest(); // Call the open() method to make the request httpRequest.open("POST", url, true); // Send the proper header information along with the request httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); httpRequest.setRequestHeader("Connection", "close"); // Send the request httpRequest.send('value='+value); httpRequest.onreadystatechange = getData; // Assign the callback function } //else, alert else { alert("Screw IE6...") } } //This function handles the server response and does nothing at all with it. function getData() { // Do nothing. } </script> </head> <body> <form name="http servo" action="#" method="post"> UID of electric impee: <input type="text" name="uid" value="your impee's uid goes here"><br> <input type="button" value="A" onclick="moveTo(0.0, this.form)" /> <input type="button" value="B" onclick="moveTo(0.25, this.form)" /> <input type="button" value="C" onclick="moveTo(0.5, this.form)" /> <input type="button" value="D" onclick="moveTo(0.75, this.form)" /> <input type="button" value="E" onclick="moveTo(1.0, this.form)" /> <br> </form> </body> </html>
RGB LED with agent
- Look at the code below and wire up your imp accordingly:
/* Electric Imp Web-controlled LEDs by: Jim Lindblom SparkFun Electronics date: November 1, 2013 license: Beerware. Please use, reuse, and modify this code. If you find it useful, buy me a beer some day! This is a simple electric imp example, which shows how to interface the imp with an agent and webpage. This example code goes hand-in-hand with an HTML webpage. Check out this page for more information: https://learn.sparkfun.com/tutorials/electric-imp-breakout-hookup-guide/example-2-web-control This will show how you can use html color, text, and radio form inputs to control LEDs on/off, PWM them, and set a timer to turn them off. Circuit: A common cathode RGB LED is connected to the imp's pins 1, 2, and 5. The red anode connects to 1 through a 47 Ohm resistor, green 2, and blue 5. The cathode of the LED connects to ground. Another simple, red LED is connected to the imp to imp pin 9, through another 47 Ohm resistor. The cathode of the LED is grounded. */ imp.configure("LED Web Control", [], []); // Configure the imp /////////////// // Pin Setup // /////////////// // Setup reference variables for our pins: redPin <- hardware.pin1; // R of RGB greenPin <- hardware.pin2; // G of RGB bluePin <- hardware.pin5; // B of RGB ledPin <- hardware.pin9; // Lonely red LED // Configure our pins: ledPin.configure(DIGITAL_OUT); // Simple digital output redPin.configure(PWM_OUT, 0.01, 0); // PWM output 10ms clock, off greenPin.configure(PWM_OUT, 0.01, 0); // PWM output 10ms clock, off bluePin.configure(PWM_OUT, 0.01, 0); // PWM output 10ms clock, off ///////////////////////////////// // Agent Function Declarations // ///////////////////////////////// // setLed will turn the lonely red LED on or off. // This function will be called by the agent. function setLed(ledState) { ledPin.write(ledState); } // setRGB will take a table input, and set the RGB LED accordingly. // the table input should have parameters 'r', 'g', and 'b'. // This function will be called by the agent. function setRGB(rgbValue){ bluePin.write(rgbValue.b/255.0); redPin.write(rgbValue.r/255.0); greenPin.write(rgbValue.g/255.0); } // setUser will print out to the log the name of the LED changer // This function will be called by the agent. function setUser(suspect){ server.log(suspect + " set the LEDs."); } // setTimer will turn the LEDs off after a specified number of seconds // This function will be called by the agent. function setTimer(time){ if (time != 0) imp.wakeup(time, ledsOff); // Call ledsOff in 'time' seconds. } /////////////////////////////////// // Important Agent Handler Stuff // /////////////////////////////////// // Each object that the agent can send us needs a handler, which we define with // the agent.on function. The first parameter in agent.on is an identifier // string which must be matched by the sending agent. The second parameter is // the name of a function to be called. These functions are already defined up // above. agent.on("led", setLed); agent.on("rgb", setRGB); agent.on("user", setUser); agent.on("timer", setTimer); ////////////////////// // Helper Functions // ////////////////////// // ledsOff just turns all LEDs off. function ledsOff(){ ledPin.write(0); redPin.write(0); greenPin.write(0); bluePin.write(0); }
- The agent is a piece of squirrel code living and running in the electric imp cloud. While the imp is managing all of its hardware pins, the agent can be off mingling with other servers and dealing with Internet traffic. There are built in functions which allow the imp to send data to the agent, and vice-versa.
In this example, the agent is set up to listen for HTTP requests. Upon receiving a request, the agent will parse the query, and relay the important information back to the imp./* Agent for imp Web-Controlled LEDs by: Jim Lindblom SparkFun Electronics date: November 1, 2013 license: Beerware. Please use, reuse, and modify this code. If you find it useful, buy me a beer some day! This is the agent portion of the LED Web Controller. It defines how http requests to https://agent.electricimp.com/XXXXXXXXXXXX are handled. Check your agent URL to find out what, exactly, XXXXXXXXXXXX is. For example, if your agent url is https://agent.electricimp.com/UpyYpRLmBB7m sending https://agent.electricimp.com/UpyYpRLmBB7m?led=0 should turn the lonely red led off. https://agent.electricimp.com/UpyYpRLmBB7m?led=1 would turn the LED on. * There are also request handlers for "rgb", which should be a #RRGGBB formatted string. E.g: https://agent.electricimp.com/UpyYpRLmBB7m?rgb=%238500b7 * A "user" request handler can receive a string. E.g.: https://agent.electricimp.com/UpyYpRLmBB7m?user=Jim * And a "timer" handler looks for a number-looking string. E.g.: https://agent.electricimp.com/UpyYpRLmBB7m?timer=10 The parameters can be combined in one request. E.g.: https://agent.electricimp.com/UpyYpRLmBB7m?led=1&rgb=%237f3fff&timer=10&user=Jim */ // At the start, print a message to say we're online, and print the agent URL: server.log("LED Web Control Agent Online: " + http.agenturl()); // requestHandler handles all http requests coming into the agent. It's only // setup to look for a select few requests: "led", "rgb", "user" and "timer". function requestHandler(request, response) { try { // Try provides us with exception handling, in case a runtime error occurs // check if the user sent led as a query parameter if ("led" in request.query) { // if they did, and led=1.. set our variable to 1 if ((request.query.led == "1") || (request.query.led == "0")) { // convert the led query parameter to an integer local ledStatus = request.query.led.tointeger(); // send "led" message to device, and send ledState as the data device.send("led", ledStatus); } } // check if an "rgb" query was received: if ("rgb" in request.query) { // colors are sent as a string, we've got to do some work to convert // them to a number, which is eventually what we'll need to do // pwm on our RGB led pins. local color = request.query.rgb; // get the query into a variable if (color[0] == '#') { // The request should start with '#' (%23) // We'll construct a table with three parameters: r, g, and b // Do some work to convert r, g, and b from ASCII characters // to 0-255 values. local returnTable = { r = ASCIItoHex(color[1])*16 + ASCIItoHex(color[2]) g = ASCIItoHex(color[3])*16 + ASCIItoHex(color[4]) b = ASCIItoHex(color[5])*16 + ASCIItoHex(color[6]) }; device.send("rgb", returnTable); // send our color table to the imp } } // check if a "user" query was received. if ("user" in request.query) { device.send("user", request.query.user); // Simply pass the value out to the imp. } // check if a "timer" query was received: if ("timer" in request.query) { // convert to an integer, and pass it out to the imp. device.send("timer", request.query.timer.tointeger()); } // send a response back saying everything was OK. response.send(200, "OK"); } catch (ex) { response.send(500, "Internal Server Error: " + ex); } } // Set up a handler for HTTP requests. This is the function that we defined above. // https://electricimp.com/docs/api/http/onrequest/ http.onrequest(requestHandler); ////////////////////// // Helper Functions // ///////////////////////////// // This function converts an ASCII character to a number function ASCIItoHex(colorNibble) { if ((colorNibble >= '0') && (colorNibble <= '9')) { return colorNibble - 48; } else if ((colorNibble >= 'a') && (colorNibble <= 'f')) { return colorNibble - 87; } else if ((colorNibble >= 'A') && (colorNibble <= 'F')) { return colorNibble - 55; } }
- Instead of typing in parameters, you are going to create a webpage with a form:
- Open BBEdit and create a new blank document.
- Paste the following code into the blank document:
<html> <head> </head> <body onLoad=updateURL()> <h4>What is your agent's url?</h4> <form name="url"> https://agent.electricimp.com/<input type="text" name="agentUrl" placeholder="UpyYpRLmBB7m" onChange=updateURL()> </form> <h4>Imp Inputs:</h4> <form name="leds" id="ledSend" method="get"> Lonely Red LED: <input type="radio" name="led" value="0" checked>Off <input type="radio" name="led" value="1">On<br> Set the RGB LED: <input type="color" name="rgb"> (Chrome/Opera use color input, other browsers format as "#XXXXXX", where X is 0-9, a-f, or A-F.) <br> How long should the LEDs stay on? <input type="text" name="timer" value="10">seconds<br> Your name? So we know who to blame! <input type="text" name="user" placeholder="Your name here"><br> <br> <input type="submit" value="Update!"> </form> </body> <script language="javascript"> function updateURL() { ledForm = document.leds; urlForm = document.url; ledForm.action = "https://agent.electricimp.com/" + urlForm.agentUrl.value; } </script> </html>
- Save the file with the .html extension
- Open in a browser and test
- Open BBEdit and create a new blank document.
Web Response
What if you wanted to send data from the imp back to a web page? This example from Sparkfun shows how to use the imp to post a response to a web server. Maybe you want to monitor the light level in your room with a simple photocell? Or live stream the temperature in a classroom.- Create a new model
- Copy, paste and edit the following Device code:
/* electric imp Web Response Example (device) by: Jim Lindblom SparkFun Electronics date: November 5, 2013 license: Beerware. Use, reuse, and modify this code however you see fit. If you find it useful, buy me a beer some day! The idea for this code was inspired by this gist by industrialinternet: https://gist.github.com/industrialinternet/5419730 This example code demonstrates how an imp can publish information to a webpage. All of the imp's pins are configured as inputs. They can be either digital or analog. There are three parts to this code: 1. The imp (device) code. The imp itself simply reads its pin values, and sends them out to the agent. 2. The agent code. The agent waits for an http request. On such a request, it'll construct a JSON of the imp's pin values, and respond with that. 3. An html page to make the http request and handle the response from the agent. There are a number of ways to do this. See one example at the bottom of The agent code. */ /////////// // Setup // /////////// // Register imp. This good practice: imp.configure("Imp Pin Web Response",[],[]); // Configure pins: hardware.pin1.configure(DIGITAL_IN_PULLUP); hardware.pin2.configure(DIGITAL_IN_PULLUP); hardware.pin5.configure(ANALOG_IN); hardware.pin7.configure(DIGITAL_IN_PULLUP); hardware.pin8.configure(DIGITAL_IN_PULLUP); hardware.pin9.configure(DIGITAL_IN_PULLUP); ////////////////////////// // Function Definitions // ////////////////////////// // sendPins reads each of the pins, stores them in a table, and sends that // table out to the agent. // It calls itself every 100ms -- 10 times a second. function sendPins(){ // Read each of the pins and store them in a table. // The key names -- "pin1", "pin2", etc. -- should be kept the same, unless // you also change them in the device.on() function on the agent. local pinValues = { pin1 = hardware.pin1.read(), pin2 = hardware.pin2.read(), pin5 = hardware.pin5.read(), pin7 = hardware.pin7.read(), pin8 = hardware.pin8.read(), pin9 = hardware.pin9.read(), voltage = hardware.voltage() // We'll also send the operating voltage. } // Once the table is constructed, send it out to the agent with "impValues" // as the identifier. agent.send("impValues", pinValues); // Schedule a wakeup in 100ms, with a callback to this function. imp.wakeup(0.1, sendPins); } sendPins(); // Call sendPins once, and let it do the rest of the work.
- In the above code there is a line agent.send(string, object). This function is used to send data from the imp, to the agent. In the Agent code you will need to create a handler to deal with the data sent by the imp.
You will need to:
- Define an imp handler function to deal with the data sent by the imp and store the data sent by the imp into a global variable.
- Create another handler to be called when an HTTP request is received. Upon receiving the request the agent will construct a response based on the data received from the imp, and send that out to the requester.
/* electric imp Web Response Example (agent) by: Jim Lindblom SparkFun Electronics date: November 5, 2013 license: Beerware. Use, reuse, and modify this code however you see fit. If you find it useful, buy me a beer some day! The agent half of this code accomplishes two tasks: 1. In the device.on("impValues", function) definitions, the agent receives a table of pin values from the imp. It stores those values in a global variables. 2. On an http request, respondImpValues(request, response) is called. This function constructs a JSON of the imp pin values, and responds with that. Also, check the comment at the bottom of this code for an example HTML file, which sends a request to the imp, then parses and prints the response. */ ////////////////////// // Global Variables // ////////////////////// _pin1 <- ""; // Stores pin 1 value received from imp _pin2 <- ""; _pin5 <- ""; _pin7 <- ""; _pin8 <- ""; _pin9 <- ""; _voltage <- ""; ////////////////////////// // Function Definitions // ////////////////////////// // respondImpValues is called whenever an http request is received. // This function will construct a JSON table containing our most recently // received imp pin values, then send that out to the requester. function respondImpValues(request,response){ // First, construct a JSON table with our received pin values. local pinTable = { "pin1": ""+_pin1+"", // e.g.: "pin1" : "1" "pin2": ""+_pin2+"", "pin5": ""+_pin5+"", // e.g.: "pin5" : "48491" "pin7": ""+_pin7+"", "pin8": ""+_pin8+"", "pin9": ""+_pin9+"", "voltage": ""+_voltage+"" + " V", // e.g.: "voltage" : "3.274 V" } // the http.jsonencode(object) function takes a squirrel variable and returns a // standardized JSON string. - https://electricimp.com/docs/api/http/jsonencode/ local jvars = http.jsonencode(pinTable); // Attach a header to our response. // "Access-Control-Allow-Origin: *" allows cross-origin resource sharing // https://electricimp.com/docs/api/httpresponse/header/ response.header("Access-Control-Allow-Origin", "*"); // Send out our response. // 200 is the "OK" http status code // jvars is our response string. The JSON table we constructed earlier. // https://electricimp.com/docs/api/httpresponse/send/ response.send(200,jvars); } // device.on("impValues") will be called whenever an "impValues" request is sent // from the device side. This simple function simply fills up our global variables // with the equivalent vars received from the imp. device.on("impValues", function(iv) { _pin1 = iv.pin1; _pin2 = iv.pin2; _pin5 = iv.pin5; _pin7 = iv.pin7; _pin8 = iv.pin8; _pin9 = iv.pin9; _voltage = iv.voltage; }); /////////// // Setup // /////////// // http.onrequest(function) sets up a function handler to call when an http // request is received. Whenever we receive an http request call respondImpValues // https://electricimp.com/docs/api/http/onrequest/ http.onrequest(respondImpValues);
- The device.on() function sets up a handler to be called when the imp sends a defined string. In this case, we're looking for the imp sending impValues. The data associated with this string is a table, full of all of the imp's pin readings. That function is called every time the imp sends that specific string to the agent.
- The respondImpValues() function is the handler for any HTTP request (using the http.onrequest function). This function constructs a JSON string of data, in the form of"key":"value", "key":"value", which the requesting HTTP client should be able to parse and understand.
- You can build and run the model, but you won't see anything yet.You need to create a web page that can both send an HTTP request and deal with the response from the agent. Open BBEdit and create a new blank document. Paste in the following:
<html> <head> <title>Electric Imp Breakout</title> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.js"></script> <script> $( function() { // Edit these values first! The externalURL variable should be the // unique URL of your agent. e.g. the last part of: // https://agent.electricimp.com/your_agent's_unique_URL // pollRate defines how often the values on your page will refresh. var externalURL ="your_agent's_unique_URL"; var pollRate ="1000"; function poll(){ // Construct an ajax() GET request. // http://www.w3schools.com/jquery/ajax_ajax.asp $.ajax({ type: "get", url: "https://agent.electricimp.com/"+externalURL, // URL of our imp agent. dataType: "json", // Expect JSON-formatted response from agent. success: function(agentMsg) { // Function to run when request succeeds. // jQuery find "pin1" id and overwrite its data with "pin1" key value in agentMsg $("#pin1").html(agentMsg.pin1); $("#pin2").html(agentMsg.pin2); $("#pin5").html(agentMsg.pin5); $("#pin7").html(agentMsg.pin7); $("#pin8").html(agentMsg.pin8); $("#pin9").html(agentMsg.pin9); $("#vin").html(agentMsg.voltage); updateBG(agentMsg.pin5); // Try this if you have a photocell connected to pin 5 }, error: function(err) { console.log("err"+ err.status) } }); } // setInterval is Javascript method to call a function at a specified interval. // http://www.w3schools.com/jsref/met_win_setinterval.asp setInterval(function(){ poll(); }, pollRate); // This function updates the function updateBG(lightSensor) { if (lightSensor > 30000) { document.body.style.backgroundColor = "#FFFFFF"; } else { document.body.style.backgroundColor = "#AAAAAA"; } } }); </script> </head> <body> <h3>Imp Pins:</h3> <div id="pins"> <p> <b>Pin 1:</b> <span id="pin1"><!-- This is where the pin 1 reading will go --></span></p> <p> <b>Pin 2:</b> <span id="pin2"><!-- This is where the pin 2 reading will go --></span></p> <p> <b>Pin 5:</b> <span id="pin5"><!-- This is where the pin 5 reading will go --></span></p> <p> <b>Pin 7:</b> <span id="pin7"><!-- This is where the pin 7 reading will go --></span></p> <p> <b>Pin 8:</b> <span id="pin8"><!-- This is where the pin 8 reading will go --></span></p> <p> <b>Pin 9:</b> <span id="pin9"><!-- This is where the pin 9 reading will go --></span></p> <p> <b>Voltage:</b> <span id="vin"><!-- This is where the voltage reading will go --></span></p> </body> </html>
- Save with the .html extension
- The poll() function in the <script> area up top sets up an AJAX request to the electric imp agent. If the request succeeds, function(agentMsg) is executed. This function parses the JSON message received from the agent, and dynamically updates the data in your blank <span> tags defined below. poll() is set to be called every pollRate (defaulted to 1000) milliseconds.
Review
- Edit the imp Device code to send the desired data to the agent. Use the agent.send() function to do this.
- Edit the Agent code in two places:
Read the data in from the imp in the device.on() function.
In an HTTP request handler, send the data out as a JSON string. - Add something in the AJAX success function to look for the correct JSON "key" and "value" combination. Then do something with that data.
- Add a location in the HTML body to show the data. E.g. <span id="myData"><!--Data goes here--></span>
Best Practices
imps that need to stay awake and connected to the cloud service should do a server.log every few minutes:,
function phoneHome(){ //Ping the cloud service. server.log("I'm still awake."); //Phone home again every 4 minutes. imp.wakeup(240, phoneHome); }
Squirrel
Squirrel is an object oriented language similar to C or Javascript.The official language documentation can be found at squirrel-lang.org . Just note that the imp uses a slightly modified version of Squirrel 3.0.4.
There are a few differences between the standard Squirrel libraries and imp Squirrel's libraries. The biggest one is that the Squirrel math library is in its own namespace, math. So where the official Squirrel documentation talks about, acos(), on an imp that would be math.acos(). Note that all file handling calls do not work with the imp as there's no filesystem on the imp.
xively
xively is is an online database service allowing developers to connect sensor-derived data (e.g. energy and environment data from objects, devices & buildings) to the Web. open API rapidly create connected objects.
Adding Remote Control
The power of Electric Imp is in connecting your appliances to the internet. You are now going to add a remote control so the blinking may be controlled from the Planner.
The Planner and impees pass data to-and-fromeach other via connections referred to as input and output ports. From the imp's perspective an input accepts data sent from the Planner to the imp, while an output is the reverse. In this program you will only be concerned with the input port - a channel through which you can control operation of our LED.
To use the remote control, you will have to edit your blink program by adding an inhibit variable which, when set, will cause the blink function to switch off the LED and exit without setting a new wakeup timer. From that moment there will be no further blink activity until something happens to re-enable it.
-
Click edit to edit your blinking code.
- Add a new variable:
// Variable to represent LED inhibit state inhibit <- 0;
- Modify the blink function so that now the contents of the function look like this:
if(inhibit){ // Blinking inhibited, turn off the LED hardware.pin9.write(0); }else{ // Change state ledState = ledState?0:1; server.show(ledState); // Reflect state to the pin hardware.pin9.write(ledState); // Schedule the next state change imp.wakeup(0.1, blink); }
- Define a class to represent your input port. You'll derive from the InputPort class and specify a data type of number. Zero will switch off blinking, and non-zero will switch it on.
Set two member variables to give the port a name (which is displayed in the Planner to help with connecting nodes together) and define the data type. Alternatively these parameters can have been passed to the class constructor.
The InputPort class has a method set which is called when data arrives from the Planner. Override this method with your own implementation which will set or clear the inhibit flag depending on the value received. When you clear the flag (enable the LED) you also call blink to re-start the LED blinking.// input class for LED control channel class input extends InputPort{ name = "LED control" type = "number" function set(value){ if(value == 0){ // 0 = Inhibit LED operation inhibit = 1; }else{ // 1 = Enable LED operation inhibit = 0; blink(); } } }
- Modify imp.configure. Remember earlier that there were two empty arrays? The first is an array of InputPort instances, and the second an array of OutputPort instances. Use these arrays to create as many communication channels as you require in each direction. In this case you need just the one input.
// Register with the server imp.configure("Blink-O-Matic", [input()], []);
- Save the code, run it and click on Planner
- If the LED does not stop blinking unplug and replug
- Click Add Node and add a tick tock.
- Connect the nodes
Analog Input
You might want to create an application that has the imp to read an analog voltage on one of its pins. Each pin can be configured as an ADC (Analog to Digital Converter), which outputs a 16-bit value. The imp can read a voltage between 0 V (which will produce an output of 0) and the imp's supply voltage (usually 3.3V, occasionally slightly lower; this will produce an output of 65,535).
If you need to determine the supply voltage of the imp, it can be done in firmware:
local supplyVoltage = hardware.voltage()
- Wire up a potentiometer so that the right most lead connects to 3V, the leftmost wire connects to GND and the center lead connects to a ADC pin, lets say 7.
- Create a new program and name it.
- The first line should be a comment with the name of the program
- Next, you'll need to configure the 7 pin as an analog input :
hardware.pin7.configure(ANALOG_IN);
- Create a function called checkPot that will do three things:
-
Read the voltage on the pin and save it to a variable:
local rawValue = hardware.pin7.read();
-
Format the result (in this case, scale the number so that the output is a float between 0 and 1)
// check if this value is "different enough" from the last one so you don't send the same value over and over if (math.abs(rawValue - lastRawValue) > 150) { // divide by 65535.0 to get a value between 0.0 and 1.0 local potValue = rawValue / 65535.0; lastRawValue = rawValue; server.show(potValue); }
- Schedule the function to run again
imp.wakeup(0.01, checkPot);
-
Read the voltage on the pin and save it to a variable:
- Register with the imp service and call your function for the first time
imp.configure("Potentiometer", [], []); checkPot();
- Save, run replug in your device. Click on Planner tab
- Click the settings button to change your firmware.
PWM
Microcontrollers cannot produce varrying voltages. They produce a high (In the case of the imp 3.3V) and a low (0V). But you can "fake" analog output by using pulsewidth modulation. Just like animation creates the illusion of movement, pulsewidth modulation, manipulates the intervals bewtween on (the pulsewidth) and off in such a way that it fools the eye and appears to be between on and off.
PWM signals are often used to control servo motors, other devices, and the brightness of LEDs.
A pulse-width modulated signal sends pulses of constant frequency, but varies the duty cycle (ratio of time on to time off). By driving an LED with this signal, it is possible to take advantage of persistence of vision (POV) to make the LED appear brighter or dimmer.
The imp is capable of producing a PWM signal on any of the user-accessible pins. For this example, the imp will output a PWM signal on pin 9. The LED will be powered by the 3.3V supply from the April board, and the imp will sink current from the LED on pin 9. When the voltage on pin 9 is high, the LED will be off; when the voltage is low, the LED will be on.
LED forward voltage is approximately 1.8V
Supply voltage is 3.3V
V = IR
I = 4 mA (0.004 A)
The voltage applied to the LED is therefore 3.3V - 1.8V = 1.5V
R = V/I = 1.5V / 0.004 A = 375Ω
the program
- Configure pin 9 as a PWM output
- Read in a value - this example will use an Input Port to get a value from the planner.
- Set the duty cycle of the PWM output based on the value received
- Open the Code tab, create a new program, name it and comment out a line that names the program
- To configure pin 9 as a PWM output, there are three arguments that must be dealt with:
- Mode: PWM_OUT
- Period in seconds (1/frequency)
- Duty cycle (a floating point number between 0 and 1, where 1 is 100% duty cycle)
Note that a %100 duty cycle turns the LED completely off; pin 9 will stay high and no current will flow through the LED.
hardware.pin9.configure(PWM_OUT, 1.0/500.0, 1.0);
- Take in a value from the planner by creating an Input Port. This is done by extending the InputPort base class. The set method of this new class will receive input sent to an object of this class by default; implement the set method to take in a value and perform an action.
class ledBrightness extends InputPort{ name = "LED Brightness" type = "number" function set(value) { // write a floating point number between 0.0 and 1.0, where 1.0 = 100% duty cycle server.log(value); //LED's brightness is inversely related to the duty cycle hardware.pin9.write(1.0-value); } }
Setting the duty cycle of the PWM signal is extremely straightforward. When a pin is configured as a PWM output, calling write with a floating point number between 0 and 1 sets the duty cycle of the output signal appropriately.
The set method in the InputPort will be called any time new data arrives on the input port, so there is no need to implement a loop or schedule updates within this firmware. - Outside the new input port class definition, register with the imp service. Make sure the input port is included in the array of input ports (the second argument) in imp.configure.
imp.configure("Brightness Controller", [ledBrightness], []);
- Save it, run it, navigate to Planner
- Under firmware, select the program. After a second or two, you will see the node update and the label will change to “ Brightness Controller”. Note that the LED will be off when initialized and will remain off until a value is sent to the imp.
- Add a Tick Tock node and connect it to your program
Servo Motors
Servo motors are motors whose position can be set via a pulse-width modulated signal
The imp can output a PWM signal on any pin, and you can change the duty cycle of that signal, making it easy to control servos.
For this example, the imp will control the position of a single servo motor. The imp will take in a floating-point value between 0 and 1 via an Input Port, then use that value to set the position of the servo motor.
In this application, the power supply is slightly more important; most generic servos operate at supply voltages between 4.5V and 6V, so 2 AA batteries will be insufficient.
- Create a new program, name it and include a comment that names the program in the first line.
- Configure the pin as a PWM output.
- Mode: PWM_OUT
- Period in seconds (1/frequency)
- Duty cycle (a floating point number between 0 and 1, where 1 is 100% duty cycle)
Configure pin 9 as a PWM output. Note that the duty cycle is initialized to 8.5% (1.7 ms) to set the motor position to the center.hardware.pin1.configure(PWM_OUT, 0.02, 0.085);
- Take in a value from the planner by creating an Input Port. Extend the InputPort base class. The set method of this new class will receive input sent to an object of this class by default; implement the set method to take in a value and perform an action.
class servo extends InputPort{ name = "servo position" type = "number" function set(value) { server.log(value); // scale the duty cycle we set on the pin so that 0 = fully counter clockwise and 1 = fully clockwise hardware.pin1.write(0.04 + (value * 0.09)); } }
- When a pin is configured as a PWM output, calling write with a floating point number between 0 and 1 sets the duty cycle of the output signal appropriately.
The set method in the InputPort will be called any time new data arrives on the input port, so there is no need to implement a loop or schedule updates within this firmware. - Register with the imp service. Note that the input port is included in the array of input ports (the second argument) in imp.configure.
imp.configure("Servo Controller", [servo], []);
- Save, go to planner, set up firmware, connect a Tick Tock Node.
- Use a second imp with a potentiometer to control the servo.
Creating Input Ports
Creating Input Ports You can create as many input ports on your imp as you like. Input ports can be all different types, with different actions to take when new data is received, or many instances of one or several classes of input port that you might define.
To create an input port, write a class definition that extends the InputPort base class. You'll need three things to make a functional input port:
- A type member, which will be displayed on the planner when you're making connections. This should describe the sort of data you want to take in with this input port (float, integer, degrees, command, etc.)
- A name member, which will be shown as the name of the input port on the planner when you're making connections. For example, servo position, message, or LED brightness.
- An implementation of the set function, which will be called when new data is sent to your input port. The set function describes what to do with this new data.
class Servo extends InputPort{ type = "float" name = "position" function set(value) { hardware.pin1.write(0.04 + (value * 0.09)); } }
class Servo extends InputPort{ type = "float" pin = null // define a constructor so that you can construct seperate instances for servos 1 and 2 constructor(name, pin) { // call through to the base (InputPort) constructor with the provided name base.constructor(name) this.pin = pin // here you might like to configure the pin, if you didn't already do that at global scope } function set(value) { this.pin.write(0.04 + (value * 0.09)); } }
With the constructor included in the definition, input port objects could be constructed right at imp.configure:
imp.configure("April Dual Servo Controller", [Servo("Pan", hardware.pin1), Servo("Tilt", hardware.pin2)], []);
Creating Output Ports
Just like with input ports, you can create as many output ports on your imp as you like. Output port construction is done in a single line of code, with a single argument, and actually emitting data is done with the output port's set method.To create an output port, simply set a local variable to the value returned from the OutputPort constructor. The constructor takes one argument: the name of your new output port.
local my_output = OutputPort("potentiometer");
my_output.set(value);