Project tutorial
Dash Lid

Dash Lid

The Dash Lid is an easy to use, low friction replenishment device. You just use it like a lid.

  • 4,385 views
  • 0 comments
  • 11 respects

Components and supplies

Apps and online services

About this project

Introduction

The Dash Lid is the Amazon Container with a twist. It is designed to be a very low-friction device and method to use the Amazon Dash Replenishment Service.

The DashLid measures the remaining product in the container daily and requests a replenishment from Amazon if it is close to running out.

When a new shipment arrives from Amazon, the lid is simply placed on the new container.

Why Protein Powder?

Although the lid could work on many containers with a screw-type lid, protein powder makes a good use case right now because it typically takes awhile to deplete, it is easily and reliably measured, and has a higher price range than flour or sugar.

Thisr means that early adopters could find that it is worth the price of the sensor and software to acquire long-term customers.

The Customer's Point of View

The customer will use the lid in two scenarios:

1. Initial Setup

2. Recurring Use

For initial setup the customer will plug their lid into a computer, enter their WIFI credentials, and select the time to check for replenishment. The customer then runs an app that opens a web page to allow the customer to login to Amazon and select their product.

I believe that this is as easy as it can be. The device will likely need to charge before use, so plugging it into a computer has a dual purpose. The USB connection is also more secure than wireless when exchanging data with the device.

After the device is charged (on the MKR1000 the yellow light will stop flashing) it can be put on the new container.

When a new shipment arrives, the customer just moves the lid to the new container, optionally pouring in leftover product.

How it Works

