Things used in this project

Schematics

Raspberry Pi GPIO useage
Basic document showing the pins used for the Raspberry PI
basicpi_U609AED7Cg.fzz

Code

Book SelectorC#
This code uses the GoodReads API to get a list of book ISBN's for uploading into DRS. The numbers in each of the string arrays are the author id's from GoodReads. I obtained this by navigating to the author page for each of the chosen authors. For example the first id for thriller authors is 3892 which corresponds to Tom Clancy via the URL https://www.goodreads.com/author/show/3892.Tom_Clancy
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace BookSelector
{
    public class Actions
    {
        private static Random random;
        private string apiAddress = "https://www.goodreads.com/author/list/";
        private string key = "<YOUR_KEY>";
        private string[] thrillerAuthors = { "3892", "2989", "6866", "5194", "18411" };
        private string[] horrorAuthors = { "88506", "3389", "10366", "9355", "6941" };
        private string[] comedyAuthors = { "1654", "4", "7963", "2929", "1221698" };
        private string[] sciFiAuthors = { "4763", "545", "4192148", "51204", "25375" };

        public Actions()
        {
            random = new Random(DateTime.Now.Millisecond);
            random.Next();
        }

        public void GenerateCSV(string filePath)
        {
            var list = GetBookList();
            StringBuilder sb = new StringBuilder();
            foreach(Book b in list)
            {
                sb.AppendLine($"{b.ISBN},{b.Genre},{b.Title.Replace(',', '.')},{b.Author}");
            }
            if (File.Exists(filePath))
                File.Delete(filePath);
            using (var f = File.CreateText(filePath))
            {
                f.Write(sb.ToString());
            }
        }

        public List<Book> GetBookList()
        {
            List<Book> books = new List<Book>();
            books.Add(GetBook(thrillerAuthors[random.Next(0, thrillerAuthors.Length - 1)], "Thriller"));
            books.Add(GetBook(horrorAuthors[random.Next(0, horrorAuthors.Length - 1)], "Horror"));
            books.Add(GetBook(comedyAuthors[random.Next(0, comedyAuthors.Length - 1)], "Comedy"));
            books.Add(GetBook(sciFiAuthors[random.Next(0, sciFiAuthors.Length - 1)], "SciFi"));

            return books;
        }

        public Book GetBook(string authorId, string genre)
        {
            string serviceCall = $"{apiAddress}{authorId}?format=xml&key={key}";
            WebRequest req = WebRequest.Create(serviceCall);
            var resp = (HttpWebResponse)req.GetResponse();
            Stream s = resp.GetResponseStream();
            using (StreamReader sr = new StreamReader(s))
            {
                var y = sr.ReadToEnd();
                var doc = XElement.Parse(y);
                string author = doc.Elements("author").Elements("name").FirstOrDefault().Value;
                var rtn = doc.Elements("author").Elements("books").Elements("book").ToList();
                int count = rtn.Count();
                Random rand = new Random();
                int toPick = rand.Next(count - 1);
                var isbn = rtn[toPick].Element("isbn").Value;
                var title = rtn[toPick].Element("title").Value;
                Book b = new Book(isbn, title, genre, author);

                return b;
            }
        }
    }
}
Main - ServiceActionsC#
This file contains all of the actions to call the DRS services
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace BookClub
{
    public class ServiceActions
    {
        protected JsonSerializerSettings settings;
        private string drsAddress = "https://dash-replenishment-service-na.amazon.com";
        private State state;
        private SaveState saveState;

        public ServiceActions()
        {
            saveState = new SaveState();
            state = saveState.DeserializeState(@"c:\booktest\settings.xml");
            settings = new JsonSerializerSettings();
            settings.NullValueHandling = NullValueHandling.Ignore;
            settings.TypeNameHandling = TypeNameHandling.None;
        }

        public async void Authorize()
        {
            var result = Task.Run(async () => {
                return await this.GetAccessToken();
            }).Result;
            dynamic d = JsonConvert.DeserializeObject(result, settings);
            state.AuthorizationCode = d.access_token;
            state.RefreshToken = d.refresh_token;
            saveState.SerializeState(state);
        }

        public async void RefreshToken()
        {
            var result = Task.Run(async () =>
            {
                return await this.GetRefreshToken();
            }).Result;
            dynamic d = JsonConvert.DeserializeObject(result, settings);
            state.AuthorizationCode = d.access_token;
            state.RefreshToken = d.refresh_token;
            saveState.SerializeState(state);
        }
        public void Replenish(string slotId)
        {
            RefreshToken();
            SendReplenRequest(drsAddress, slotId);
        }

        public void UpdateStatus()
        {
            string json = "{ \"mostRecentlyActiveDate\" : \"" + DateTime.Now + "\"}";
            RefreshToken();
            SendStatusRequest(drsAddress, json);
        }

        protected async Task<string> GetAccessToken()
        {
            string accessAddress = "https://api.amazon.com/auth/o2/token";
            Auth auth = new Auth("authorization_code", state.AccessToken, state.ClientId, state.ClientSecret, "https%3A%2F%2Flocalhost");
            return await GetServiceResponse(accessAddress, auth.GetBody());
        }

        protected async Task<string> GetRefreshToken()
        {
            string accessAddress = "https://api.amazon.com/auth/o2/token";
            Auth auth = new BookClub.Auth("refresh_token", state.RefreshToken, state.ClientId, state.ClientSecret, "https%3A%2F%2Flocalhost");
            return await GetServiceResponse(accessAddress, auth.GetBody());
        }
        protected async Task<string> GetServiceResponse(string address, string body)
        {
            var request = (HttpWebRequest)WebRequest.Create(address);
            request.ContentType = "application/x-www-form-urlencoded";
            request.Method = "POST";
            request.Headers["Cache-Control"] = "no-cache";
            var data = System.Text.Encoding.UTF8.GetBytes(body);
            using (var sw = new StreamWriter(await request.GetRequestStreamAsync()))
            {
                sw.Write(body);
            }
            try
            {
                using (var response = await request.GetResponseAsync())
                {
                    using (var sr = new StreamReader(response.GetResponseStream()))
                    {
                        return (sr.ReadToEnd());
                    }
                }
            }
            catch (WebException ex)
            {
                var resp = ex.Response as HttpWebResponse;
                using (var sr = new StreamReader(resp.GetResponseStream()))
                {
                    string s = sr.ReadToEnd();
                }
            }
        }

        protected async void SendReplenRequest(string address, string slotId)
        {
            var request = (HttpWebRequest)WebRequest.Create(address + "/replenish/" + slotId);
            request.ContentType = "application/json";
            request.Headers["x-amzn-accept-type"] = "com.amazon.dash.replenishment.DrsReplenishResult@1.0";
            request.Headers["x-amzn-type-version"] = "com.amazon.dash.replenishment.DrsReplenishInput@1.0";
            request.Headers["Authorization"] = "Bearer " + state.AuthorizationCode;
            request.Method = "POST";
            try
            {
                var response = await request.GetResponseAsync() as HttpWebResponse;
                    using (var sr = new StreamReader(response.GetResponseStream()))
                    {
                        var s =  (sr.ReadToEnd());
                    }
            }
            catch (WebException ex)
            {
                var resp = ex.Response as HttpWebResponse;
                using (var sr = new StreamReader(resp.GetResponseStream()))
                {
                    string s = sr.ReadToEnd();
                }
            }
        }

        protected async void SendStatusRequest(string address, string jsonBody)
        {
            var request = (HttpWebRequest)WebRequest.Create(address);
            request.ContentType = "application/json";
            request.Headers["x-amzn-accept-type"] = "com.amazon.dash.replenishment.DrsDeviceStatusResult@1.0";
            request.Headers["x-amzn-type-version"] = "com.amazon.dash.replenishment.DrsDeviceStatusInput@1.0";
            request.Headers["Authorization"] = "Bearer " + state.AuthorizationCode;
            request.Method = "POST";
            using (var sw = new StreamWriter(await request.GetRequestStreamAsync()))
            {
                sw.Write(jsonBody);
            }
            try
            {
                var response = await request.GetResponseAsync() as HttpWebResponse;
            }
            catch (WebException ex)
            {
                var resp = ex.Response as HttpWebResponse;
                using (var sr = new StreamReader(resp.GetResponseStream()))
                {
                    string s = sr.ReadToEnd();
                }
            }
        }
    }
}
Book Selector - TestC#
I use a unit test to run this. The intention going forward would be for a management dashboard, but is dependent on seeing what if any changes are made to the DRS API's in the near future. This would then determine if this was to be part of an automated process such as PowerShell or bash, or if it was to continue being a manual process.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using BookSelector;
using System.IO;

