AB
Created February 21, 2018

Mailbox Assistant

Waiting on an important letter? Arriving home for the day? Ask the Mailbox Assistant to check your mail to avoid unnecessary trips.

AdvancedShowcase (no instructions)6 hours370
Mailbox Assistant

Things used in this project

Story

Read more

Schematics

Sketch

Attempt at the schematic

Code

app.js

JavaScript
Javascript (Node) to run on the raspberry pi. Update the config file with your custom values
"use strict";

var noble = require('noble');
var request = require('request');
var bunyan = require('bunyan');
var mqtt = require('mqtt');
var url = require('url');
var config = require('./config');

var mailboxServiceUUID = config.mailboxServiceUUID;
var mailboxCharUUID = config.mailboxCharUUID;

var log = bunyan.createLogger({
  name: "MailBoxAssistant",
  streams: [
    {
      level: 'debug',
      stream: process.stdout//logINFOandabovetostdout
    },
    {
      level: 'debug',
      type: 'rotating-file',
      path: '/var/tmp/mba.log',//logERRORandabovetoafile
      period: '1d',//dailyrotation
      count: 2//keep2backcopies
    }
  ]
});

// Parse
var mqtt_url = url.parse("mqtt://vtkglnor:Uxc-XkB7BN2o@m11.cloudmqtt.com:13296");
var auth = (mqtt_url.auth || ':').split(':');
var options = { "username": auth[0], "password": auth[1] };

noble.stopScanning();
noble.on('stateChange', function (state) {
  if (state === 'poweredOn') {
    log.info('scanning...');
    noble.startScanning([mailboxServiceUUID], false);
  }
  else {
    noble.stopScanning();
  }
});

var mailboxChar = null;
var result = null;
var message = null;

noble.on('discover', function (mailbox) {
  // we found the mailbox, stop scanning
  noble.stopScanning();

  log.info('Found ', mailbox.advertisement.localName, '!');

  // Once the mailbox has been discovered, then connect to it.
  mailbox.connect(function (err) {

    if (err) {
      log.error("Error connecting: ", err);
    }

    // Once the mailbox has been connected, then discover the
    // services and characteristics of interest.
    //
    mailbox.discoverServices([mailboxServiceUUID], function (err, services) {

      if (err) {
        log.error("Error discovering services: ", err);
      }

      services.forEach(function (service) {
        // This must be the service we were looking for.
        log.info('Found service:', service.uuid);


        // So, discover its characteristics.
        service.discoverCharacteristics([], function (err, characteristics) {
          if (err) {
            log.info("Error finding characteristics. ", err);
            mailbox.disconnect(function (err) {
              if (err) {
                log.error("Couldn't disconnect");
              }
            });
          }

          characteristics.forEach(function (characteristic) {
            //
            // Loop through each characteristic and match them to the
            // UUIDs that we know about.
            //
            log.info('Found characteristic:', characteristic.uuid);
            if (mailboxCharUUID == characteristic.uuid) {
              mailboxChar = characteristic;
              checkMailbox();
            }

          })

          //
          // Check to see if we found all of our characteristics.
          //
        });
      });
    });
  });
});

function checkMailbox() {

  mailboxChar.on('data', function (data, isNotification) {
    result = data.readUInt8(0);
    log.debug("NotificationResult = ", result);

    if (result) {
      message = "You got mail";
    }
    else {
      message = "No mail detected";
    }
    connectAndPublish();
  });

  mailboxChar.subscribe(function (err) {
    if (err) {
      log.error("Subscribe Error");
    }
  })

}

