Wednesday, December 21, 2011


240 CPS NYC- Mail Chute

Alex Kozovski
Eszter Ozsvald
Michael Colombo
Yonatan Ben-Simhon

Rob Faludi: Sensitive Buildings class
NYU Tisch ITP 2011 Fall

Project Description

There are more than 900 mail chutes still in use in New York City, and 240 CPS is counted among them. However, it's underutilized due to common belief that it's unreliable. There are countless stories and myths about mail being stuck of lost in these chutes, which results in tenants being wary of using them on a day-to-day basis. Our mail chute tracking system aims to solve this by verifying the mail's passage through several floors of the building until it has safely arrived in the 1st floor holding box.

A challenge to the project was that we had to accomplish this goal without putting anything inside the mail chute itself, as its federal property. An initial idea was to sense the sound of an incoming envelope or package. We recorded mail going down the chute to see if this was possible. While it made noise, and interesting noise at that, we looked into more reliable alternatives.

We found this in the use of photocells and an LED attached to the glass. The inside of the mail chute is black, but when something drops through, it reflects the light from the LED and changes the value of the photo sensor. By using this method, we were able to reliably track pieces of mail and then use XBee for the units on various floors to communicate with each other as the mail made it's way down.

We packaged the sensors with an Arduino, Xbee, and battery pack in a 5.5 x 5.5" box that's affixed to the outside of the chute. Made of wood, we shaved a column on the inside of the box's facade so thin that the light from an indicator LED could shine through. This ran along a laser-cut silhouette of 240 CPS, allowing each LED to successively light from the inside, giving a warm glow instead of a familiar bare LED, while also giving visual confirmation to the user of the mail being sent.

Technical Description

Xbees were measured on site to communicate for a range of 8 floors without interruptions (past the 9th floor communication was slow suggesting repeated packet loss & resends) when placed directly against the mail chute. We used this fact to cover the entire building (26 floors) while using only 5 detection boxes placed 5 floors apart.

As the mail chute & mail box are USPS property we were unable to use sensors that would be physically triggered by the mail and had to resort to external detection methods. While acoustic print of mail falling down the chute was impressive we found it harder to detect. We measured simple visual impact of mail falling in the chute & noticed a detectable change at mail fall. The challenges were in the repeatability & S/N ration as at different times, locations and mail items very different input levels were recorded. We have overcome these challenges by setting multiple high sensitivity photo sensors in each module & installing a continuously self calibrating (and self error correcting) coded mechanism.

Challenges & Solutions

●Photocell covering partial chute width: 2 photocells at 1/3 width intervals cover full width of chute.
●Pitch darkness: pulldown resistors at high values (~1-10 MOhm)
●Changing light conditions: annealing calibration for a changing environment; continuous exponential moving average; no reporting when in lighting conditions change mode (letter signal is a brief < 100 ms peak, lighting changes are slower and tend to lack distinct peaks)
●Low signal: minimal signal required (<5% above calculated moving average); multiple sensors confirm signal; assistive light projected from module into chute. Evaluated at simulated conditions with no false readings or missed readings. Evaluated in situ to detect dark (including black) envelopes successfully.
●Nigh noise: multiple senses from same sensor required for reporting. best available photocells chosen; averaged ambient conditions.
●Failure detection: modules connected in ring topology, checking self messages on every report; calibration restarts for erratic readings (>4% inconsistency of measurements for period of 1 second).
●Accountability: current version reports only detected mail. Identification of 'stuck' mail requiers a longer time study in practical use at building. Suggested mechanism: signal not captured by consecutive detectors will be rechecked for 'close to threshold' & communication lapse conditions by coordinator queries to relevant detectors.


//Code for mail detection in 240 CPS mail chute

#define PD1 A0 //photocell 1
#define PD2 A5 //photocell 2
#define SELF 1 //ID of box. pin defined as SELF indicator (LED)
#define PINS 2 //lowest actual pin value for LEDs. pin counts start at this value inpractice.

//Global Variables
float avg1 = 755; //value of moving average for photocell 1
float avg2 = 750; //value of moving average for photocell 2
int avgSampleSize = 100; //sample size for callibration & moving average
float alpha = 1.0/avgSampleSize; //alpha value for exponential moving average
float factor = 1.0 - alpha; //1-alpha
long time = 0; //system time. zeros out every 51 days.
long lastRead = 0; //time of last sensing for SELF
float sensitivity1 = 0.95; //sensing threshold. signal to noise determined at <2%; sensitivity requieres 5% off average
float sensitivity2 = 0.95; //sensing threshold for other photocell
long travelTime = 300; //maximum expected time for sensing a single item in the chute. in practice this value ~ 50 ms.
long lightTime = 1000; //duration of LED signaling
int blinktime = 20; //duration of blink time for cinsecutive semses within lightTime. < actual travel time. Noticable by human eye
boolean reading = false; //boolean keeping current status (i.e. sensor state)
long pinUps[5]; //time value of last detection in any of the sensors. Used to determine signal (LED lights for 1 sec after last detection)
int outgoing = 0; //number of detections currently reported by SELF without confirmation by other. Zeros when reported back.

