One of the fun things about creating is that you're not limited to solving problems that already exist, but are able to look ahead at problems that may exist someday and solve them early. This idea came to me while thinking about the future of ride sharing companies, and how they have been moving into the food delivery market. While these companies employ a huge number of drivers, they have been experimenting with self-driving vehicles for picking up and dropping off passengers. Once drivers are no longer needed for passenger transportation, it only makes sense to also remove them from the food delivery process. While thinking about how this would work, I imagined a vehicle with containers that would contain food orders from customers. In order to prevent customers early in a route from taking all of the orders off of the vehicle, I figured these containers would need to be locked until accessed by the correct customer. This led me to creating this demo project.
Imagined ProcessCustomer makes an order through an app, and is sent a message with their order combination code. Employee at the restaurant loads the order into a box, and the combination is set/box is locked. Self-driving vehicle goes about its route to deliver food to various customers.
Each customer goes to the box with their order number and enters their combination in order to unlock their box and retrieve their food. Vehicle moves to its next destination.
Combination Food LockerThis demo project uses Firebase to keep data current and to send a notification to the user after their order is placed with their combination. When an order is placed, the box is updated with the order number and a new combination that has been sent to the customer. Once the box has been locked, it can be unlocked by entering the combination (example: blue, green, blue, yellow, green). As this is a proof of concept project, I am simply using a servo as the lock (albeit a very flimsy one), and three buttons for the combination. I chose to go with a button combination system rather than NFC or some other authentication system because customers wouldn't need to remember their phone or have to put any sort of personal information into a device that they don't own. The less information you spread around about your users, the less vulnerable that information will be.
As you can see in the pictures, the box is just a simple wooden box with holes drilled out for the USB power cord, the lock notch, wire holes for the buttons and a long opening for the LCD's pins. This part will vary depending on what box you use for building and how you're locking the box. I just grabbed a $2 wooden box from a craft store.
Why?Mainly because this seems like a direction food delivery could move in the future, and this could be a solution to making that a lot smoother. I'm also a big fan of not talking to people once I'm home, so the idea of just retrieving food from what essentially breaks down to a delivery vending machine makes me pretty happy. This would be efficient, secure and save on labor.
DisclaimerThis is just a proof of concept idea that was put together for fun. Are there flaws in it? Definitely. For starters, there could be a real lock on the box. Are they big enough to not build it in the first place? Nah. Along those same lines, this went from idea to a simple project in a few hours, and was a bit of a challenge to myself to see how much I could get done quickly. Rapid prototyping with Android Things is pretty easy, and will just get easier as more components have drivers made by the community.
Code, Code, CodeThere's four main parts that go into this prototype: Firebase for a quick backend, the lock mechanism, the combination unlocker, and the display. Initial device configuration and unlock code are driven by Firebase, and the user is given a limited number of chances to unlock the device. Rather than rewriting the entire process for setting up Firebase, you can see one of my Hackster projects that also uses Firebase here. This project also goes over creating an initial Android Things project. Your initial data will look like the following:
The above database sample stores the user's order number, the state of the lock, and a representation of the lock combination. Since my prototype is using three buttons (blue, green, and yellow), I am abbreviating them to their first letter for the combination string. This means that the above combination would be opened by hitting the buttons in the following order: blue, green, yellow, blue, green, yellow.
Once you've set up your base Android Things application , it's time to start adding dependencies. You will need to add the following lines to the dependencies
node of build.gradle
to work with a few different libraries.
compile 'com.google.android.things.contrib:driver-pwmservo:0.2'
compile 'com.nilhcem.androidthings:driver-lcd1602:0.0.1'
compile 'com.google.android.things.contrib:driver-button:0.4'
compile 'com.google.firebase:firebase-core:11.4.0'
compile 'com.google.firebase:firebase-database:11.4.0'
Initial Helper ClassesNext, create the following Java classes: BoxConfig.java, BoxDisplay.java, BoxLock.java, Lock.java, and CombinationUnlocker.java.
BoxConfig
will store information from Firebase related to the box.
public class BoxConfig {
private String combination;
private boolean locked;
private int orderNumber;
public BoxConfig() {
}
public BoxConfig(String combination, boolean locked, int orderNumber) {
this.combination = combination;
this.locked = locked;
this.orderNumber = orderNumber;
}
public String getCombination() {
return combination;
}
public void setCombination(String combination) {
this.combination = combination;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public int getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(int orderNumber) {
this.orderNumber = orderNumber;
}
}
BoxDisplay
extends a great third party library by Gautier Mechling for working with the 1602 LCD screen. The main reason for using a wrapper here is organizing the pins for the display.
public class BoxDisplay extends Lcd1602 {
private static final String GPIO_LCD_RS = "BCM26";
private static final String GPIO_LCD_EN = "BCM19";
private static final String GPIO_LCD_D4 = "BCM21";
private static final String GPIO_LCD_D5 = "BCM20";
private static final String GPIO_LCD_D6 = "BCM16";
private static final String GPIO_LCD_D7 = "BCM12";
public BoxDisplay() throws IOException {
super(GPIO_LCD_RS, GPIO_LCD_EN, GPIO_LCD_D4, GPIO_LCD_D5, GPIO_LCD_D6, GPIO_LCD_D7);
begin(16, 2);
}
}
The Lock
interface simply allows the device to lock or unlock the box. This interface allows us to abstract the actual locking mechanism, since that implementation detail isn't in its final form and allows a future developer to easily change it out without breaking a lot of code.
public interface Lock {
void openLock();
void closeLock();
}
The BoxLock
class implements Lock
and uses a servo motor as the locking mechanism
public class BoxLock implements Lock {
private Servo mServo;
private double mOpenAngle = 0;
private double mClosedAngle = 120;
public BoxLock() {
try {
mServo = new Servo("PWM1");
mServo.setEnabled(true);
mServo.setAngleRange(0f, 180f);
mServo.setAngle(mClosedAngle);
} catch (IOException e) {}
}
@Override
public void openLock() {
try {
mServo.setAngle(mOpenAngle);
} catch( IOException e ) {}
}
@Override
public void closeLock() {
try {
mServo.setAngle(mClosedAngle);
} catch (IOException e) {}
}
public void close() throws IOException {
mServo.close();
}
}
The last helper class you will need to create is the CombinationUnlocker. This class wraps the three buttons used for opening the lock and handles logic that leads to either an unlock operation, or disabling the box until an employee can reset it.
public class CombinationUnlocker {
private String PIN_BLUE = "BCM14";
private String PIN_GREEN = "BCM15";
private String PIN_YELLOW = "BCM23";
private Button mBlueButton;
private Button mGreenButton;
private Button mYellowButton;
private int mNumberOfAttempts = 3;
private CombinationCallback mCallback;
private String mCombination;
private String mNeededCombinationKeys;
public CombinationUnlocker() {
try {
mBlueButton = new Button(PIN_BLUE, Button.LogicState.PRESSED_WHEN_HIGH);
mBlueButton.setOnButtonEventListener(new Button.OnButtonEventListener() {
@Override
public void onButtonEvent(Button button, boolean b) {
if( !b ) {
checkKeyInCombination('b');
}
}
});
mGreenButton = new Button(PIN_GREEN, Button.LogicState.PRESSED_WHEN_HIGH);
mGreenButton.setOnButtonEventListener(new Button.OnButtonEventListener() {
@Override
public void onButtonEvent(Button button, boolean b) {
if( !b ) {
checkKeyInCombination('g');
}
}
});
mYellowButton = new Button(PIN_YELLOW, Button.LogicState.PRESSED_WHEN_HIGH);
mYellowButton.setOnButtonEventListener(new Button.OnButtonEventListener() {
@Override
public void onButtonEvent(Button button, boolean b) {
if( !b ) {
checkKeyInCombination('y');
}
}
});
} catch( IOException e ) {
}
}
private void checkKeyInCombination(char key) {
Log.e("Test", "check key: " + key);
if( !TextUtils.isEmpty(mNeededCombinationKeys) && !TextUtils.isEmpty(mCombination) && mNumberOfAttempts >= 1 ) {
if( mNeededCombinationKeys.toLowerCase().charAt(0) == key) {
mNeededCombinationKeys = new StringBuilder(mNeededCombinationKeys).deleteCharAt(0).toString();
} else {
mNeededCombinationKeys = mCombination;
mNumberOfAttempts--;
}
if( TextUtils.isEmpty(mNeededCombinationKeys) ) {
mCallback.onCombinationSuccess();
} else if( mNumberOfAttempts == 0 ) {
mCallback.onCombinationFailed();
}
}
}
public void close() throws IOException {
mBlueButton.close();
mGreenButton.close();
mYellowButton.close();
}
public void setCallbacks(CombinationCallback callbacks) {
mCallback = callbacks;
}
public void setCombination(String combination) {
mCombination = combination;
mNeededCombinationKeys = combination;
}
public interface CombinationCallback {
void onCombinationSuccess();
void onCombinationFailed();
}
}
MainActivityThe MainActivity.java class is where the magic happens. First, implement the CombinationUnlocker.CombinationCallback
interface at the top of your class. Next you will need to derive a unique identifier for your device (I'm manually setting it to 12345
for this prototype), create instances of all of your support classes, create a reference to your Database URL and a reference to your Firebase database as global values.
private static final String BOX_URL = "https://lockbox-1750e.firebaseio.com/devices/";
private BoxLock mLock;
private BoxDisplay mDisplay;
private CombinationUnlocker mUnlocker;
private DatabaseReference mDatabaseRef;
private BoxConfig mConfig;
private static final String DEVICE_UUID = "12345";//Make an actual UUID later
In onCreate()
you will call various helper methods that we will discuss separately.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initLock();
initDisplay();
initUnlocker();
initDatabase();
}
initLock()
will initialize the lock that your project is using
private void initLock() {
mLock = new BoxLock();
mLock.closeLock();
}
initDisplay()
will create a new display object and ensure that it's clear for new text.
private void initDisplay() {
try {
mDisplay = new BoxDisplay();
mDisplay.clear();
} catch( IOException e ) {
}
}
initUnlocker()
will initialize your buttons for unlocking the box.
private void initUnlocker() {
mUnlocker = new CombinationUnlocker();
mUnlocker.setCallbacks(this);
}
and finally, initDatabase() will connect to Firebase, pull down the configuration information and initialize the default values of your application.
private void initDatabase() {
mDatabaseRef = FirebaseDatabase.getInstance().getReferenceFromUrl(BOX_URL + DEVICE_UUID );
mDatabaseRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
mConfig = dataSnapshot.getValue(BoxConfig.class);
initDefaults();
}
@Override
public void onCancelled(DatabaseError databaseError) {}
});
}
Once everything is set up, you will need to populate your unlocker interface methods so that your storage box will unlock after a successful combination, or let the user know when they have tried too many times.
@Override
public void onCombinationSuccess() {
mLock.openLock();
try {
mDisplay.clear();
mDisplay.setCursor(0, 0);
mDisplay.print("Enjoy!");
} catch( IOException e ) {
}
}
@Override
public void onCombinationFailed() {
try {
mDisplay.clear();
mDisplay.print("Out of attempts");
mDisplay.setCursor(0, 1);
mDisplay.print("Will not unlock");
} catch( IOException e ) {
}
}
Finally, you will need to close connections when your application has finished.
@Override
protected void onDestroy() {
super.onDestroy();
try {
mLock.close();
mDisplay.close();
mUnlocker.close();
} catch( Exception e ) {
}
}
See It In ActionNow that you have all of the code that you will need, it's time to see the box in action. If you enter the combination in correctly, the box will unlock.
If someone fails to unlock the combination box after a few attempts, it will prevent them from unlocking and will need to be reset by someone with administrator privileges.
Improvements That I'd Like to MakeFirst thing first, to paraphrase a great movie, "you're gonna need a bigger box." The box I used was cheap and off the shelf, so I went with it. Along those same lines, you'd want a material that's a bit more hardy than you'll find in a two dollar wooden box.
If you're really going to transport food, temperature matters. There's a great project that I saw during the 2017 Co-Making the Future contest that was able to separate out heating and cooling areas of a lunch box, which would be perfect for this project (I'd have added it to this one, but I didn't want to "steal" it until I update this after the Android Things contest) - https://www.hackster.io/46508/power-lunch-5fa8e6
Locking mechanism - I used a servo because I had it layout around. This could use a solenoid or any other fancy mechanism. This implementation detail isn't as important for a proof of concept as having the software class abstracted with 'lock()' and 'unlock()', then it's just a quick swap to use a different part.
Network connection: This is the bane of most IoT that isn't set for a smart home. I'll probably look into the Hologram SD cards when I get a chance and see if I can port it to Android Things. Right now the project is set up to need a network connection to initially get the combination (which should happen at a specific spot, like a restaurant), but that's not something I think is reliable enough, so would like to try a cellular connection.
GPS: Along the same lines as a network connection, GPS could be useful for notifying once the box has entered a geofence of the customer's home so they know to come get their stuff. This one wouldn't be difficult to add, but still requires a network connection to do anything with the GPS information.
Backend: Firebase functions and web hosting for creating an online ordering system that then generates a random combination
Notifications: Once a backend is put together, sending firebase notifications to the user would be a lot easier. Could also use another service to send the combination to a user through a text message.
Comments