The client application is a Windows Forms application written in C# using Visual Studio. The basic operation is as follows:

  • The Client presents a form for the user to fill in their WIFI SSID and Password. The user can also choose a time for the device to request replenishment. For example, at 3:00 AM the device can ping the product level and if it is below a predefined ordering level a replenishment request will be sent.
  • The user is then prompted to plug in their device to charge it and set it up.
  • The next step is to request the device specifications, such as Model and Serial Number.
       private void GetDRSDeviceInfo() 
       { 
           MessageBox.Show("Please plug in your DashLid now.", "Plug in your DashLid.", MessageBoxButtons.OK, MessageBoxIcon.Information); 
           while (SerialPort.GetPortNames().Count() == 0); 
           string[] ports = SerialPort.GetPortNames(); 
           string port = ports[0]; 
           //foreach (string port in ports) 
               Console.WriteLine("Opening port " + port); 
           serialPort = new SerialPort(port, 9600, Parity.None, 8, StopBits.One); 
           //serialPort.PortName = port; 
           serialPort.Open(); 
           string deviceInfo = serialPort.ReadLine(); 
           System.Diagnostics.Debug.WriteLine(deviceInfo); 
           deviceSpecifications = Json.JsonParser.Deserialize<DeviceSpecifications>(deviceInfo); 
       } 
  • Next, using the device specs and client info stored in the client (the Windows Forms app) a URL is built to request a code grant.
       private void BuildURL() 
       { 
           string scopeData = "{\"dash:replenish\":{\"device_model\":\"" + deviceSpecifications.model + "\",\"serial\":\"" + deviceSpecifications.serial + "\",\"is_test_device\":\"true\"}}"; 
           string urlEncodedScopeData = WebUtility.UrlEncode(scopeData);  
           URL = "https://www.amazon.com/ap/oa?client_id=" + client_id + "&scope=dash%3Areplenish&scope_data=" + urlEncodedScopeData + "&response_type=code&redirect_uri=https%3A%2F%2Fwww.getpostman.com%2Foauth2%2Fcallback";    
       } 
  • The application then opens a browser with the URL to allow the user to login to Amazon.
       private void UseWatinBrowser() 
       { 
           //Check url until the code is returned 
           WatiN.Core.IE ie = new WatiN.Core.IE(URL); 
           bool OkToContinue = true; 
           while (OkToContinue) 
           { 
               string currentURL = ie.Url; 
               System.Diagnostics.Debug.WriteLine(currentURL); 
               if (currentURL.Contains("code=")) 
               { 
                   returnURL = currentURL; 
                   OkToContinue = false; 
                   ie.ForceClose(); 
               } 
               else 
                   System.Threading.Thread.Sleep(1000); 
           } 
       } 
  • In the background the application checks for the browser URL to contain the code, which happens once every second. When the code is detected the browser closes.
       private void ExtractCodeFromReturnURL() 
       { 
           //returnURL = "https://app.getpostman.com/oauth2/callback?code=cccccccooooooooodddddddeeeeee&scope=dash%3Areplenish"; 
           int startString = returnURL.IndexOf("?code") + 6; 
           int endString = returnURL.IndexOf("&scope"); 
           code = returnURL.Substring(startString, endString - startString); 
           //System.Diagnostics.Debug.WriteLine(code); 
       } 
  • Next the code is used to obtain the DRS Tokens.
       private void ObtainDRSTokens() 
       { 
           var client = new RestClient("https://api.amazon.com/auth/o2/token"); 
           var request = new RestRequest(Method.POST); 
           request.AddHeader("cache-control", "no-cache"); 
           request.AddHeader("content-type", "application/x-www-form-urlencoded"); 
           request.AddParameter("application/x-www-form-urlencoded", "grant_type=authorization_code&code=" + code + "&client_id=" + client_id + "&client_secret=" + client_secret + "&redirect_uri=https%3A%2F%2Fwww.getpostman.com%2Foauth2%2Fcallback", ParameterType.RequestBody); 
           IRestResponse response = client.Execute(request); 
           //System.Diagnostics.Debug.WriteLine(response.Content); 
           tokens = Json.JsonParser.Deserialize<Tokens>(response.Content); 
       } 
  • The tokens are sent to the DRS device through USB serial encoded as JSON.
       private void SendDeviceInfo() 
       { 
           deviceData = new DeviceData(); 
           deviceData.AccessToken = tokens.access_token; 
           deviceData.RefreshToken = tokens.refresh_token; 
           deviceData.WifiPassword = wifiPassword; 
           deviceData.WifiSsid = wifiSsid; 
           deviceData.ClientId = client_id; 
           deviceData.ClientSecret = client_secret; 
           deviceData.SlotId = slot_id; 
           deviceData.ReplenishHour = replenishHour; 
           DateTime dateTime = System.DateTime.Now; 
           deviceData.Year = (byte)(dateTime.Year - 2000); 
           deviceData.Month = (byte)dateTime.Month; 
           deviceData.Day = (byte)dateTime.Day; 
           deviceData.Hour = (byte)dateTime.Hour; 
           deviceData.Minute = (byte)dateTime.Minute; 
           deviceData.Second = (byte)dateTime.Second; 
           string json = Json.JsonParser.Serialize<DeviceData>(deviceData); 
           System.Diagnostics.Debug.WriteLine(json); 
           serialPort.WriteLine(json); 
       } 
  • The Arduino takes over and waits for the real-time clock to check the product level and possibly request replenishment.
  • The application can optionally run after this to print out Arduino status in the Visual Studio debugging console.
       private void ReadDeviceDebuggingInfo() 
       { 
           while (true) 
           { 
               if (serialPort.BytesToRead > 0) 
               { 
                   System.Diagnostics.Debug.WriteLine(serialPort.ReadLine()); 
               } 
           } 
       } 
  • Every 24 hours the ultrasonic sensor pings the product level. If the level is below the replenish threshold the product is ordered by the Dash Lid. The customer receives a replenishment email.

Getting Started with Dash Replenishment Service

A good place to start would be to look at the Amazon documentation here.

You will need to Create an LWA Security Profile, Create and SNS Topic, and Create your Amazon Dash Device.

For more detailed information on the process that is directly related to the AmazonDRS library used in this project, see the AmazonDRS Getting Started page.

Construction Note

Don't try to solder to LiPo batteries. You can make a simple connector out of solid wire like so:

Code

Visual Studio DashLid Client SolutionC#
This code is run to setup the DashLid Device.
No preview (download only).
Visual Studio DashLid Client CodeC#
Used to set up the Dash Lid
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using RestSharp;
using System.IO.Ports;