function connectAndPublish() {
  // Create a client connection
  var client = mqtt.connect(mqtt_url, options);

  client.on('error', function (err) {
    log.warn("Error on MQTT Connect " + err)
    client.end();

  });

  client.on('connect', function () {
    log.debug("Connected to mqtt");

    client.publish('device/' + config.email, message, function () {
      log.info("Message is published to device/" + config.email);

    });
    
//Test User for Alexa Skill certification
    let date = new Date();
    let seconds = date.getSeconds();
    if (seconds % 2 === 0) {
      client.publish('device/' + config.testEmail, "No mail detected", function () {
        log.info("Test User No Mail detected");
      });
    }
    else {
      client.publish('device/' + config.testEmail, "You got mail", function () {
        log.info("Test User Mail detected");
      });
    }
    client.end(); // Close the connection when published
  });

  client.on('close', function () {
    log.warn('Disconnected from MQTT');

    noble.once('disconnect', function () {
      log.warn('Disconnected from Mailbox');
    });
    setInterval(function () {
      process.exit(1);
    }, 5000); // 5 seconds

  });

}

mailbox.ino

Arduino
Arduino code to check the state of the mailbox and communicate over BLE.
#include <CurieBLE.h>
//#include <SoftwareSerial.h>

#define ECHOPIN 12
#define TRIGGERPIN 13
//#define XBEERX 10
//#define XBEETX 11

//Interval in milliseconds
#define ONE_HOUR 1000 //3600000;
#define DEFAULT_DELAY 50

BLEPeripheral mailbox;
BLEService mailboxService("03dee8ee-d962-49ae-a85a-8ef54a632cfa");

BLEUnsignedCharCharacteristic mailboxChar("03dee8ee-d962-49ae-a85a-8ef54a632cfb", BLERead | BLEWrite | BLENotify);
//SoftwareSerial Xbee (XBEERX, XBEETX);

//used to store current time after a reading
unsigned long pastTime = 0;

//used to calculate the size of the mailbox
unsigned long defaultDistance;

//Used to set visual cues
int notificationLed;

//Pin number
const int pinNumber = 3;

//Sample size
const int sample_size = 11;

//Array to hold samples
unsigned long sample[sample_size];

unsigned long RangeInCentimeters(void)
{

  pinMode(TRIGGERPIN, OUTPUT);
  digitalWrite(TRIGGERPIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIGGERPIN, HIGH);
  delayMicroseconds(5);
  digitalWrite(TRIGGERPIN, LOW);
  pinMode(ECHOPIN, INPUT);

  unsigned long duration;
  duration = pulseIn(ECHOPIN, HIGH);

  //  Serial.print("duration = ");
  //  Serial.println(duration);

  unsigned long durationInCentimeters;
  durationInCentimeters = duration / 29 / 2;

  return durationInCentimeters;

}

void getSample()
{

  //used to store current time after a reading
  unsigned long pastTime = 0;

  //used to store current time
  unsigned long currentTime = 0;

  int i = 0;

  //  Serial.print("sample_size = ");
  //  Serial.println(sample_size);

  while (i < sample_size) {
    currentTime = millis();
    if (currentTime - pastTime >= DEFAULT_DELAY) {
      sample[i] = RangeInCentimeters();
      pastTime = currentTime;
      //      Serial.print("Reading = ");
      //      Serial.println(i);
      i++;
    }
  }

  //  Serial.print("start sort");
  sampleSort();
  //  Serial.print("end sort");
}

void sampleSort()
{
  // sort
  for (int i = 1; i < sample_size; ++i) {
    unsigned long j = sample[i];
    int k;

    for (k = i - 1; (k >= 0) && (j < sample[k]); k--) {
      sample[k + 1] = sample[k];
    }
    //    Serial.print("end k");
    sample[k + 1] = j;
  }
}

/*The median filter would take the last group of readings and first sort them and then pull out the center reading. http://forum.arduino.cc/index.php?topic=20920.0*/

unsigned long getSampleMedian()
{
  int index = sample_size / 2;
  return sample[index];
}

/*The simplest mode filter, is simple as this question, "Do the last two readings agree?" (with in a certain distance, and the certain distance depends upon how much variation one expects in the actual use.  Even so most use the logic of "are these readings exactly the same")?  If so, use the readings, otherwise throw away to oldest reading, and compare the next reading with the most current reading and do this again.  Obviously with very noisy data, one might have to sample a larger group of readings, and pull out the most common reading, but for most applications, this simple two reading filter works very well. http://forum.arduino.cc/index.php?topic=20920.0*/