//Arduino Initialization

void setup() {
for (int i = 0; i < 5 ; i++){
pinMode(PINS+i, OUTPUT); //set all LED signaling pins
pinUps[i]=0; // set all 'last detection time' to 0
callibrate(); //initialize photocell calibraion
//annealing callibration. reads twice per photocell per iteration
//averages readings using an exponential moving average
//restarts callibration at erratic conditions: reading > 2% current average

void callibrate(){
avg1 = analogRead(PD1);
avg2 = analogRead(PD2);
time = millis();
lastRead = millis();
int checks = 0;
for (int i=0; i int sensorValue1a = analogRead(PD1);
int sensorValue2a = analogRead(PD2);
int sensorValue1b = analogRead(PD1);
int sensorValue2b = analogRead(PD2);
int tmp1 = ceil(sensorValue1a+sensorValue1b-(2*avg1));
int tmp2 = ceil(sensorValue2a+sensorValue2a-(2*avg2));
if (tmp1 < max(2,2*avg1/i) && tmp2 < max(2,2*avg2/i)){
//if both sensors current averaged readings are within either
//2/#iterations of current moving average (applicable within about first half of iterations)
//0.2 percent of current moving average (forced threshold for final iterations)
//this allows low tolerance at initial averaging but a tight read for finalization
avg1 = moveAvg(avg1, sensorValue1a);
avg2 = moveAvg(avg2, sensorValue2a);
//restart when reading of either sensors > 2% than current average
avg1 = analogRead(PD1);
avg2 = analogRead(PD2);
i = 0;
float moveAvg(float currAvg, int sample){
//exponential moving average
//keeps averaged measures always updated
//not sensitive to sudden changes
//requires minimal memory
//formula: alpha*read_value +
(1-alpha)*current average
float retval = currAvg * factor;
retval = (alpha * sample) + retval;
return retval;

//main loop
//makes measurements to determine sensing
//checks for notifications from other boxes
//reports to user ny lighting LEDs
//makes decisions about detections
//continuously calibrates ambient light

void loop() {
//read twice per sensor
int sensorValue1a = analogRead(PD1);
int sensorValue2a = analogRead(PD2);
int sensorValue1b = analogRead(PD1);
int sensorValue2b = analogRead(PD2);

//check XB for signals from other boxes

if (Serial.available() > 0) {
//display LED by calculated time
//set current time. used for LED lighting decisions & recalibration decisions
time = millis();
//calculate current threshold by current moving aberage value & set sensitivity
//calculated seperately for each photocell as each has its own reading and averages
int threshold1 = floor(avg1*sensitivity1);
int threshold2 = floor(avg2*sensitivity2);
if ((sensorValue1a < threshold1) && (sensorValue1b < threshold1) || (sensorValue2a < threshold2) && (sensorValue2b < threshold2))//495)
// detection is reported only when 2 readings for either photocell pass threshold level
if (abs(lastRead-time)>travelTime && !reading){
//set time of last sensing to NOW - will be used for LED reporting
lastRead = time;
//set SELF status to 'in detection'
reading = true;
//report detection
//sensor above threshold for prolonged time. Ignore detection, continue averaging to adapt to new lighting conditions
avg1 = moveAvg(avg1,sensorValue1a);
avg2 = moveAvg(avg2,sensorValue2a);
//no detection - continue averaging ambient light
reading = false;
avg1 = moveAvg(avg1,sensorValue1a);
avg2 = moveAvg(avg2,sensorValue2a);

//procedures required on every mail detection event

void mailDetected(){
//inform all boxes
Serial.print(SELF, DEC);
//increment count of reported sensings
//if sensing while in SELF LED on: blink
if (time - pinUps[SELF] < lightTime){
digitalWrite(PINS+SELF, LOW);
digitalWrite(PINS+SELF, HIGH);
//set last time of detection in array (used for lighting LEDs)
pinUps[SELF] = time;

//procedures requiered on every mail reported via Xbee

void mailNotified(int val){
//for all other boxes - set last time of detection to NOW
if (val != SELF){
Serial.print(val, DEC);
//if new letter reported while LED on - short blink
if (time - pinUps[val] < lightTime){
digitalWrite(PINS+val, LOW);
digitalWrite(PINS+val, HIGH);
//if SELF looped through Xbee - decrement number of active detections. Signaling through other boxes has succeeded
//set last time of detection in array (used for lighting LEDs)
pinUps[val] = time;
//read incoming data from other boxes
int readSerial(){
int val = 0;
if (Serial.available() > 0) {;
return val;

//report detections by lighting up appropriate LEDs for lightTime (1 second) from last sensing

void lightLEDs(){
for (int i=0; i<5 ; i++){
//if value of last detection for specific box within time threshold compared to NOW - light LED
if (time - pinUps[i] < lightTime){
digitalWrite(PINS+i, HIGH);
//if value of last detection for specific box outside time threshold compared to NOW - turn LED off
digitalWrite(PINS+i, LOW);