//Watin problems?
//Add reference to Interop.SHDocVw
//Set CopyLocal to true, or copy the dll into the Release/Debug folders

namespace DRS
{
    public partial class SetupForm : Form
    {
        SerialPort serialPort;
        string returnURL;
        string code;
        Tokens tokens;
        string wifiPassword;
        string wifiSsid;

        //Enter your client info here.
        //Go to https://developer.amazon.com/iba-sp/overview.html -> APPS & SERVICES -> Security Profile (in sub-header) and choose your security profile.
        const string client_id = "";
        const string client_secret = "";
        const string slot_id = ""; //APPS & SERVICES -> Dash Replenishment Service  (sub-header)

        DeviceData deviceData;
        DeviceSpecifications deviceSpecifications;
        string URL;
        byte replenishHour;

        public SetupForm()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //Save the settings from the form
            replenishHour = (Byte) cbRefreshHour.SelectedIndex;
            wifiSsid = tbSSID.Text;
            wifiPassword = tbPassword.Text;

            GetDRSDeviceInfo();
            BuildURL();
            UseWatinBrowser();
            ExtractCodeFromReturnURL();
            ObtainDRSTokens();
            SendDeviceInfo();
            ReadDeviceDebuggingInfo();
        }

        private void GetDRSDeviceInfo()
        {
            while (SerialPort.GetPortNames().Count() == 0); //Wait for a COM port to appear - when MKR1000 is connected
            string[] ports = SerialPort.GetPortNames();
            string port = ports[0];
            
            Console.WriteLine("Opening port " + port);

            serialPort = new SerialPort(port, 9600, Parity.None, 8, StopBits.One);
            serialPort.Open();
            string deviceInfo = serialPort.ReadLine();
            System.Diagnostics.Debug.WriteLine(deviceInfo);
            deviceSpecifications = Json.JsonParser.Deserialize<DeviceSpecifications>(deviceInfo);
        }

        private void BuildURL()
        {
            string scopeData = "{\"dash:replenish\":{\"device_model\":\"" + deviceSpecifications.model + "\",\"serial\":\"" + deviceSpecifications.serial + "\",\"is_test_device\":\"true\"}}";
            string urlEncodedScopeData = WebUtility.UrlEncode(scopeData); 
            URL = "https://www.amazon.com/ap/oa?client_id=" + client_id + "&scope=dash%3Areplenish&scope_data=" + urlEncodedScopeData + "&response_type=code&redirect_uri=https%3A%2F%2Fwww.getpostman.com%2Foauth2%2Fcallback";   
        }

        private void UseWatinBrowser()
        {
            //Check url until the code is returned
            WatiN.Core.IE ie = new WatiN.Core.IE(URL);
            bool OkToContinue = true;
            while (OkToContinue)
            {
                string currentURL = ie.Url;
                System.Diagnostics.Debug.WriteLine(currentURL);
                if (currentURL.Contains("code="))
                {
                    returnURL = currentURL;
                    OkToContinue = false;
                    ie.ForceClose();
                }
                else
                    System.Threading.Thread.Sleep(1000);
            }
        }

        private void ExtractCodeFromReturnURL()
        {
            //returnURL example: "https://app.getpostman.com/oauth2/callback?code=cccccccooooooooodddddddeeeeee&scope=dash%3Areplenish";
            int startString = returnURL.IndexOf("?code") + 6;
            int endString = returnURL.IndexOf("&scope");
            code = returnURL.Substring(startString, endString - startString);
            //System.Diagnostics.Debug.WriteLine(code);
        }

        private void ObtainDRSTokens()
        {
            var client = new RestClient("https://api.amazon.com/auth/o2/token");
            var request = new RestRequest(Method.POST);
            request.AddHeader("cache-control", "no-cache");
            request.AddHeader("content-type", "application/x-www-form-urlencoded");
            request.AddParameter("application/x-www-form-urlencoded", "grant_type=authorization_code&code=" + code + "&client_id=" + client_id + "&client_secret=" + client_secret + "&redirect_uri=https%3A%2F%2Fwww.getpostman.com%2Foauth2%2Fcallback", ParameterType.RequestBody);
            IRestResponse response = client.Execute(request);

            //System.Diagnostics.Debug.WriteLine(response.Content);
            tokens = Json.JsonParser.Deserialize<Tokens>(response.Content);
        }

