Using RxJS to improve the code in displaying the current time in an LCD

In the previous article you saw how we can use NodeJS and a Raspberry Pi to display the current time along with extra text through an 16x2 LCD.

But there’s something that bothers me quite a bit.

Its on the readability of the code.

When you get inside the event handler on the lcd the levels of nesting go deep starting with setInterval().

const Lcd = require('lcd');
const moment = require('moment')
const lcd = new Lcd({ rs: 25, e: 24, data: [23, 17, 21, 22], cols: 16, rows: 2})

lcd.on('ready', () => {
    setInterval(() => { 
    lcd.setCursor(0, 0);
    lcd.print(moment().format('h:mm:ss a'), (err) => {
        if (err) {
            throw err;
        }
     });
    }, 1000);
});

process.on('SIGINT', () => {
  lcd.close();
  process.exit();
});

There’s probably a better way to do this.

That’s where RxJS comes in. It is a functional reactive library for dealing with streams, events and cases with time based logic such as this with Observables and Higher Order Functions.

It allows you to compose such operations and error-handling in a declarative manner.

This isn’t a monad tutorial but RxJS Observables are monads. A Superset of the IO Monad specifically because time is a first class citizen. One key takeaway that Monads give you is composability and the power to do Railway Oriented Programming.

Observable types are sequences so you need an Observable to start the sequence.

The first of the operations is the lcd.on('ready', cb). Based on its signature the lcd module is an EventEmitter.

In order to turn that into an Observable we need the fromEvent function from rxjs.

const onLcdReady = fromEvent(lcd, 'ready')

To provide 1 second intervals we use the interval function from rxjs to provide interval behavior

const everySecond = interval(1000)

Then to show the current time we need to wrap it in a Observable

mergeMap(() => of(moment().format('h:mm:ss a'))) 

I kind of did that inside of the lift function since defining the moment().format('h:mm:ss a') as a const would net me the same time when it was created. Simply put the time won’t update.

To weave them all together we use mergeMap:

onLcdReady.pipe(
  mergeMap(() => everySecond),
  mergeMap(() => of(moment().format('h:mm:ss a'))))

This code returns an Observable that emits the current time every second. By itself this won’t do anything since we haven’t called subscribe yet.

So the full code:

Once we call subscribe the functions inside the mergeMaps are executed every second due to the interval function. The raw time is passed to the subscribe function to be shown through to the 16x2 LCD.

Then run it using npm.

As you can see its now more concise. This is why I love declarative programming than imperative programming since it reads better. (In this case, it reads better)

Caveat

It does fall short when you want to display two lines. You have to put the second print method inside the first’s callback.

Because of how the lcd’s API is designed. Once you call setCursor() it clears up the lcd display asynchronously and was designed to be like that for optimization purposes. See this test for more information.

So right now I’m looking to transfer to a Framework called Johnny Five since its APIs are more high level than the ones provided in lcd.

Comments