namespace BookSelector.Tests
{
    [TestClass]
    public class ActionsTests
    {
        [TestMethod]
        public void TestMethod1()
        {
            Actions a = new Actions();
            a.GenerateCSV(@"c:\booktest\latest.csv");
            Assert.IsTrue(File.Exists(@"c:\booktest\latest.csv"));
        }
    }
}
Main - AuthC#
Helper classed used in main the get the request bodies for calls to obtain auth and refresh tokens
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BookClub
{
    public class Auth
    {
        public Auth(string grant_type, string code, string client_id, string client_secret, string redirect_url)
        {
            this.grant_type = grant_type;
            this.code = code;
            this.client_id = client_id;
            this.client_secret = client_secret;
            this.redirect_uri = redirect_url;
        }
        public string grant_type { get; set; }
        public string code { get; set; }
        public string client_id { get; set; }
        public string client_secret { get; set; }
        public string redirect_uri { get; set; }

        public string GetBody()
        {
            if (grant_type != "refresh_token")
                return $"grant_type={grant_type}&code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}";
            else
                return $"grant_type={grant_type}&refresh_token={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}";
        }
    }
}
Main - SlotC#
Basic class to hold slot values
    public class Slot
    {
        public Slot(int slotNumber, string name, string id)
        {
            SlotNumber = slotNumber;
            Name = name;
            SlotID = id;
        }
        public int SlotNumber { get; set; }
        public string Name { get; set; }
        public string SlotID { get; set; }
    }