unsigned long getSampleMode(bool highest)
{
  unsigned long mode = sample[0];
  int mode_count = 1;
  int count = 1;

  for (int i = 1; i < sample_size; i++) {
    if (sample[i] == sample[i - 1])
      count++;
    else
      count = 1;

    if (sample[i] == mode)
      mode_count++;
    else if ((!highest && count) > mode_count || (highest && count) == mode_count) {
      mode_count = count;
      mode = sample[i];
    }
  }

  return mode;
}

unsigned long getDistance()
{
  unsigned long range;

  if ((range = getSampleMode(true)) != getSampleMode(false))
    range = getSampleMedian();

  return range;
}

void setup() {
  while(!Serial) ;
  Serial.begin(115200);
  Serial.println("Start Setup");

//  Xbee.begin(57600);
  Serial1.begin(57600);
  Serial.println("Start Xbee");

  //This sets a default timeout for certain functions.
  Serial.setTimeout(25);

  // begin initialization
  BLE.begin();
  // Serial.print("begin initialization");
  // set advertised local name and service UUID:
  BLE.setLocalName("MailBox");
  BLE.setAdvertisedService(mailboxService);

  // add the characteristic to the service
  mailboxService.addCharacteristic(mailboxChar);

  // add service
  BLE.addService(mailboxService);

  //Read the distance set it as the default
  getSample();
  defaultDistance = getDistance();

  // set the initial value for the characeristic:
  mailboxChar.setValue(0);

  // start advertising
  BLE.advertise();

  Serial.println("BLE Mailbox Peripheral");
}

void loop() {
  //  Serial.print("Start Loop");
  // put your main code here, to run repeatedly:

  long currentTime = millis();

  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();

  // if a central is connected to peripheral:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's MAC address:
    Serial.println(central.address());

    // while the central is still connected to peripheral:
    while (central.connected()) {
      // if the remote device wrote to the characteristic,

      int userInitatedCheck = 0;
      if (mailboxChar.written()) {
        String val;

        if (mailboxChar.value() == 2) {
          val = "checkmail";
        }
        else if (mailboxChar.value() == 3) {
          val = "reset";
        }
        Serial.print("User Initated ");
        Serial.println(val);
        userInitatedCheck = checkCommand(val);
      }
      else if (currentTime - pastTime >= ONE_HOUR) {
        if (userInitatedCheck == 0) {
          pastTime = currentTime;
        }
        checkStatus();
      }
      else {
        currentTime = millis();
      }
    }

    // when the central disconnects, print it out:
    Serial.print(F("Disconnected from central: "));
    Serial.println(central.address());
  }
    else if (Serial1.available()) {
      Serial.print("Xbee value= ");
      Serial.println(Serial1.read());
    }
  else if (currentTime - pastTime >= ONE_HOUR ) {
    Serial.println("Time's up");
    checkStatus();
    pastTime = millis();
  }
}

int checkCommand(String readValue) {

  if (readValue.equalsIgnoreCase("checkmail")) {
    checkStatus();
  }
  else if (readValue.equalsIgnoreCase("reset")) {
    getSample();
    defaultDistance = getDistance();

    //may need to add an delay here
    checkStatus();
  }
  return 0;

}

void checkStatus()
{

  //use to gather the range.
  unsigned long range;

  //take a measurment and store it.
  getSample();
  range = getDistance();

  Serial.print("DD= ");
  Serial.println(defaultDistance);
  Serial.print("RIC= ");
  Serial.println(range);

  if (range != defaultDistance) {
    mailboxChar.setValue(1);
    Serial.println("You got mail");
  }
  else {
    mailboxChar.setValue(0);
    Serial.println("No mail detected");
  }

}

Credits

AB

AB

1 project • 0 followers

Comments