Introduction: Fake Dynamic Price Tag

About: its my birfday

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 16x2 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 8x5 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 64x5 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.

Step 4: Code: Niceties

To make things a little easier on myself, I added a few niceties to the code. This includes things like: a function for clearing a specific line by overwriting it with spaces, and a function for centering a given string on a line.

I also wanted the display to cycle through three distinct phases:

  1. "Dynamic Pricing" with the logo below
  2. "by Amazon" with the logo below
  3. random price display

For that, I built a simple system that keeps track of how long a given phase has been active, and after a certain period, moves on to the next one.

See the attached code for all the gory details!

Step 5: The Box

Now, so we don't get the bomb squad called on us, let's make a nice box for the whole thing. We'll be doing this with laser-cut acrylic. There are a lot of online tools for jump-starting the process of making simple boxes. I recommend makercase.com, since it lets you specify the inner dimensions and accounts for the thickness of the material.

We measured the Arduino, LCD and 9V battery, and estimated that we'd be able to fit it in a case that was 4" x 2.5" x 2". So, we plugged those into makercase, with an 1/8" thick acrylic. We modified the resulting PDF to add a rounded window for the LCD, and a slot along the bottom for a display tag (more on that later). The resulting file is attached as a PDF.

We used acrylic adhesive (the toxic methyl ethyl ketone kind) to assemble four sides of the box. Then we attached the LCD panel to the front with hot glue. Once we had everything working and fitting, we sealed the last two sides of the box with hot glue, so that we could easily take it apart later. Since we weren't expecting the device to receive much wear-and-tear, we left the Arduino and battery unsecured on the bottom of the case.

Potential Improvements

  • We neglected to build in any way to turn the device on or off. Ha. Room for a switch on the bottom or back of the box would have been a good idea.
  • The slot along the bottom for the hanging tag could have been closer to the front of the box, for improved visibility.

Step 6: Blending In

And now, the hard part: sneaking it into a store.

Whole Foods branding

Some things we learned in reverse-engineering Whole Foods and Amazon branding:

  • Body text is generally in Scala Sans
  • Header text is in something that looks a lot like Brighton – one of those generic "warm and friendly" fonts
  • Whole Foods Green is something close to #223323
  • Stake out your local store for examples of graphic elements that repeat: they're fond of scalloped borders, sunbursts, and simple vector art.

The hanging tag

We cut a slit in the bottom of the acrylic case so that we could attach a hanging tag to the box, explaining what's going on. See the attached PDF for an example. This is designed to be cut out and inserted into the slot; it should fit and hold without any adhesive.

Shelving

As for actually attaching the box to a shelf, Whole Foods uses pretty standard shelving components. We took measurements and found a compatible hook in a hardware store. We affixed the box to the hook with hot glue.

If you can't find such a hook, you could try magnets – glue some to the back of the box, and just snap it onto a shelf.

Deploy!

Place the box at eye-level to attract the attention of passersby. Don't get caught! Best of luck!

Attachments