In the previous article we learned how to make an LCD watch with NanoPi, Node-RED, ESP+Firmata and deploy it with Isaax. Now let’s imagine you are running a highly modernized shop where you want to display price tags on LCDs.ESL is a well-established technology but it usually relies on RF transmission and is rather pricey ($10+) display modules. Lets try to cut costs and make an alternative prototype using some cheap LCD screens, ESP + Firmata ($6 approx.), RaspPi Zero W as the “AP base” and, again, Isaax for deployment.

Here is what a typical ESL system looks like:

We shall omit the head office (Amazon will play this role for us), multiple stores and checkout terminals and focus on the AP base (our RaspPi Zero) and the labels themselves (LCDs+ESP). The AP base will periodically request Amazon Product API and transmit product prices in multiple currencies to the labels via Firmata.

Price fetch and currency conversion

Lets set up a Node.js service which will request Amazon Product API every hour and also convert its USD price to JPY and EUR, again, requesting the fixer.io currency exchange API:

const amazon = require('amazon-product-api');
const fixer = require('fixer-io-node');
const InfiniteLoop = require('infinite-loop');

const {AWS_ID: awsId, AWS_SECRET: awsSecret, AWS_TAG: awsTag, INTERVAL=1000*60*60} = 
process.env;

const client = amazon.createClient({awsId, awsSecret, awsTag});
const loop = new InfiniteLoop();

async function fetchPrice(client) {
    try {
        console.log('requesting product price...');
        const results = await client.itemLookup({
            idType: 'ASIN',
            itemId: 'B075PRXBH2',
            responseGroup: 'Offers'
        });
        const USD = Number(results[0].OfferSummary[0].LowestNewPrice[0].Amount[0]) / 100;
        console.log(`lowest new price is $${USD}`);
        const {rates: {JPY, EUR}} = await fixer.base('USD');
        console.log('JPY price is', Number.parseInt(USD * JPY));
        console.log('EUR price is', Number.parseInt(USD * EUR));
    } catch (error) {
        throw error;
    }
}

loop
    .add(fetchPrice, client)
    .setInterval(INTERVAL)
    .run();

This code is not sending any prices to our labels yet – it just receives a single product’s price in USD and converts it to JPY. I chose Raspberry Pi Zero Barebones Kit (Argon40) as the product for our experiment.

UPDATE: Since Raspberry Pi Zero Barebones Kit happened to be very popular – the seller quickly ran out of stock. I’ve changed our test product to Alexa Echo Dot (2nd Generation), ASIN B01DFKC2SO – hopefully Amazon has a plentiful supply of them..

I was lucky enough to find an npm package for everything we needed:

Feel the power of NPM!

Broadcasting to price labels

Now lets change our code so it can send the price data to our ESP + Firmata label:

...
const five = require('johnny-five');
const EtherPortClient = require('etherport-client').EtherPortClient;

const amazonEchoLabel = new five.Board({
    port: new EtherPortClient({
        host: '192.168.0.108', // IP address of the ESP
        port: 3030
    }),
    timeout: 1e5,
    repl: false
});

...

// we send data only after our label has initialized
amazonEchoLabel.on('ready', () => {
    console.log('Amazon Echo label ready');
    const lcd = new five.LCD({
        controller: "PCF8574AT"
    });
    fetchPrice(client, lcd);
    mainLoop.add(fetchPrice, client, lcd).setInterval(INTERVAL).run();
});

Also our fetchPrice function becomes displayPrice as now it actually displays the product name and the price on the LCD:

async function displayPrice(client, lcd) {

    try {
        console.log('requesting product price...');
        const results = await client.itemLookup({
            idType: 'ASIN',
            itemId: 'B01DFKC2SO',
            responseGroup: 'Offers'
        });
        const USDPrice = Number(results[0].OfferSummary[0].LowestNewPrice[0].Amount[0]) / 100;
        console.log(`lowest new price is $${USDPrice}`);
        const {rates: {JPY}} = await fixer.base('USD');  // we fetch and display JPY price for this cluster
        const JPYPrice = Number.parseInt(USDPrice * JPY);
        lcd
            .cursor(0, 0)
            .print('Alexa Echo Dot 2')
            .cursor(1, 0)
            .print(`$${USDPrice}`)
            .cursor(1, 6)
            .print(` | JPY${JPYPrice}    `);
    } catch (error) {
        throw error;
    }
}

As you can see we display the price in USD and JPY. Too easy! Lets imagine we have two departments in our shop – one with prices in JPY and the other one – with prices in EUR.

Isaax can deploy different branches on different clusters – thus we can run JPY-version on one cluster and EUR – on the other.

For that we need to provide two branches jpy and eur. Oops, forgot to mention – a sample repository can be found here: https://github.com/yentsun/esl-example, with both branches already set up.

Deploying with ISAAX

Okay, we have our JPY-label code separated from EUR-label one and we are ready to set up the two clusters. But before that, we need to setup our Amazon. Associate credentials as environment variables to be able to call Amazon Product API. Remember this line?

const {AWS_ID: awsId, AWS_SECRET: awsSecret, AWS_TAG: awsTag} = process.env;

Right! Time to set those envars up. Its pretty trivial with Isaax – just open your project panel, navigate to the ENVIRONMENT VARIABLES tab and add the variables like so:

These variables are project-wide, so they’ll work for both of our clusters. Make sure you selected the correct branch for each cluster:

See it in action

Now, if everything went well we should have two prototype price labels for the Alexa Echo Dot 2item from Amazon, one with euro price:

And one with yen:

All connected to Amazon API with a dynamically calculated price in different currencies!

In the next article we’ll attempt to deploy and manage a massive fleet of these labels, just like in a real store.

WRITTEN BY: Max Korinets. Max is an IoT developer and evangelist at isaax.io.


Tomoyuki Sugita

Chief Product Officer of XSHELL Inc.

Leave a Reply

Your email address will not be published. Required fields are marked *