        private void SendDeviceInfo()
        {
            deviceData = new DeviceData();
            deviceData.AccessToken = tokens.access_token;
            deviceData.RefreshToken = tokens.refresh_token;
            deviceData.WifiPassword = wifiPassword;
            deviceData.WifiSsid = wifiSsid;
            deviceData.ClientId = client_id;
            deviceData.ClientSecret = client_secret;
            deviceData.SlotId = slot_id;
            deviceData.ReplenishHour = replenishHour;

            DateTime dateTime = System.DateTime.Now;
            deviceData.Year = (byte)(dateTime.Year - 2000);
            deviceData.Month = (byte)dateTime.Month;
            deviceData.Day = (byte)dateTime.Day;
            deviceData.Hour = (byte)dateTime.Hour;
            deviceData.Minute = (byte)dateTime.Minute;
            deviceData.Second = (byte)dateTime.Second;
            string json = Json.JsonParser.Serialize<DeviceData>(deviceData);
            System.Diagnostics.Debug.WriteLine(json);
            serialPort.WriteLine(json);
        }


        private void ReadDeviceDebuggingInfo()
        {
            while (true)
            {
                if (serialPort.BytesToRead > 0)
                {
                    System.Diagnostics.Debug.WriteLine(serialPort.ReadLine());
                }
            }
        }


        // FIN
























        private void TestSendingDeviceInfo()
        {
            //SerialPort testPort = new SerialPort("COM5", 9600, Parity.None, 8, StopBits.One);
            //testPort.Open();
            //string line = testPort.ReadLine();
            //System.Diagnostics.Debug.WriteLine(line);

            deviceData = new DeviceData();
            deviceData.AccessToken = "Atza|testAccessToken";
            deviceData.RefreshToken = "Atzr|testRefreshToken";
            deviceData.WifiPassword = wifiPassword;
            deviceData.WifiSsid = wifiSsid;
            deviceData.ClientId = client_id;
            deviceData.ClientSecret = client_secret;
            deviceData.SlotId = slot_id;
            deviceData.ReplenishHour = 3;

            DateTime dateTime = System.DateTime.Now;
            deviceData.Year = (byte)(dateTime.Year - 2000);
            deviceData.Month = (byte)dateTime.Month;
            deviceData.Day = (byte)dateTime.Day;
            deviceData.Hour = (byte)dateTime.Hour;
            deviceData.Minute = (byte)dateTime.Minute;
            deviceData.Second = (byte)dateTime.Second;
            string json = Json.JsonParser.Serialize<DeviceData>(deviceData);
            //testPort.WriteLine(json);
        }




        private void ParseTokensTest()
        {
            string tokens = "{\"access_token\":\"Atza|testAccessToken\",\"refresh_token\":\"Atzr|testRefreshToken\",\"token_type\":\"bearer\",\"expires_in\":3585}";
            System.Diagnostics.Debug.WriteLine(tokens);
            Tokens t = Json.JsonParser.Deserialize<Tokens>(tokens);
        }

        private void flowLayoutPanel1_Paint(object sender, PaintEventArgs e)
        {

        }

        private void label3_Click(object sender, EventArgs e)
        {

        }
    }
}
Arduino Code FilesArduino
MKR1000 Code
No preview (download only).
Arduino Code for DashLidArduino
DashLid Code
#include <ArduinoJson.h>
#include <RTCZero.h>
#include "AmazonDRS.h"

//JSON Parsing -------------------------------------------------------------------------------
StaticJsonBuffer<2000> jsonBuffer;
String readString;
//--------------------------------------------------------------------------------------------

//DRS ----------------------------------------------------
//WiFi creds ----------------------------------------------------------------------------------
//char ssid[] = ""; //  your network SSID (name)
//char pass[] = ""; // your network password (use for WPA, or use as key for WEP)
//------------------------------------------------------------------------------------------------------

