Are You Okay? Widget
Contributors:
elecia
Loading the Code
As with any good Imp program, there are two halves to this code: the device (the Imp itself) and the agent (a server-side process that handles Internet requests and responses). Create a new model in the Imp IDE, and copy/paste the code below into the proper half of the IDE.
Then check out the next couple of pages, where we dissect each section of the code. You'll need to change some constants to make the code work for you.
Note: You can grab the most up-to-date agent and device codes here on GitHub.
Device Code
language:JavaScript
// Are you ok? widget for monitoring loved ones
// It is ok to use, reuse, and modify this code for personal or commercial projects.
// License: Beerware.
// If you do, consider adding a note in the comments giving a reference to
// this project and/or buying me a beer some day.
/**************************** 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
/**************************** 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 *******************************************/
// Variable to represent LED state
class LEDColor
{
redPin = null
greenPin = null
bluePin = null
goalLED = [0xFF, 0xFF, 0xFF]; // power on goal is white
currentLED = [0, 0, 0];
inLEDRamp = false; // prevents multiple LED flares
constructor(redPin, greenPin, bluePin) {
// PWM frequency in Hz
local pwm_f = 500.0;
redPin.configure(PWM_OUT, 1.0/pwm_f, 0.0);
greenPin.configure(PWM_OUT, 1.0/pwm_f, 0.0);
bluePin.configure(PWM_OUT, 1.0/pwm_f, 0.0);
this.redPin = redPin
this.greenPin = greenPin
this.bluePin = bluePin
this.off();
}
function update() {
local div = (1.0/255.0);
this.redPin.write( currentLED[0] * div);
this.greenPin.write( currentLED[1] * div);
this.bluePin.write( currentLED[2] * div);
}
function off() {
this.redPin.write(0);
this.greenPin.write(0);
this.bluePin.write(0);
}
function setGoalColor (red, green, blue)
{
if (inLEDRamp) {
// not updating if we are already doing something
} else {
goalLED[0] = red;
goalLED[1] = green;
goalLED[2] = blue;
ledRamp();
inLEDRamp = true;
}
}
}
local rgbLed = LEDColor(redHWPin, greenHWPin, blueHWPin);
// this function looks at the difference between the goal LED
// and the actual LED and finds a way to smoothly transition
function ledRamp()
{
local difference = [0, 0, 0];
local totalDifference = 0;
local i;
for (i = 0; i < 3; i++) {
difference[i] = rgbLed.goalLED[i] - rgbLed.currentLED[i];
if (0 < difference[i] && difference[i] < LED_RAMP_STEPS) {
difference[i] = LED_RAMP_STEPS; // will be 1 after divide
} else if (0 > difference[i] && -difference[i] < LED_RAMP_STEPS) {
difference[i] = -LED_RAMP_STEPS; // will be -1
}
rgbLed.currentLED[i] += (difference[i] / LED_RAMP_STEPS);
totalDifference += difference[i];
}
if (-3 < totalDifference && totalDifference < 3) {
local goal = 0;
for (i = 0; i < 3; i++) {
goal += rgbLed.goalLED[i];
rgbLed.currentLED[i] = rgbLed.goalLED[i];
rgbLed.goalLED[i] = 0;
}
if (goal == 0) {
// finished
rgbLed.inLEDRamp = false;
rgbLed.off();
GetReadyToSleep();
} else {
rgbLed.update();
imp.wakeup(LED_HOLD_TIME, ledRamp); // it will start ramping down
}
} else {
rgbLed.update();
imp.wakeup(LED_RAMP_STEP_TIME, ledRamp);
}
}
/************************ Battery monitoring ***************************************/
// This project originally used a rechargeable battery with a MAX17043 LiPo fuel
// gauge to determine the state of charge (SOC). However, since the Impee is sleeping
// so much, we might get a reasonable battery life out of 4AAs. To get back to
// rechargeable, replace this code with that found in rechargeable_device.
function FuelGaugeResetFromBoot()
{
// do nothing
}
function FuelGaugeReadSoC()
{
local voltage = hardware.voltage();
local normalizedVoltgage = (voltage - MIN_EXPECTED_VOLTAGE) / (MAX_EXPECTED_VOLTAGE - MIN_EXPECTED_VOLTAGE);
if (normalizedVoltgage < 0) normalizedVoltgage = 0
local percent = math.floor(100 * normalizedVoltgage);
return percent;
}
/************************ Accelerometer ***************************************/
// Many thanks to https://gist.github.com/duppypro/7225636
// I mooched much of that code for the MMA8452Q accelerometer, though I made some
// changes for efficiency
const ACCEL_ADDR = 0x3A // 0x1D << 1
// Note: if your accelerometer has the SAO line pulled down
// (the resistor on the Sparkfun board), change the address to
/// const ACCEL_ADDR = 0x38 // 0x1C << 1
// MMA8452 register addresses and bitmasks
const STATUS = 0x00
const OUT_X_MSB = 0x01
const WHO_AM_I = 0x0D
const I_AM_MMA8452Q = 0x2A // read addr WHO_AM_I, expect I_AM_MMA8452Q
const INT_SOURCE = 0x0C
const SRC_ASLP_BIT = 0x80
const SRC_TRANSIENT_BIT = 0x20
const SRC_ORIENTATION_BIT = 0x10
const SRC_PULSE_BIT = 0x08
const SRC_FF_MT_BIT = 0x04
const SRC_DRDY_BIT = 0x01
const TRANSIENT_CFG = 0x1D
const TRANSIENT_SRC = 0x1E
const TRANSIENT_THRESHOLD = 0x1F
const TRANSIENT_COUNT = 0x20
const PULSE_CFG = 0x21
const PULSE_SRC = 0x22
const PULSE_THSX = 0x23
const PULSE_THSY = 0x24
const PULSE_THSZ = 0x25
const PULSE_TMLT = 0x26
const PULSE_LTCY = 0x27
const PULSE_WIND = 0x28
const CTRL_REG1 = 0x2A
const GOAL_DATA_RATE = 0x20 // 100 Hz
const CLEAR_DATA_RATE =0xC7
const LNOISE_BIT = 0x4
const F_READ_BIT = 0x2
const ACTIVE_BIT = 0x1
const CTRL_REG2 = 0x2B
const ST_BIT = 0x7
const RST_BIT = 0x6
const SLEEP_OVERSAMPLE_CLEAR = 0xE7
const SLEEP_OVERSAMPLE_SET = 0x18 // 11 = low power
const AUTOSLEEP_BIT = 0x4
const NORMAL_OVERSAMPLE_CLEAR = 0xFC
const NORMAL_OVERSAMPLE_SET = 0x03 // 11 = low power
const CTRL_REG3 = 0x2C
const WAKE_TRANSIENT_BIT = 0x40
const WAKE_ORIENTATION_BIT = 0x20
const WAKE_PULSE_BIT = 0x10
const WAKE_FREEFALL_BIT = 0x08
const IPOL_BIT = 0x02
const CTRL_REG4 = 0x2D
const INT_EN_ASLP_BIT = 0x80
const INT_EN_TRANSIENT_BIT = 0x20
const INT_EN_ORIENTATION_BIT = 0x10
const INT_EN_PULSE_BIT = 0x08
const INT_EN_FREEFALL_MT_BIT = 0x04
const INT_EN_DRDY_BIT = 0x01
const CTRL_REG5 = 0x2E
// Writes a single byte (dataToWrite) into addressToWrite. Returns error code from i2c.write
// Continue retry until success. Caller does not need to check error code
function writeReg(addressToWrite, dataToWrite) {
local err = null
while (err == null) {
err = i2c.write(ACCEL_ADDR, format("%c%c", addressToWrite, dataToWrite))
// server.log(format("i2c.write addr=0x%02x data=0x%02x", addressToWrite, dataToWrite))
if (err == null) {
server.error("i2c.write of value " + format("0x%02x", dataToWrite) + " to " + format("0x%02x", addressToWrite) + " failed.")
}
}
return err
}
// Read numBytes sequentially, starting at addressToRead
// Continue retry until success. Caller does not need to check error code
function readSequentialRegs(addressToRead, numBytes) {
local data = null
while (data == null) {
data = i2c.read(ACCEL_ADDR, format("%c", addressToRead), numBytes)
if (data == null) {
server.error("i2c.read from " + format("0x%02x", addressToRead) + " of " + numBytes + " byte" + ((numBytes > 1) ? "s" : "") + " failed.")
}
}
return data
}
function readReg(addressToRead) {
return readSequentialRegs(addressToRead, 1)[0]
}
function AccelerometerSetActive(mode) {
// Sets the MMA8452Q active mode.
// 0 == STANDBY for changing registers
// 1 == ACTIVE for outputting data
if (mode) {
writeReg(CTRL_REG1, readReg(CTRL_REG1) | ACTIVE_BIT)
} else {
writeReg(CTRL_REG1, readReg(CTRL_REG1) & ~ACTIVE_BIT)
}
}
// Reset the accelerometer
function AccelerometerResetFromBoot() {
local reg = null;
server.log("Looking for accelerometer...")
do {
reg = readReg(WHO_AM_I) // Read WHO_AM_I register
if (reg == I_AM_MMA8452Q) {
server.log("Found MMA8452Q. Sending RST command...")
break
} else {
server.error("Could not connect to MMA8452Q: WHO_AM_I reg == " + format("0x%02x", reg))
imp.sleep(i2cRetryPeriod)
}
} while (true)
// send reset command
writeReg(CTRL_REG2, readReg(CTRL_REG2) | RST_BIT)
do {
reg = readReg(WHO_AM_I) // Read WHO_AM_I register
if (reg == I_AM_MMA8452Q) {
server.log("Accelerometer found.")
break
} else {
server.error("Could not connect to MMA8452Q: WHO_AM_I reg == " + format("0x%02x", reg))
imp.sleep(i2cRetryPeriod)
}
} while (true)
AccelerometerSetActive(false);
writeReg(CTRL_REG1, 0x1A); // 100 Hz ODR + fast read + low noise
// Set up accel for transient detection, see
// http://cache.freescale.com/files/sensors/doc/app_note/AN4071.pdf
writeReg(TRANSIENT_CFG, 0x1E); // Enable X Y Z Axes and enable the latch
writeReg(TRANSIENT_THRESHOLD, ACCEL_TRANSIENT_THRESHOLD);
writeReg(TRANSIENT_COUNT, 0x05); // 50ms
reg = readReg(TRANSIENT_SRC) // this clears the register
// Set up accel for single tap pulse detection, see
// http://cache.freescale.com/files/sensors/doc/app_note/AN4072.pdf
writeReg(PULSE_CFG, 0x55); // Enable X Y Z Axes and enable the latch
writeReg(PULSE_THSX, ACCEL_TAP_THRESHOLD);
writeReg(PULSE_THSY, ACCEL_TAP_THRESHOLD);
writeReg(PULSE_THSZ, ACCEL_TAP_THRESHOLD);
writeReg(PULSE_TMLT, 0x03); // 30ms at 100Hz ODR
writeReg(PULSE_LTCY, 100); // 100ms at 100Hz ODR
reg = readReg(PULSE_SRC) // this clears the register
writeReg(CTRL_REG4, INT_EN_TRANSIENT_BIT | INT_EN_PULSE_BIT);
writeReg(CTRL_REG5, INT_EN_TRANSIENT_BIT | INT_EN_PULSE_BIT);
writeReg(CTRL_REG3, WAKE_TRANSIENT_BIT | WAKE_PULSE_BIT | IPOL_BIT); // move to int1
AccelerometerSetActive(true);
}
function readAccelData() {
local rawData = null // x/y/z accel register data stored here, 3 bytes
rawData = readSequentialRegs(OUT_X_MSB, 3) // Read the three raw data registers into data array
return rawData;
}
function AccelerometerIRQ() {
local reg = null
if (wakeupPin.read() == 1) { // only react to low to high edge
IndicateGoodInteraction();
reg = readReg(INT_SOURCE)
if (reg & SRC_TRANSIENT_BIT) {
reg = readReg(TRANSIENT_SRC) // this clears SRC_TRANSIENT_BIT
server.log(format("Transient src 0x%02x", reg))
agent.send("motionDetected", "soft gentle motion.");
}
if (reg & SRC_PULSE_BIT) {
reg = readReg(PULSE_SRC) // this clears SRC_PULSE_BIT
server.log(format("Pulse src 0x%02x", reg))
agent.send("motionDetected", "hard rapping.");
}
} else {
// server.log("INT LOW")
}
} // end AccelerometerIRQ
/************************ Device code ***************************************/
function GetReadyToSleep()
{
local sleepSeconds = 3600; // an hour
// this will effectively reset the system when it comes back on
server.sleepfor(sleepSeconds);
}
function CheckBatteryAndGoToSleep()
{
agent.send("batteryUpdate", FuelGaugeReadSoC());
server.log("going to sleep");
imp.onidle(GetReadyToSleep);
}
function IndicateGoodInteraction()
{
rgbLed.setGoalColor(255, 255, 255); // white
}
function IndicateLowBattery()
{
rgbLed.setGoalColor(200, 200, 0); // yellow
}
function IndicateNoWiFi()
{
rgbLed.setGoalColor(255, 0, 0); // red
}
function HandleReasonForWakeup(unused = null)
{
local reason = hardware.wakereason();
local stateOfCharge = FuelGaugeReadSoC();
local timeout = 30;
if (reason == WAKEREASON_TIMER) {
// quiet wakeup
server.log("Timer wakeup")
CheckBatteryAndGoToSleep();
} else {
if (!server.isconnected()) {
IndicateNoWiFi()
}
if (stateOfCharge < MIN_GOOD_STATE_OF_CHARGE)
{
server.log("Low battery " + stateOfCharge)
IndicateLowBattery();
}
if (reason == WAKEREASON_PIN1) {
server.log("PIN1 wakeup")
AccelerometerIRQ();
} else { // any other reason is a reset of sorts
server.log("Reboot")
AccelerometerResetFromBoot();
FuelGaugeResetFromBoot();
}
}
}
// things to do on every time based wake up
imp.setpowersave(true);
// on error: don't try to reconnect, throw an error so we can indicate a
// problem to the user
server.setsendtimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, 30);
// Configure interrupt for wakeup. Connect MMA8452Q INT1 pin to imp pin1.
wakeupPin.configure(DIGITAL_IN_WAKEUP, AccelerometerIRQ);
// figure out why we woke up
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();
}
Agent Code
language:JavaScript
// Are you ok? widget for monitoring loved ones
// license: Beerware.
// It is ok to use, reuse, and modify this code for personal or commercial projects.
// If you do, consider adding a note in the comments giving a reference to
// this project and/or buying me a beer some day.
// This agent monitors the device, making sure it communicates
// and gets moved by its user regularly. This will also send messages
// via twitter (email and Twilio texting is an exercise
// left to the next person).
/************************ Settings ***************************************/
// debug output frequency: these prevent twitter flurries where you
// get the same message 10 times because you are tapping the device
const dtDebugMessageMotionDetected = 80; // seconds
const dtDebugMessageBatteryUpdateDetected = 600; // seconds
// This is how long the device will go without an update from the
// user before it cries for help
// 43200 ==> 12 hours ==> three times a day
// 129600 ==> 36 hours ==> every day (not same time every day)
// 216000 ==> 60 hours ==> every couple days
const dtNoMotionDetected = 129600; // seconds
const dtNoBatteryUpdate = 21600; // seconds (21600 ==> 6 hours)
const dtEverythingFineUpdate = 432000; // seconds (432000 ==> 5 days)
const MIN_GOOD_STATE_OF_CHARGE = 25; // percent
// Twitter permissions for @ayok_status
// It is ok to use this as long as you update the monitoredDevices
// so it prints your mane.
// Also note, it is for debug: if abused, the permissions will
// change (and remember others can see these tweets!).
_CONSUMER_KEY <- "HxwLkDWJTHDZo5z3nENPA"
_CONSUMER_SECRET <- "HvlmFx9dkp7j4odOIdfyD9Oc7C5RyJpI7HhEzHed4G8"
_ACCESS_TOKEN <- "2416179944-INBz613eTjbzJN4q4iymufCcEsP5XJ6xW5Lr8Kp"
_ACCESS_SECRET <- "1YdwAiJViQY45oP8tljdX0PGPyeL8G3tQHKtO43neBYqH"
// Twilio set up for texting
// http://forums.electricimp.com/discussion/comment/4736
// more extensive code https://github.com/joel-wehr/electric_imp_security_system/blob/master/agent.nut
// Mailgun for emailing
// http://captain-slow.dk/2014/01/07/using-mailgun-with-electric-imp/
/************************ Handle setting the device's name ***************************************/
// You have to set up your unit the first time by putting in a URL:
// https://agent.electricimp.com/{agentUrl}/settings?name={nameValue}&attn={attnValue}
// Look at the top of the Imp editor for you agent URL, you'll see something like
// https://agent.electricimp.com/abce1235 <-- random string numbers and letters
// So you'll build up one that looks like
// https://agent.electricimp.com/abce1235/settings?name={Maxwell}&attn={@logicalelegance}
// Where Maxwell is the name of the unit and @logicalelegance is where I want messages to be sent.
// default settings
settings <- {
name = "Unknown", // name of the unit
attn = "" // who to send messages to
};
// Loads settings, if they exist
function loadSettings() {
// load data
local data = server.load();
// if there are settings
if ("settings" in data) {
settings.name = data.settings.name;
settings.attn = data.settings.attn;
}
}
// Load settings on agent start/restart
loadSettings();
// Saves the settings with server.save
function saveSettings(newName, newAttn) {
// load settings
local data = server.load();
// if settings isn't in the stored data
if (!("settings" in data)) {
// create settings table in data
data["settings"] <- { name = "", attn = "" };
}
// set new values
settings.name = newName;
settings.attn = newAttn;
// save values
data.settings.name = newName;
data.settings.attn = newAttn;
server.save(data);
}
function httpHandler(req, resp) {
// grab the path the request was made to
local path = req.path.tolower();
// if they made a request to /settings:
if (path == "/settings" || path == "/settings/") {
// grab query parameters we need
if ("name" in req.query && "attn" in req.query) {
// save them
saveSettings(req.query.name, req.query.attn);
// respond with the new settings
resp.send(200, http.jsonencode(settings));
return;
}
}
// if they didn't send settings pass back a 200, OK
resp.send(200, "OK");
}
// attach httpHandler to onrequest event
http.onrequest(httpHandler);
/************************ Twitter ***************************************/
// from: github.com/electricimp/reference/tree/master/webservices/twitter
helper <- {
function encode(str) {
return http.urlencode({ s = str }).slice(2);
}
}
class TwitterClient {
consumerKey = null;
consumerSecret = null;
accessToken = null;
accessSecret = null;
baseUrl = "https://api.twitter.com/";
constructor (_consumerKey, _consumerSecret, _accessToken, _accessSecret) {
this.consumerKey = _consumerKey;
this.consumerSecret = _consumerSecret;
this.accessToken = _accessToken;
this.accessSecret = _accessSecret;
}
function post_oauth1(postUrl, headers, post) {
local time = time();
local nonce = time;
local parm_string = http.urlencode({ oauth_consumer_key = consumerKey });
parm_string += "&" + http.urlencode({ oauth_nonce = nonce });
parm_string += "&" + http.urlencode({ oauth_signature_method = "HMAC-SHA1" });
parm_string += "&" + http.urlencode({ oauth_timestamp = time });
parm_string += "&" + http.urlencode({ oauth_token = accessToken });
parm_string += "&" + http.urlencode({ oauth_version = "1.0" });
parm_string += "&" + http.urlencode({ status = post });
local signature_string = "POST&" + helper.encode(postUrl) + "&" + helper.encode(parm_string)
local key = format("%s&%s", helper.encode(consumerSecret), helper.encode(accessSecret));
local sha1 = helper.encode(http.base64encode(http.hash.hmacsha1(signature_string, key)));
local auth_header = "oauth_consumer_key=\""+consumerKey+"\", ";
auth_header += "oauth_nonce=\""+nonce+"\", ";
auth_header += "oauth_signature=\""+sha1+"\", ";
auth_header += "oauth_signature_method=\""+"HMAC-SHA1"+"\", ";
auth_header += "oauth_timestamp=\""+time+"\", ";
auth_header += "oauth_token=\""+accessToken+"\", ";
auth_header += "oauth_version=\"1.0\"";
local headers = {
"Authorization": "OAuth " + auth_header,
};
local response = http.post(postUrl + "?status=" + helper.encode(post), headers, "").sendsync();
return response
}
function Tweet(_status) {
local postUrl = baseUrl + "1.1/statuses/update.json";
local headers = { };
local response = post_oauth1(postUrl, headers, _status)
if (response && response.statuscode != 200) {
twitterDebug("Error updating_status tweet. HTTP Status Code " + response.statuscode);
twitterDebug(response.body);
return null;
} else {
twitterDebug("Tweet Successful!");
}
}
}
function twitterDebug(string)
{
// when debugging twitter, turn on the server logging
// server.log(string)
}
twitter <- TwitterClient(_CONSUMER_KEY, _CONSUMER_SECRET, _ACCESS_TOKEN, _ACCESS_SECRET);
/**************************** End twitter block *******************************************/
/**************************** Message block *******************************************/
// Returns a preformated DateTime string.
// Helper function for debugMessage
function GetDateTimeStr(timestamp) {
local d = date(timestamp, 'u'); // UTC time
local day = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];
return format("%s %02d:%02d:%02d", day[d.wday], d.hour, d.min, d.sec)
}
// These are the messages you use when bringing up the device,
// for checking that the battery is draining slowly and
// testing taps. These don't use the attn string so on
// Twitter they are relatively quiet
function debugMessage(string)
{
local message = settings.name + ": " + string;
twitter.Tweet(message);
server.log(message)
}
// These are the important messages:
// 1) No user motion
// 2) Batteries are low
// 3) Intermittent, everything is fine
function caregiverMessage(string)
{
local message = settings.name + ": " + string;
twitter.Tweet(attn + " " message);
server.log("!!!!" + message);
}
/**************************** Device handling *******************************************/
local lastTimeMotionDetected = 0;
local lastTimeBatteryUpdate = 0;
local lastBatteryReading = 0;
local batteryUpdateFromDeviceTimer;
local motionUpdateFromDeviceTimer;
local everythingIsFineDeviceTimer;
// This creates a debug string if motion is sent from the device
// More importantly, it resets the timer so we don't send an "I'm lonely" message
function motionOnDevice(type)
{
local thisCheckInTime = time();
if ((lastTimeMotionDetected != 0) &&
((thisCheckInTime - lastTimeMotionDetected) > dtDebugMessageMotionDetected)) {
local datestr = GetDateTimeString(thisCheckInTime);
local sendStr = datestr + " I felt movement. It was a " + type;
debugMessage(sendStr);
}
lastTimeMotionDetected = thisCheckInTime;
imp.cancelwakeup(motionUpdateFromDeviceTimer);
motionUpdateFromDeviceTimer = imp.wakeup(dtNoMotionDetected, noMotionFromDevice);
}
function noMotionFromDevice()
{
local stringOptions = [
"No one has played with me since ",
"I need to be pet but haven't been since ",
"The last time someone filled my cuddle tank was ",
"It's been eons since my last hug: ",
"I'm so lonely, no one has paid attention to me for so long: ",
"I'm hungry, hungry for hugs! Last feeding was "
];
if (lastTimeMotionDetected) {
local datestr = GetDateTimeString(lastTimeMotionDetected);
local choice = math.rand() % stringOptions.len();
local sendStr = stringOptions[choice] + datestr;
caregiverMessage(sendStr)
} else {
sendStr = "No movement since device turned on!"
caregiverMessage(sendStr)
}
motionUpdateFromDeviceTimer = imp.wakeup(dtNoMotionDetected, noMotionFromDevice);
eveverythingNotFine();
}
function noBatteryUpdateFromDevice()
{
local sendStr;
if (lastTimeBatteryUpdate) {
local stringOptions = [
"Device did not check in, last check in at ",
];
local datestr = GetDateTimeStr(lastTimeBatterUpdate);
local choice = math.rand() % stringOptions.len();
sendStr = stringOptions[choice] + datestr +
" battery then: " + lastBatteryReading +
", minutes " + (time() - lastTimeBatteryUpdate)/60;
} else {
sendStr = "Device has not checked in since server restart."
}
caregiverMessage(sendStr)
batteryUpdateFromDeviceTimer = imp.wakeup(dtNoBatteryUpdate, noBatteryUpdateFromDevice);
eveverythingNotFine();
}
function eveverythingNotFine()
{
// everything is not fine, reset counter to happy message
imp.cancelwakeup(everythingIsFineDeviceTimer);
everythingIsFineDeviceTimer = imp.wakeup(dtEverythingFineUpdate, everythingFineUpdate);
}
function everythingFineUpdate()
{
local sendStr;
if (lastBatteryReading > MIN_GOOD_STATE_OF_CHARGE) {
local stringOptions = [
"Nothing to be concerned about, everything is going really well! Battery at %d %%",
];
local choice = math.rand() % stringOptions.len();
sendStr = stringOptions[choice];
} else {
local stringOptions = [
"Things are going fine but my batteries are getting low: %d %%",
];
local choice = math.rand() % stringOptions.len();
sendStr = stringOptions[choice];
}
caregiverMessage(format(sendStr, lastBatteryReading));
everythingIsFineDeviceTimer = imp.wakeup(dtEverythingFineUpdate, everythingFineUpdate);
}
function batteryUpdateFromDevice(percentFull)
{
local thisCheckInTime = time();
if ((thisCheckInTime - lastTimeBatteryUpdate) > dtDebugMessageBatteryUpdateDetected) {
local datestr = GetDateTimeStr(thisCheckInTime);
local sendStr = datestr + " battery update: " + percentFull ;
debugMessage(sendStr)
}
// update the device timer
imp.cancelwakeup(batteryUpdateFromDeviceTimer);
batteryUpdateFromDeviceTimer = imp.wakeup(dtNoBatteryUpdate, noBatteryUpdateFromDevice);
lastTimeBatteryUpdate = thisCheckInTime;
lastBatteryReading = percentFull;
}
// register the device actions. It will wake up with the accelerometer says
// to (motion). It will also wake up on a timer to read the battery.
device.on("motionDetected", motionOnDevice);
device.on("batteryUpdate", batteryUpdateFromDevice);
// This timer is to complain if we haven't heard anything from the device.
// We should be getting ~ hourly battery updates. If we miss more than one
// or two, then the device is having trouble with communication (or its
// batteries are dead). We need to fuss because the regular monitoring is
// therefore also offline.
batteryUpdateFromDeviceTimer = imp.wakeup(dtNoBatteryUpdate, noBatteryUpdateFromDevice);
// This is the critical timer, if the device does not sense motion in this
// time it will fuss
motionUpdateFromDeviceTimer = imp.wakeup(dtNoMotionDetected, noMotionFromDevice);
// Everyone needs to know things are ok. So every few days, we'll send an
// all clear to indicate everything is functioning normally.
everythingIsFineDeviceTimer = imp.wakeup(dtEverythingFineUpdate, everythingFineUpdate);