The Barbecue Planner will be a great show-off device that you can put centrally on your kitchen table; a little barbecue shaped alarm that tells you when it’s going to be barbecue weather in the coming days. You can select your friends you want to be there and Barbecue Planner will take care of the rest: sending the invites, registering participants and ordering your Barbecue supplies through the Amazon DRS system; delivered just in time at your door. Of course the order is based on the number of friends that will attend and their preferences for their food and drinks, topped up with other supplies you need for firing up the barbecue like coals or gas. Happy barbecue time!
Amazon DRSThis project is built around ordering supplies through Amazon DRS (Dash Replenishment Service). Short explanation of what this is about: an end-user subscribes to receive delivery of a refill just before he runs out of stock. Processing the order and delivery is the part that Amazon will cater for. Keeping track of the stock and timing the refill is what this project is about.
For tracking the stock, we could measure usage by counting, getting the weight of a box or read the level of a container. I decided to go for something different: looking at the weather forecast!
The BarbecuePlanner keeps track of the forecast in your area. It will alert you when the perfect barbecue weather is upcoming. To make sure you are ready for a barbecue party, you can accept a day, select your friends to invite, and let BarbecuePlanner take the ordering process for you.
Amazon Customer Experience (CX) Guidelines
Amazon does offer a Customer Experience (CX) description of how a device and the ordering process should behave. The BarbecuePlanner is not fully compatible on all items. For example the approach of weather forecast is a bit different from sensing usage levels; we don't have a physical sensor, we sense the weather. That means I cannot make usage records and send inventory messages based on consumption (other than "barbecue date passed, inventory = 0). Also the cancellation followup is not implemented as the user does start the ordering process actively. But an item that does fit with this approach and which I did implement, is that the user can see the setup and follow orders from the device.
The challenge of DRS Slots
For the Replenishment service, you will setup “slots”. As an example, take a printer: there will be 5 slots for ink cartridges (Black, Photo, Magenta, Cyan, Yellow), where you as a user can select a brand and size for each color. If Magenta runs out, your printer will order the “Magenta Slot”, where Amazon DRS knows which cartridge you selected at the setup screen.
The challenge I found with this method: you can only have 1 order a time for a single slot. That means that in Barbecue terms, for “Slot: drinks” you selected “12 pack of small Cola cans” as the brand/size. For a large group of friends, I cannot order 5 packs of 12 cans at the same time, because I cannot select the number, or the type of product. So I ended up creating groups of slots: Drinks 1-10 persons, Drinks 11-20 persons, etc. In each slot, other sizes of packs are used. For the 1-10 persons, a 12 pack can be selected. For 11-20 it will be a 24 pack. Once this project comes to real life, better mix packs and other sizes can be designed this way.
Registration and ordering flow
Before starting to develop, we need to understand the Amazon flow:
- User will grant us access through Amazon LWA (Login With Amazon) and selects the products he wants to order for each slot and he selects a delivery address and payment method
- Once an order is needed, we send a Replenishment request to Amazon
- Amazon will respond with a quick status message (order received / already in progress / error)
- Amazon will send a detailed response to an Amazon SNS (Simple Notification Service) topic (order confirmed / order canceled / order shipped)
- As SNS is just a "mail office" without storage, we need to have an endpoint behind it. If the BarbecuePlanner would have a website with an open URL the SNS can post the data to it. But we don’t have that yet; everything is on my local network. So we ask SNS to posts the messages to Amazon SQS (Simple Queue Service) which will hold the messages in a queue for 14 days. With BOTO3 we can get the messages from there.
What is a hackster project without hardware? So here we go; a Barbecue device with a tiny computer, blinking lights, OLED screen and a joystick.
The Bill of Materials
These are the materials I use:
- A miniature BBQ
- Linkit Smart 7688 DUO
- OLED screen (i2c)
- NeoPixel LED ring (7 LEDs)
- 2-axis+select Joystick
- 470 Ohm resistor for the NeoPixel
- 1000uF 16V capacitor for the NeoPixel
- 10k Ohm resistor for the Joystick
- Wires
- USB charger
And the tools for building:
- Soldering iron
- Hot glue gun
Putting it together
To build something, you have to break it first. So first step is getting an electric drill and make some holes in my miniature barbecue. These are needed to feed the wires and joystick through. Next was to solder wires to the NeoPixel ring. Now placed all in the barbecue and used hot glue to hold all parts back together. Also the lid now stays permanently open.
As a last step, I've used a little wooden boarding to hide the wire-spaghetti. Finished product looks like this:
As the tiny computer, I chose the LinkIt Smart 7688 DUO. Really this thing is small! In previous projects I worked with Raspberry Pi's, I expected something half the size of that. But when I received the package, it was only the size of a matchbox. Opening the matchbox, 3/4 of it was foam, leaving a really tiny computer.
MCU and MPU
The Linkit Smart 7688 DUO does have 2 processors (DUO... you could have guessed that), The MCU processor for the internet connected side, and the MPU processor for the sensor connected side. You can reach both through the pinout:
I'm used to write Python code, as the Linkit Smart 7688 DUO has an MCU that can work with Python, that will be my basis. I hoped that would do all the tricks. But following a webinar from MediaTek, I realized this is not true... The sensors can only be connected to the MPU, which works with Arduino sketches. I learned that my MCU has to talk to the MPU through Firmata. This is a new world for me...
I need to connect my OLED screen through I2C, the NeoPixel on a Digital pin and the Joystick on 2 analog and 1 digital (or analog) pin. From all types something :)
Getting Started with the LinkitThe steps for getting your Linkit Smart 7688 DUO alive are well described in their manual, I recommend to read that. But some steps to give you a good start:
- Plug the USB cable into your Linkit Smart 7688 DUO and a computer.
- Your LinkIt will start up in AP mode.
- Connect your computer to the wifi-AP that the LinkIt created.
- Open website for the AP-mode, setup a password, go to the Network tab.
- Fill in the details of your regular internet connected Wifi and restart
- Board is now connected to Wifi on my home network, remember to also connect your computer to the home network again.
- Now open the setup website of your LInkit again, this time you will be in the Connected mode
- Apply firmware upgrade (see https://labs.mediatek.com/site/global/developer_tools/mediatek_linkit_smart_7688/sdt_intro/index.gsp)
- Device will restart in AP mode, so do all again:
- Connect your computer to the wifi-AP that the LinkIt created.
- Open website for the AP-mode, setup a password, go to the Network tab.
- Fill in the details of your regular internet connected Wifi and restart
- Board is now connected to Wifi on my home network, remember to also connect your computer to the home network again.
- Now open the setup website of your LInkit again, this time you will be in the Connected mode
Connect to your LinkIt 7688 DUO
The LinkIt does have an MCU and an MPU. The MCU runs on OpenWRT and can be accessed from the outside. You can write scripts, upload them and execute things. The MPU is more a closed environment and only accepts compiled Arduino sketches.
Connect to board through SSH to do admin and execute commands
- Download PuTTy and install
- Use IP-address, protocol SSH, port 22 and login with root/pwd
- Accept Security Alert
- (Nice add-on: MPuTTy if you have multiple devices in your network.)
Connect to MCU to upload script files
- Download WinSCP and install
- Use IP-address, protocol SCP, port 22 and login with root/pwd
- Accept Security Alert
Connect to MPU to upload Arduino sketches
- Download Arduino IDE and install
- Setup additonal boards by entering these two URLs in the preference screen: http://download.labs.mediatek.com/package_mtk_linkit_smart_7688_index.json,http://download.labs.mediatek.com/package_mtk_linkit_smart_7688_test_index.json
- Select the board in the menu Tools/Board/LinkIt Smart 7688 DUO
- Connect the LinkIt with your computer through USB. It should automatically connect to a serial port. The port is shown in the Arduino IDE, such that you can upload your sketches directly.
For the code we use in this project, some modules and libraries are needed:
Install packages in Python
Connect to your LinkIt using PuTTy. Once logged in, you can use PIP to install the needed Python modules by executing:
pip install qrcode
pip install requests
pip install boto3
pip install python-dateutil
Boto3 is used to connect to Amazon SQS messaging queue. We need the dateutil to use this module. I had an issue with installing python-dateutil, the tz.py files were not copied. This seems to be a known issue, copy the files manually from the GitHub.
Install packages in OpenWRT
As OpenWRT does contain a stripped down version of Python, not all modules are installed by default, or install with PIP. For my OLED display I need PIL (Python Image Library) or its sibbling PILLOW.
- Go to your LinkIt setup page, and click the link for the OpenWRT page at the bottom of your login page
- Login with the same password
- Go to the System tab, and just below that, click Software tab
- First click Update Lists to update the available packages
- In the search-box search for python-imglib and install.
Install modules in Arduino IDE
For the usage of Firmata and NeoPixel on the Arduino side, we need to install some modules:
- Download the needed library files for Adafruit NeoPixel
- Place the libraries in the Arduino folder (see preferences, Sketchbook location)
- Go to Sketch/Include Library/Manage Libraries
- Install the following libraries: Adafruit NeoPixel and Firmata
There are some flavors of Firmata, which do in basic form a bridge between Python on the MCU and Arduino on the MPU, passing over data for the Analog and Digital pins. Hurray, that is what I need for my joystick!
But getting a Neopixel working, or an OLED display is some other story. To extend Firmata, I used FirmataPlus and pyMata.
Controlling the Neopixel
To send pixel colors to the NeoPixel, I had to extend my Arduino and Python implementation of Firmata. Python will need to be able to send commands to the Arduino to light up the NeoPixels. For each color change, we need a little bit of traffic over the serial connection, more on that later. As the ring of the NeoPixel will be a constant burning fire, creating a constant load on the serial connection, I offloaded the logic for that to the Arduino code. This saves some the constant color changing serial communication for 6 pixels. Only controlling the central pixel is done from Python, and starting the Fire with a single command.
To change the code of Firmata, follow this great manual: http://www.instructables.com/id/Going-Beyond-StandardFirmata-Adding-New-Device-Sup/?ALLSTEPS
Following this guide, I did added this part to Firmata on the Arduino side:
void sysexCallback(byte command, byte argc, byte *argv)
{
...
/* NEOPIXELS */
case NEOPIXEL_DATA:
{
if (argv[0] == NEOPIXEL_COLOR)
{
int NeoPixelIndex = argv[1];
int NeoPixelRed = argv[2];
int NeoPixelGreen = argv[3];
int NeoPixelBlue = argv[4];
neopixel_strip->setPixelColor(NeoPixelIndex, NeoPixelRed, NeoPixelGreen, NeoPixelBlue);
neopixel_strip->show();
}
else if (argv[0] == NEOPIXEL_FIRE)
{
FireStatus = argv[1];
if (FireStatus == 1){
neopixel_fire();
}
}
}
break;
And added to the Firmata module files at the Python side:
def neopixel_color(self, NeoPixelIndex, NeoPixelRed, NeoPixelGreen, NeoPixelBlue):
data = [self.NEOPIXEL_COLOR, NeoPixelIndex & 0x7f, NeoPixelRed & 0x7f, NeoPixelGreen & 0x7f, NeoPixelBlue & 0x7f]
self._command_handler.send_sysex(self._command_handler.NEOPIXEL_DATA, data)
def neopixel_fire(self, FireStatus):
data = [self.NEOPIXEL_FIRE, FireStatus & 0x7f]
self._command_handler.send_sysex(self._command_handler.NEOPIXEL_DATA, data)
So from Python I can now call these as:
# Light the fire!
firmata.neopixel_fire(1)
# NeoPixel to Red:
firmata.neopixel_color(0, 255, 0, 0)
While working on the implementation of the NeoPixel control, I had some headaches before getting it working. It turned out that the PIN on the LinkIt I used (D6) is both Analog and Digital. The code in Firmata is designed to put the pin to Analog by default by setting A7 (PIN25). Setting it back to Digital did not work for my code, so in the end I had to manually exclude this pin from becoming Analog:
void systemResetCallback()
{
...
for (byte i = 0; i < TOTAL_PINS; i++) {
// pins with analog capability default to analog input
// otherwise, pins default to digital output
if (25 == i) {
// This is the NEOPIXEL pin D6, it cannot double as an Analog pin A7, so first set it to DIGITAL
setPinModeCallback(i, OUTPUT);
} else if (IS_PIN_ANALOG(i)) {
// turns off pullup, configures everything
setPinModeCallback(i, PIN_MODE_ANALOG);
} else if (IS_PIN_DIGITAL(i)) {
// sets the output to 0, configures portConfigInputs
setPinModeCallback(i, OUTPUT);
}
Controlling the OLED Display
At first I bought a SPI OLED Display. But soon I found that it uses too many wires to control through Firmata. New order! This time an I2C version. With FirmataPlus and pyMata, you can control that "easily" by sending the full I2C code over the serial connection, you don't even need to change the FirmataPlus / pyMata code!
It turned out that my ordered screen was not the easy SSD1306 version, but a SH1106 version. No worries, just another type of commands... SSD1306 can handle small commands for specific pieces of the screen, but SH1106 can only get full pages. That means that the amount of data to send for a screen-refresh is a full 64x128 pixels. The normal bytewidth of an I2C connection could handle 32 bytes in one line, but here comes some limitations of the serial Firmata connection. Each byte is 8 pixels wide, so I could do this with 32 lines. But that did give a disturbed image. I ended up with sending 16 bytes in one go.
The code I found at https://github.com/Schrottratte/sh1106 helped me big time. I did apply the needed changes in this module to send the I2C commands to Firmata instead of normal serial, and applied the smaller packet size:
from PyMata_bsl.pymata import PyMata
class device(object):
""" Base class for OLED driver classes """
def __init__(self, firmata, address=0x3C, cmd_mode=0x00, data_mode=0x40):
self.cmd_mode = cmd_mode
self.data_mode = data_mode
self.firmata = firmata
self.addr = address
def command(self, *cmd):
""" Sends a command or sequence of commands through to the device - maximum allowed is 16 bytes in one go. """
for i in xrange(0, len(cmd), 16):
self.firmata.i2c_write(self.addr, self.cmd_mode, list(cmd[i:i+16]))
def data(self, data):
""" Sends a data byte or sequence of data bytes through to the device - maximum allowed in one transaction is 16 bytes, so if data is larger than this it is sent in chunks. """
for i in xrange(0, len(data), 16):
self.firmata.i2c_write(self.addr, self.data_mode, list(data[i:i+16]))
So I need to send 64 I2C commands for the actual refresh + some guiding commands. As long as there is no other communication this goes fairly well, but as soon as serial traffic goes up, the screen becomes sluggish.
Receiving input from the Joystick
The joystick is easier: standard Firmata and just listening to analog and digital signals. Only downside for me so far, is that Firmata sends ALL analog changes over the serial to Python. When you want very accurate analog metering this is perfectly fine, but I just want to know what direction the joystick is moving to, one click at a time. From the moment I started the forwarding of analog readings, my display became sluggish. If I would improve something, it would be adjusting this part of the Firmata code at the Arduino side. But for now, I'll live with it.
I've not completed implementation for button_press, but you can see traces of available code :)
First sign of life
After some tests from the Arduino side only to confirm the connections work OK and all libraries were installed, it was time to let Python do a simple execution of the NeoPixels and OLED screen:
Weather forecastTo predict the temperature and expected rain, we use the YR.no weather forecast. This is a website from the Norwegian Meteorological Institute and the NRK that does provide detailed forecasts for Europe. And they have a good XML source.
There is no registration process, just read their documentation and get started!
Barbecue time is usually in the afternoon and evening. So I discard the weather of night and morning. But what is warm enough for you? And what about a Tuesday evening? We have that for you, see the setup website! You can put in your preferences and location. BarbecuePlanner will take care of the rest.
Amazon DRSAs stated in the introduction, you can select a day, your friends, and let BarbecuePlanner take the ordering process for you.
To get there, we need the following steps for initial setup:
- BarbecuePlanner needs to be granted access by the user through Amazon LWA
- User will select the products he want to order for each slot, and selects delivery address and payment method
- BarbecuePlanner will store these tokens for later use
When the user selects a barbecue date, the BarbecuePlanner will start the Amazon DRS ordering process:
- Send a Replenishment request to Amazon DRS
- Amazon will respond with a quick status (order received / already in progress / error)
- Amazon will send a detailed response to an SNS topic (order confirmed / order canceled / order shipped)
- Message is forwarded to Amazon SQS (Simple Queue Service) which will hold the messages in a queue for 14 days.
- With BOTO3 we can get the messages from there.
Before we can use this in our project, we need to setup and register with several Amazon services first. A detailed step by step manual is provided by Amazon.
First create an account and setup for LWA:
Then create an SNS topic:
Last setup a DRS account:
Last additional step is to create an SQS queue:
All the login credentials you got here, will be inserted in the Python code later on.
Software processes of the BarbecuePlannerDuring development I made several small components that I later merged into 2 core processes. This could even be merged into 1 process and for sure I could have made modules, etc. But for now, I have a BarbecuePlanner.py that takes care of everything on the screen, the weatherforecast, the lights and the joystick. And there is a BarbecueServer.py that acts as a webserver for everything too large to be on the small OLED screen.
Webserver
The barbecue device does only have a little 64x128 pixel sized screen. This is enough for the basic information, but setting up the Amazon LWA and DRS, plus selecting your city and preferences, I decided to create a webserver on the LinkIt. You can reach this on your local network from a computer, tablet or phone in a normal internet browser.
The HTTP protocol on port 80 was already in use, and I have to keep the URL short (see later note), Creating a connection on port :8080 made too many characters. So I decided to go for an HTTPS connection.
The webserver is mainly used for showing information, and storing simple preferences. The intermediate between both core processes is a SQLite database. Easy to store data in, easy to get data out. For the scale of data I'm using in my local barbecue device, this is powerful enough.
Setup testing
Once you boot the device up, first task after instantiation of pyMata, is checking if you did your setup. If you are missing something like the city for YR.no, the Amazon tokens, or did not select a barbecue day, the setup will not let you continue. The barbecue device will show you the URL and a QR code that links to that same URL. And that is why I needed to keep it as short as possible. This makes the QR code smaller in size, such that i can show it bigger on that little screen.
Amazon LWA
The webserver has one big task; do the Amazon LWA registration. The user clicks the "login with Amazon" button and is guided to the BarbecuePlanner login page at Amazon. The user will select the products for each Slot (Softdrinks, Sauce, Charcoal) and completes registration. Amazon will then go back to our local website and supplies a "Grant Code". The local website does a back-end login to Amazon to get an "Access Token" and "Refresh Token" from Amazon LWA. The Access Token is valid for a shorter time, but can always be refreshed with the Refresh Token (what is in a name...). Refreshing the token is a returning process in my code on the device side.
SNS and SQS
Once the registration is complete, Amazon does send a "DeviceRegisteredNotification" message to our SNS, which is forwarded to SQS. I've decided to ignore that message and throw it away. But Amazon does also send a "SubscriptionChangedNotification" message, that (as of v2) does contain the selected ASINs (product code). This is usefull information as I can now tell my user that he selected Cola! So I do process that message.
Later on in the process, Amazon does send me "OrderPlacedNotification", "OrderCancelledNotification" and "ItemShippedNotification" messages. Of course these are processed too, to show your order status at the website.
YR.no forecast
There is a subprocess that does check the YR.no forecast by downloading the XML file for your place, and storing all afternoon and evening details in the database. Then we analyze if there is barbecue weather upcoming. If you set 22 degrees, we do check if both afternoon and evening are above 22. And if you selected 0 mm rain, the total rain of afternoon+evening should not be more than 0. Last step is finding if you selected this day as a barbecue day, because maybe you only want to invite friends on a Thursday evening. If all these are OK, and you don't have a party planned yet, the middle LED of the NeoPixel will start blinking until you accept the party.
Screen
The text displayed on screen, is steered by the joystick input. This process is always triggered by the Firmata Analog messages, where it sends an Up/Down/Left/Right trigger to my RunScreen process. I store the current screen and see what action is linked to which direction.
Project demonstrationTo show how to setup the DRS connection at first boot of the Barbecue, and how to throw a Barbecue Party, I've made a video.
Enjoy, and see you at the next party!
UpdatesSince I finished this project, I've started on a new MediaTek LinkIt Smart 7688 DUO project. With that project, I've learned some changes I would need to apply in this project to make it better:
The Firmata protocol transfers 7 bit data. The max LED value for R/G/B is 255, but that is more than 7 bits. So the integer needs to be split into 2, as I'm now only receiving values with max 127. Solve this by sending more parameters at the Python side:
def neopixel_color(self, NeoPixelIndex, NeoPixelRed
data = [self.NEOPIXEL_COLOR, NeoPixelIndex & 0x7f, NeoPixelRed
self._command_handler.send_sysex(self._command_handler.NEOPIXEL_DATA, data)
There is also a change needed at Arduino side to join the 2 parameters:
if (argv[0] == NEOPIXEL_COLOR)
{
int NeoPixelIndex = argv[1];
int NeoPixelRed =
int NeoPixelGreen =
int NeoPixelBlue =
neopixel_strip->setPixelColor(NeoPixelIndex, NeoPixelRed, NeoPixelGreen, NeoPixelBlue);
neopixel_strip->show();
}
And I complained about a sluggish response on the OLED. Firmata has a command to lower the number of interrupts, this will limit the number of reads and serial sends on the joystic. This can be called from Python:
firmata.set_sampling_interval(500) # (in ms, so this is half a second!)
Or set at the Arduino side:
unsigned int samplingInterval = 19; // how often to run the main loop (in ms)
Comments