#define slotNumber 1 //This will vary for multi slot devices - dash buttons typically only serve one product/slot

const int dashButton = 5;     //DIO number of the pushbutton pin
static long buttonHigh = 0;    //millis of last button push for switch debouncing
static String slotStatus = ""; //boolean which depicts if slot is available for replenishment
static String slotId = "";     //unique slot id ex: 0a5038b7-7609-4b81-b87e-3e291f386324 
//Enter your device Info
  char jsonDeviceInfo[] ="{\"model\":\"Dry_Goods_Retro_Container\",\"serial\":\"dgrc01\"}";
  
AmazonDRS DRS = AmazonDRS();
  
//RTC ----------------------------------------------------
RTCZero rtc; //rtc RTCZero instance
bool awake = false; //true = running, false = sleep mode
//Values to set the current initial date and time 
byte seconds;
byte minutes;
byte hours;
byte day;
byte month;
byte year;
byte replenishhour;

//Ultrasonic ---------------------------------------------
#define echoPin 7 // Echo Pin
#define trigPin 8 // Trigger Pin
#define LEDPin 6 // Onboard LED

int maximumRange = 200; // Maximum range needed
int minimumRange = 5; // Minimum range needed
int replenishRange = 20; //20 cm. or greater => Replenish
long duration, distance; // Duration used to calculate distance

//Setup ---------------------------------------------------
void setup() {
 Serial.begin (9600);

 delay(10000); //delay to measure current draw. Also needed to be able to re-program MKR1000 when using sleep mode

//Exchange Data with Client ----------------------------------------------------------------
Serial.println(jsonDeviceInfo);

  while (!Serial.available()) {} //Wait for the serial port
  while (Serial.available()) {
    delay(3);  //delay to allow buffer to fill
    if (Serial.available() >0) {
      char c = Serial.read();  //gets one byte from serial buffer
      readString += c; //append to readString
    }
  }

  //if (readString.length() >0) Serial.println(readString); //see what was received

  JsonObject& root = jsonBuffer.parseObject(readString);

  // Test if parsing failed
  if (!root.success()) {
    Serial.println("parseObject() failed");
    return; //We have a problem
  }

  
  String ssidString = root["wifissid"];
  char ssid[ssidString.length()]; 
  ssidString.toCharArray(ssid, ssidString.length()+1); 

  String passString = root["wifipassword"];
  char pass[passString.length()]; 
  passString.toCharArray(pass, passString.length()+1);

  String refreshToken = root["refreshtoken"];
  DRS.setRefreshToken(refreshToken);

  String accessToken = root["accesstoken"];
  DRS.setAccessToken(accessToken);

  

  Serial.print("==========> Refresh Token: ");
  Serial.println(refresh_token);

  Serial.print("==========> Access Token: ");
  Serial.println(access_token);

  


  seconds = root["second"];
  minutes = root["minute"];
  hours = root["hour"];
  year = root["year"];
  month = root["month"];
  day = root["day"];
  replenishhour = root["replenishhour"];

  Serial.print("SSID: ");
  Serial.println(ssid);
  //Serial.print("PASS: ");
  //Serial.println(pass);
      
//------------------------------------------------------------------------------------------


  DRS.begin(ssid,pass); //Startup DRS
  
  pinMode(LEDPin, OUTPUT); //set LED pin to output
  digitalWrite(LEDPin, LOW); //turn LED off

  rtc.begin(); //Start RTC, this is where the clock source is initialized

  rtc.setTime(hours, minutes, seconds); //set time
  rtc.setDate(day, month, year); //set date
  rtc.setAlarmTime(replenishhour, 0, 0); 

  Serial.print("================> Replenish Hour: ");
  Serial.println(replenishhour);
  Serial.print("RTC Date/Time: ");
  PrintDateTime();

  
  //rtc.enableAlarm(rtc.MATCH_SS);//set alarm time to go off on matching seconds
  rtc.enableAlarm(rtc.MATCH_HHMMSS); //set alarm to go off at a specific time
  rtc.attachInterrupt(ISR); //creates an interrupt that wakes the SAMD21 which is triggered by a FTC alarm

  
  //Replenish(); //Replenish immediately for testing
  rtc.standbyMode(); //Puts the SAMD chip in standby (low power) mode. USB Serial port will not work (disconnects from USB). Comment out for testing.
}