Main - MainPage.xamlXML
XAML main page for display on Raspberry Pi
<Page
    x:Class="BookClub.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BookClub"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Page.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="FontSize" Value="35"/>
        </Style>
    </Page.Resources>
    <Grid Background="Black">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock Foreground="Yellow">Smart Book Club</TextBlock>
        <TextBlock Grid.Row="1" Name="Order"/>
        <TextBlock Grid.Row="2" Name="ChosenSlot"/>
    </Grid>
</Page>
Main - MainPage.xaml.csC#
The code behind file for MainPage.xaml. This contains the code for accessing the GPIO pins on the device and making the calls to ServiceActions to order via DRS
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Devices.Gpio;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace BookClub
{
    public sealed partial class MainPage : Page
    {
        private const int BUTTON_PIN = 5;
        private const int SELECTOR_PIN = 4;
        private GpioPin buttonPin;
        private GpioPin selectorPin;
        private static List<Slot> slots;
        private static int SelectedSlot = 1;
        private static DispatcherTimer orderTimer;

        public MainPage()
        {
            this.InitializeComponent();
            try
            {
                this.Initialize();
                slots = new List<Slot>();
                slots.Add(new Slot(1, "Thriller", "05709bc7-83d3-47b6-be79-619555e15698"));
                slots.Add(new Slot(2, "Horror", "6eea7f46-0691-48b3-8edc-fbb9b8a44c5a"));
                slots.Add(new Slot(3, "Comedy", "35cd98c5-050d-4934-a20b-479213ce5768"));
                slots.Add(new Slot(4, "SciFi", "1494afb3-0e95-4212-8450-f3302bae7c32"));
            }
            catch (Exception ex)
            {
                Order.Text = ex.Message;
            }
        }

        private void Initialize()
        {
            var gpio = GpioController.GetDefault();

            if (gpio == null)
            {
                throw new Exception("There is no GPIO controller on this device.");
            }

            buttonPin = gpio.OpenPin(BUTTON_PIN);
            if (buttonPin.IsDriveModeSupported(GpioPinDriveMode.InputPullUp))
                buttonPin.SetDriveMode(GpioPinDriveMode.InputPullUp);
            else
                buttonPin.SetDriveMode(GpioPinDriveMode.Input);
            buttonPin.DebounceTimeout = TimeSpan.FromMilliseconds(50);
            buttonPin.ValueChanged += ButtonPin_ValueChanged;

            selectorPin = gpio.OpenPin(SELECTOR_PIN);
            if (selectorPin.IsDriveModeSupported(GpioPinDriveMode.InputPullUp))
                selectorPin.SetDriveMode(GpioPinDriveMode.InputPullUp);
            else
                selectorPin.SetDriveMode(GpioPinDriveMode.Input);
            selectorPin.DebounceTimeout = TimeSpan.FromMilliseconds(50);
            selectorPin.ValueChanged += SelectorPin_ValueChanged;

            InitializeStatusTimer();
            InitializeOrderTimer();
        }

        private void InitializeStatusTimer()
        {
            DispatcherTimer dt = new DispatcherTimer();
            dt.Tick += StatusTimerTick;
            dt.Interval = new TimeSpan(12, 0, 0);
            dt.Start();
        }

        private void StatusTimerTick(object sender, object e)
        {
            SendStatus();
        }

        private void InitializeOrderTimer()
        {
            orderTimer = new DispatcherTimer();
            orderTimer.Tick += OrderTimerTick;
            orderTimer.Interval = new TimeSpan(0, 0, 30);
        }

        private void OrderTimerTick(object sender, object e)
        {
            var slotId = slots.Where(s => s.SlotNumber == SelectedSlot).FirstOrDefault().SlotID;
            MakeOrder(slotId);
            orderTimer.Stop();
            var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                Order.Text = "An Order has been placed";
            });
        }

        private void SelectorPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
                if (args.Edge == GpioPinEdge.FallingEdge)
                {
                    if (SelectedSlot == slots.Count)
                    {
                        SelectedSlot = 1;
                    }
                    else
                    {
                        SelectedSlot++;
                    }
                    var slotName = slots.Where(s => s.SlotNumber == SelectedSlot).FirstOrDefault().Name;
                    ChosenSlot.Text = "Selected slot is:  " + slotName;
                }
            });
        }

        private void ButtonPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
                if (args.Edge == GpioPinEdge.FallingEdge)
                {
                    Order.Text = "A book is in the holder";
                }
                else
                {
                    Order.Text = "An order is about to be made";
                    orderTimer.Start();
                }
            });
        }

        public void MakeOrder(string slotId)
        {
            ServiceActions actions = new ServiceActions();
            actions.Replenish(slotId);
        }

        public void SendStatus()
        {
            ServiceActions actions = new ServiceActions();
            actions.UpdateStatus();
        }
    }
}
UserLoginHTML
This is a basic html file with javascript to allow login with amazon to configure your DRS device
<!DOCTYPE html>
<html>
<head>
    <title></title>
	<meta charset="utf-8" />
