Amazon’s prices change constantly. If you leave items in your shopping cart for longer than a few hours, you’ll likely get alerted about minute fluctuations – $0.10 here, $2.04 there. Amazon and its merchants are obviously using some form of algorithmic pricing to squeeze the last penny out of the market.
That’s all to be expected (late capitalism and all that). But what happens if things go awry? In 2011, a pricing war broke out between two competing algorithms. The result: a book on the lifecycle of houseflies (out of print, but not particularly rare) skyrocketed to a price of $23.6 million.
Amazon’s recent acquisition of Whole Foods Market made us wonder: what’s stopping dynamic pricing from stepping into the physical world of retail? What if the prices in a supermarket were just as flexible as those online?
So, in this Instructable, we’ll be building a dynamic price display with an Arduino and a small LCD. We’ll also briefly talk about disguising and installing it in a store.
(And, if you’re interested, this Chrome plugin can show you the pricing history of any item on Amazon over the last 120 days.)
Needed Material
Here’s what we used to build this project:
- An Arduino Uno R3
- A standard 16×2 LCD display. We used this one from Adafruit, but as long as it’s compatible with the LiquidCrystal library, you should be good. You’ll need a few things to wire it up to the Arduino:
- some jumper cables
- a 220 ohm resistor
- a 10k ohm potentiometer (This is for controlling the contrast of the display. If you find a contrast you like, you can replace the potentiometer with a fixed resistor.)
- Some acrylic for the box. We used a cast matte black acrylic, laser cut and assembled with acrylic solvent-adhesive and hot glue.
- Magnets and/or a shelving hook to attach the box in-store. If you go the hook route, you could measure and 3d-print one, or try to find one online (Alibaba, perhaps?), or … acquire it in some other, more nefarious manner. Be safe.
First, let’s get the display going!
Step 1: Wire Up the Display
There sure are a lot of pins on the back of that LCD. Luckily, the documentation for the software library we’re going to use has a good guide to wiring it up. Check it out.
In summary, your wiring should end up like this:
- Power:
- LCD GND (pin 1) → Arduino GND
- LCD VDD (pin 2) → Arduino +5V
- LCD RW (pin 5) → Arduino GND
- Data stuff:
- LCD RS (pin 4) → Arduino digital pin 12
- LCD Enable (pin 6) → Arduino digital pin 11
- LCD D4 (pin 11) → digital pin 5
- LCD D5 (pin 12) → digital pin 4
- LCD D6 (pin 13) → digital pin 3
- LCD D7 (pin 14) → digital pin 2
- Display contrast:
- Wire a 10k potentiometer’s legs to Arduino’s +5V and GND
- Potentiometer’s output → LCD VO (pin 3).
- Backlight:
- LCD BL1 (pin 15) → 220 ohm resistor → Arduino +5V
- LCD BL2 (pin 16) → Arduino GND
When that’s all set, load up one of the example LiquidCrystal projects in the Arduino IDE and see if it works! Remember to double-check the LCD initialization code in the samples – the pin numbers need to be correct or you won’t see anything.
For example, the “Blink” example has this code, which is correct given the above setup:
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
Tips
- Save yourself some soldering and invest in some crimp ends and header connectors. On projects like this where we’re going to cram the electronics into a small case, being able to make short jumper cables is incredibly helpful.
- Similarly, heatshrink tubing is really useful to make sure nothing shorts out when it’s all pressed up against itself.
- Since there are so many things going to GND and +5V, we opted to make a franken-cable (see the photo above) to be as compact as possible. If space were less of an issue, a breadboard or protoshield would have been an easier option.
- Some potentiometers are weirdly shaped. Generally, the left lead is used as ground, the rightmost lead as power, and the middle one as output. If yours has two leads on the front and one on the back, the one on the back is the output.
Gotchas
- If you don’t see anything on your LCD, try turning the potentiometer all the way in one direction, then the other. At its lowest contrast, the LCD’s content is completely invisible.
- If you see really weird gibberish on the LCD, or only one line instead of two, make sure all your connections are secure. We had a faulty connection to ground and it was causing the weirdest display issues.
- The LCD initialization code (what gets run by lcd.init() in the setup() function) is important and takes a while. If something is wrong with your display and you suspect a faulty wire, don’t expect jiggling things to suddenly make it work. You may need to reset the Arduino so the initialization code has a chance to run properly.
- Make sure your wires are pretty short, but not too short. Nothing’s worse than having to resolder because you’re a few centimeters away from a header.
Great! Now let’s make it show some fancy things.
Step 2: Code: Basics
First things first: let’s have the display show “Current Price:” on the top line, and a random price in some range on the second. Every so often, let’s have the price refresh. This is pretty simple, but will highlight the basic use of the LiquidCrystal library and some of its quirks.
First, let’s pull in the library and define some constants:
#include <LiquidCrystal.h>const uint8_t lcdWidth = 16; const uint8_t lcdHeight = 2;const long minPriceInCents = 50; const long maxPriceInCents = 1999;const unsigned long minMillisBetweenPriceUpdates = 0.25 * 1000; const unsigned long maxMillisBetweenPriceUpdates = 2 * 1000
Great! Those are the parameters for the price range and how often it will refresh. Now let’s make an instance of the LCD class provided by the library and initialize it. We’ll print something out over the serial console, just to have some reassurance that things are working, even if we don’t see anything on the LCD. We’ll do that in the setup() function, which runs once after the Arduino boots. Note, though, that we declare the lcd variable outside of setup(), because we want access to it throughout the program.
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup() { Serial.begin(9600); lcd.begin(lcdWidth, lcdHeight); Serial.println("LCD initialized"); lcd.print("Current Price:"); }
And for the meat, we’ll use the built-in random() function and the String() initializer to construct a decimal price. random() only generates integers, so we’ll divide its result by 100.0 to get a floating-point value. We’ll do this in loop(), so it happens as often as possible, but with a random delay between the constants we defined earlier.
void loop() { double price = random(minPriceInCents, maxPriceInCents) / 100.0; String prettyPrice = "$" + String(price, 2); lcd.setCursor(0, 1); lcd.print(prettyPrice); delay(random(minMillisBetweenPriceUpdates, maxMillisBetweenPriceUpdates)); }
One thing to note is the call to lcd.setCursor(). The LiquidCrystal library doesn’t automatically advance your text to the next line after a print, so we need to manually move the (invisible) cursor to the second line (here 1 – it’s zero-based). Also note that we didn’t have to print “Current Price:” again; the LCD is not cleared unless you do so manually, so we only have to update dynamic text.
Give it a run and you’ll quickly see a related problem. If the price was, say, “$14.99” and then “$7.22”, the display will show “$7.229”. Remember, the display doesn’t clear itself unless you tell it to. Even if you print on the same line, any text past what you print will remain. To fix this problem, we have to pad our string with spaces to overwrite any potential garbage. The easiest way to do this is to just tack on a few spaces to our prettyPrice variable:
String prettyPrice = "$" + String(price, 2) + " ";
With that change in place, we’ve got a proof of concept! Let’s gussy it up a bit.
Step 3: Code: Custom Characters
One of the coolest features of the LCD module we’re using is the ability to create up to 8 custom characters. This is done through the createChar() method. This method takes an array of 8×5 bits that describe which pixels of the LCD to turn on for the given character. There are a few tools online to help generating these arrays. I used this one.
If you’re not feeling particularly designerly, I recommend using the Threshold filter in Photoshop to turn an image into black-and-white, and converting that to characters. Remember that you have a maximum of 8 custom characters, or 64×5 pixels.
I opted for using 6 of those characters for the Amazon arrow logo, and the remaining 2 for a nicer trademark symbol. You can follow the CustomCharacter example in the Arduino IDE for how to use API. This is how I decided to group things:
// Define the data for the Trademark characters const size_t trademarkCharCount = 2;<br>const uint8_t trademarkChars[trademarkCharCount][8] = { { B00111, B00010, B00010, B00000, B00000, B00000, B00000, B00000 }, { B10100, B11100, B10100, B00000, B00000, B00000, B00000, B00000 } }; uint8_t firstTrademarkCharByte; // The byte used to print this character; assigned in initCustomChars()
Then I used a function like this, called from setup(), to create the characters:
void initCustomChars() { firstTrademarkCharByte = 0; for(size_t i = 0; i < trademarkCharCount; i++) { lcd.createChar(logoCharCount + i, (uint8_t *)trademarkChars[i]); } }
After that, printing the custom characters is as simple as using lcd.write() with the appropriate bytes. I wrote a helper function to print a range of bytes, and defined printTrademark() in terms of it:
void writeRawByteRange(uint8_t line, uint8_t col, uint8_t startValue, size_t numBytes) { for(uint8_t i = 0; i < numBytes; i++) { lcd.setCursor(col + i, line); // need to use write(), not print() - print will turn the integer // value into a string and print *that* lcd.write(startValue + i); } } void printTrademark(uint8_t line, uint8_t col) { writeRawByteRange(line, col, firstTrademarkCharByte, trademarkCharCount); }
The Amazon arrow logo was treated in a similar way. See the attached code for full details.
Source: Fake Dynamic Price Tag