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
// 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
// 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);