Manage a fleet of Electronic Shelve Labels

In the previous article we assembled an ESL prototype with ESP-12 and Firmata governed by RaspPi Zero W with Johnny Five. Lets continue and learn how we can connect numerous ESLs to our RaspPi hub. The deployment part via Isaax stays unchanged – we only add more labels and change the code to manage them. We’ll need to introduce a map with every label’s IP bound to the product’s ASIN id. The request we make to Amazon will be a bit extended, so that we have product title in response. We’ll also need to employ Johnny Five’s special Boards constructor for managing multiple boards. You can find the code for this article herehttps://github.com/yentsun/esl-example/tree/fleet A little glossary on terms I use in the article:

  • label – the ESL, which consists of a board and an LCD
  • board – an ESP device, running on Firmata as server
  • hub – a RaspPi Zero with Node.js and Johnny Five acting as client

The Map

I was hoping to find a way for some sort of label discovery so we could avoid listing every label’s IP in order to connect them, but, alas, I failed. It could probably be done with some other Firmata tool (or even non-Firmata, maybe MQTT-based), but since in our examples we use Johnny Five, the only way to connect to a board is by it’s IP… if you’re using Johnny Five as client. There is another option though: Johnny Five becomes the server and ESP+Firmata becomes a client. Then you’ll need to set up server IP and a dedicated port on a Firmata device and then provide that port opened on the Johnny Five machine acting as server. A port for each label? Don’t know if there is any benefit in it, but here we have some alternative. It seems to me that no matter how we connect out labels to the hub – we won’t get away without binding each label ID to the product ID it displays. So lets stick to our initial design and just map label IP’s to product ID’s: map.json:
{
  "192.168.0.108": "B01DFKC2SO",
  "192.168.0.103": "B075RWFCHB",
  "192.168.0.107": "B073SQYXTW"
}
IMPORTANT: Don’t forget to reserve those IPs for the labels in your network router!

Extend The Request

In our prototype we only requested product’s Offers from Amazon. Since we have numerous labels now, we don’t want to hardcode the product’s titles for each label in our map – we want to request them as well:
const results = await client.itemLookup({
    idType: 'ASIN',
    itemId,
    responseGroup: 'ItemAttributes,OfferSummary'  // <-- notice the new 'ItemAttributes' group
});
Unpack it from response and display:
const title = results[0].ItemAttributes[0].Title[0];
...
lcd.cursor(0, 0).print(title)

Introduce five.Boards

The prototype’s single label has been initialized with five.Board constructor:
const amazonEchoLabel = new five.Board({
    port: new EtherPortClient({
        host: '192.168.0.107', // IP address of the ESP
        port: 3030
    }),
    timeout: 1e5,
    repl: false
});
Its probably natural to assume that to init multiple boards you just need multiple const board = new five.Board() calls… No! It took me some time to realize this won’t work and even more time to find the solution. Enter five.Boards – a special constructor to initialize multiple boards. First, lets use our map bindings to create a collection of board configs:
const MAP = require('./map');
const boards = [];
Object.keys(MAP).map((host) => {
    const asin = MAP[host];
    boards.push({
        id: asin,  // <-- we'll use product's ASIN as board id
        port: new EtherPortClient({host, port: 3030}),
        repl: false
    });
});
Next, we create a boards collection instance:
const labels = new five.Boards(boards);
Finally, wait for all of the labels to initialize and launch the request/display routine for each of them:
const requestAndDisplay = require('./requestAndDisplay');
labels.on('ready', function () {
    const boards = this;
    boards.each(async (board) => {
        const lcd = new five.LCD({controller: "PCF8574AT", board});
        await requestAndDisplay(board.id, lcd);  // initial display
        const loop = new InfiniteLoop();
        loop // looped update
           .add(requestAndDisplay, board.id, lcd)
           .setInterval(Number(INTERVAL))
           .run();
    });
});

The Result

Let’s see what we have: A set of price labels for some devices from the Amazon Echo family! As expected, long titles do not fit into 16 char LCD, so you’ll have to find a workaround – an LCD with autoscroll (I couldn’t get LCD.autoscroll() to work on mine), a bigger LCD, some advanced title string processing, etc]]>

上部へスクロール