//Loop -----------------------------------------------
void loop() {
 if (awake)
 { 
  Serial.println("awake in loop");
  ping();
  awake = false;
 }
}

void ISR()
{
  //Serial.println("awake in ISR");
  awake = true;
  PrintDateTime();
}

void ping()
{
  Serial.println("awake in ping");
 //Setup Ultrasonic Sensor
 pinMode(trigPin, OUTPUT);
 pinMode(echoPin, INPUT);

 //Ping Ultrasonic Sensor and show distance
 /* The following trigPin/echoPin cycle is used to determine the distance of the nearest object by bouncing soundwaves off of it. */ 
 //digitalWrite(trigPin, LOW); 
 delayMicroseconds(2); 

 digitalWrite(trigPin, HIGH);
 delayMicroseconds(10); 
 
 digitalWrite(trigPin, LOW);
 duration = pulseIn(echoPin, HIGH);
 
 //Calculate the distance (in cm) based on the speed of sound.
 float distance = duration/58.2;
 
 if (distance >= maximumRange || distance <= minimumRange)
 {
  //Ignore
  //Serial.println("out of range");
 }
 else {
  //We have a valid distance reading, see if we need to replenish
   digitalWrite(LEDPin, HIGH);
   delay(1000);
   digitalWrite(LEDPin, LOW);
   delay(1000);
  if (distance >= replenishRange)
    Replenish();
    
 /* Send the distance to the computer using Serial protocol, and turn LED OFF to indicate successful reading. */
 Serial.print("Distance: ");
 Serial.print(distance,3);
 Serial.print(" cm Duration: ");
 Serial.println(duration);
 }
}

void Replenish()
{
  Serial.println("Requesting replenishment");
    DRS.retrieveSubscriptionInfo();  //check slot statuses

  slotStatus = DRS.getSlotStatus(slotNumber);
  slotId = DRS.getSlotId(slotNumber);

        //Replenish          
        if(slotStatus == "true")   //if the product in slot are available 
        {
            //we have a match! replenish the products associated with that slot!    
            Serial.println("Trying to replenish!!!");        
            DRS.requestReplenishmentForSlot(slotId);
        }
        else
        {
          Serial.print("Sorry, slot ");
          Serial.print(slotId);
          Serial.println(" is not available at this time");
        }
}

void print2digits(int number) 
{
  if (number < 10) {
    Serial.print("0"); // print a 0 before if the number is < than 10
  }
  Serial.print(number);
}

void PrintDateTime()
{
    // Print date...
  print2digits(rtc.getMonth());
  Serial.print("/");
  print2digits(rtc.getDay());
  Serial.print("/");
  print2digits(rtc.getYear());
  Serial.print(" ");

  // ...and time
  print2digits(rtc.getHours());
  Serial.print(":");
  print2digits(rtc.getMinutes());
  Serial.print(":");
  print2digits(rtc.getSeconds());

  Serial.println();
}
An Arduino library for WiFi101 connected devices implementing the Amazon Dash Replenishment API

Schematics

DashLid Schematic
Dashlid schematic eoy6akpqfl

Comments

Similar projects you might like

Amazon Dash Button for Arduino!

Project tutorial by Brian Carbonette

  • 9,829 views
  • 3 comments
  • 24 respects

Amazon Dash Replenishment Pens

Project tutorial by Alex Merchen

  • 691 views
  • 0 comments
  • 3 respects

Dash Button Santa with Arduino MKR1000

Project tutorial by Team Programarfacil

  • 1,812 views
  • 0 comments
  • 7 respects

IoT for coins

Project tutorial by Erik Moran

  • 8,373 views
  • 1 comment
  • 42 respects

Visibility sensor for divers

Project tutorial by svenvdvoort

  • 2,169 views
  • 0 comments
  • 6 respects

RFID Door Unlock

Project tutorial by Kirby G

  • 21,983 views
  • 1 comment
  • 53 respects
Add projectSign up / Login