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.
language:JavaScript
/**************************** 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.
language:JavaScript
/**************************** 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:
language:JavaScript
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
language:JavaScript
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:
language:JavaScript
// 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:
language:JavaScript
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:
language:JavaScript
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:
language:JavaScript
// 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
.)
language:JavaScript
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.