Are You Okay? Widget
What The Software Does (Device)
If you havenât used the Electric Imp, have a look at the Electric Imp Hookup Guide. You can use the Hello, World code with this system set up as described. It will blink red if you change all instances of hardware.pin1
to hardware.pin2
in the device code.
However, once your have your Electric Imp talking to your network and downloading code, we can do a lot more. Our device is going to be a bit more complex, as it:
- Controls an RGB LED via PWM to do dimming
- Configures the accelerometer to interrupt when movement exceeds a threshold
- Goes to deep sleep to conserve battery
- On regular wake-ups, reads the battery voltage
Constants
At the top of the file are parameters for you to change.
/**************************** User parameters ***************************************/ // Imp's hardware.voltage reads the output of the regulator so we don't see the // whole range of the batteries, do the best with the info available const MAX_EXPECTED_VOLTAGE = 3.3; // max readable const MIN_EXPECTED_VOLTAGE = 2.6; // dying const MIN_GOOD_STATE_OF_CHARGE = 25; // percent // when there is movement, how much movement does there have to be // to get the accelerometer to wake up the device const ACCEL_TAP_THRESHOLD = 10; // experimentally derived threshold const ACCEL_TRANSIENT_THRESHOLD = 0x02; // experimentally derived threshold // the LED ramps up to a color, holds for a bit, then ramps down const LED_HOLD_TIME = 5.0; // seconds const LED_RAMP_STEP_TIME = 0.05; // seconds per ramp step (0.05 = 200Mhz) const LED_RAMP_STEPS = 20; // steps in ramp at timing above
Setup
First, set up the hardware to match the hookup instructions.
/**************************** Hardware *******************************************/ /* Pin Assignments according to silkscreen * Pin 1 = Input: wakeup interrupt from accelerometer * Pin 2 = PWM Red * Pin 5 = PWM Blue * Pin 7 = PWM Green * Pin 8 = I2C SCL (yellow wire for me) * Pin 9 = I2C SDA (green wire for me) */ wakeupPin <- hardware.pin1; redHWPin <- hardware.pin2; greenHWPin <- hardware.pin5; blueHWPin <- hardware.pin7; i2c <- hardware.i2c89; i2c.configure(CLOCK_SPEED_400_KHZ);
LED Ramping
The ramp up and down, is done via the class LEDColor
. Its functions are:
constructor(redPin, greenPin, bluePin)
-- initial creation, this is called with:local rgbLed = LEDColor(redHWPin, greenHWPin, blueHWPin);
.- function
setGoalColor (red, green, blue)
-- sets the state variablegoalLED
to the values passed in. - function
update()
-- writes to the LED with the values incurrentLED
. - function
off()
-- turns the LED off.
This is used in conjunction with ledRamp
function to make the LED come up softly, hold for a few seconds, then dim softly. The parameters can be reconfigured, if you want a different profile.
Battery Monitoring
The battery monitoring subsystem is straightforward for AA batteries: read the Impâs voltage and compare it against expected.
FuelGaugeResetFromBoot
-- no initialization is needed for this monitoring.FuelGaugeReadSoC
-- does a bit of math to move from reading to percentage. It depends on the constantsMIN_EXPECTED_VOLTAGE
andMAX_EXPECTED_VOLTAGE
to be set at the top of the file.
In the Going Further section, there is the option of using rechargeable LiPo batteries and monitoring them with a Fuel Gauge board. In that instance, these functions are replaced with more complex ones that speak to another chip via I2C.
Accelerometer
Adding an MMA8452Q to an Electric Imp is pretty common, so I used the code available from duppypro, making minor modification changes to the configuration (and some of the code).
The stock Sparkfun MMA8452Q has the address line pulled HIGH, so the I2C address is:
const ACCEL_ADDR = 0x3A // 0x1D << 1
If your accelerometer has the SAO line pulled LOW (the resistor in place on the back of on the Sparkfun board), change the address to
const ACCEL_ADDR = 0x38 // 0x1C << 1
The accelerometer has a few functions you probably wonât need to call directly (they are internal to the subsystem):
writeReg(addressToWrite, dataToWrite)
-- writes to accelâs address via I2C.readSequentialRegs(addressToRead, numBytes)
-- readsnumBytes
from accelâs address over I2C.readReg(addressToRead)
-- calls the above function but for one byte at a time.AccelerometerSetActive(mode)
-- sets the accelerometer into register modification mode or normal (active) mode.
The functions you may want to look at further are:
AccelerometerResetFromBoot()
â verifies accelâs existence and configures interrupts and thresholds for the systemAccelerometerIRQ()
â called after the accelerometer creates an interrupt.
There in one unused function, there for debugging and amusement:
readAccelData()
â get the data from accelerometer.
If you havenât played with an accelerometer before, they can be fun. Change IndicateGoodInteraction()
to something like this:
// Get values that are absolute value, 0 to 255, and proportional to 1G local data = [0,0,0]; local rawData = readAccelData() foreach (i, val in rawData) { val = (val < 128 ? val : val - 256); // set val range -128 to 128 val = (val < 0 ? -val : val); // positive only val = val * 2; // brighter light data [i] = (val > 256 ? 255 : val); } setGoalColor (data[0], data[1], data[2])
Now, the LED will indicate the orientation of the accelerometer every time you move or tap it.
AYOK Code
While the code thus far has been for different subsystems, this is the new code for making the Are-You-OK widget do its thing.
GetReadyToSleep()
-- sends the Imp to deep sleep. When it restarts, it will resume from the top of the program (so it is different than theledRamp
âsimp.wakeup
).CheckBatteryAndGoToSleep()
-- every hour the Imp wakes up to check battery status and send a note to the server.IndicateGoodInteraction()
-- sets the LED to white and starts a ramp.IndicateLowBattery()
-- sets LED to yellow, starts a ramp.IndicateNoWiFi()
-- sets LED to red, starts a ramp.HandleReasonForWakeup(unused = null)
-- the state machine of the device, it looks at the reason for wakeup and acts appropriately.
When the Imp comes back from deep sleep, it runs this code:
imp.setpowersave(true);
Before we get very far into the code, we want the Imp to send this code an error if it has trouble connecting:
server.setsendtimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, 30);
The important part there is the RETURN_ON_ERROR
portion. Without this line, the Imp will try to connect but wonât let the device code run. Weâll turn the LED to red if we canât get connected.
Next, we need to make sure the accelerometer will wake the system up after it goes to sleep:
// Configure pin1 for wakeup. Connect MMA8452Q INT1 pin to imp pin1. wakeupPin.configure(DIGITAL_IN_WAKEUP, AccelerometerIRQ);
Note that since the Imp is usually in deep sleep, the AccelerometerIRQ
seldom gets called directld. Instead HandleReasonForWakeup
notes that wakeup reason is WAKEREASON_PIN1
. If the system is not, then this code is responsible for calling HandleReasonForWakeup
. It starts by verifying the system is connected to WiFi. If not, then it tries for 3s before calling HandleReasonForWakeup
. (If the imp is connected, then it immediately calls HandleReasonForWakeup
.)
if (!server.isconnected()) { // we probably can't get to the internet, try for // a little while (3 seconds), then get pushed to // HandleReasonForWakeup where IndicateNoWiFi will be called server.connect(HandleReasonForWakeup, 3) } else { HandleReasonForWakeup(); }
Much of the complexity here is due to the goal of having low power usage -- ideally to make the system last for 6 months (or more) on four AAs.