</head>
<body>
    <div id="amazon-root"></div>
    <script type="text/javascript">

      window.onAmazonLoginReady = function() {
          amazon.Login.setClientId('<yourclientid>');
    };
    (function(d) {
        var a = d.createElement('script'); a.type = 'text/javascript';
        a.async = true; a.id = 'amazon-login-sdk';
        a.src = 'https://api-cdn.amazon.com/sdk/login1.js';
        d.getElementById('amazon-root').appendChild(a);
    })(document);

    </script>
    <a href="#" id="LoginWithAmazon">
        <img border="0" alt="Login with Amazon"
             src="https://images-na.ssl-images-amazon.com/images/G/01/lwa/btnLWA_gold_156x32.png"
             width="156" height="32" />
    </a>
    <script type="text/javascript">

        document.getElementById('LoginWithAmazon').onclick = function () {
            var options = new Object();
            var scope = ('dash:replenish');
            var scope_data = new Object();
            scope_data['dash:replenish'] = { "device_model": "<yourdevicemodel>", "serial": "<yourserial>" };
            options['scope_data'] = scope_data;
            options['scope'] = scope;
            options['response_type'] = 'code';
            amazon.Login.authorize(options, "<yourredirecturi>");
        return false;
        };

    </script>
</body>
</html>

Credits

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

PHPoc Blue to Control Door Knob by MQTT Protocol and Cortana
Intermediate
  • 76
  • 7

Programming a PHPoc Blue board to control a servo motor to rotate the knob of a door using MQTT Protocol through voice command.

Face Detection Using OpenCV With Raspberry Pi
Intermediate
  • 3,190
  • 32

Protip

In this project, we are going to detect faces using OpenCV with Raspberry Pi.

Creating an IoT Server with Home Assistant and MQTT
Intermediate
  • 3,624
  • 20

Protip

This guide will help you set up Home Assistant. Access it from any web browser and automations.

Automated GPS Controlled Photo Taker
Intermediate
  • 1,751
  • 20

For this build, I put together a backpack to take pictures when I am close to places that I like; especially in London.

DIY Smart Assistant Speaker/Lamp (Google Home or Alexa)
Intermediate
  • 1,951
  • 5

Full instructions

DIY build using 3D printed enclosure and parts, a recycled Bluetooth speaker, a Raspberry Pi, and a Philips Hue light bulb.

Home Automation Using Wiscore and OpenHab
Intermediate
  • 279
  • 2

Protip

This project will help you setup your very own Alexa controlled Home Appliances using the wonderful Wiscore Module from RAK Wireless.

ProjectsCommunitiesTopicsContestsLiveAppsBetaFree StoreBlogAdd projectSign up / Login