Using INI Files in Your Node.js Applications for Health and Profit

ini files
Think about your worst nightmare becoming reality: you are running a high-flying startup and launching a multi-billion-dollar online business, and betting the farm on a Node.js ecosystem and JSON configuration files. Your sysadmin is asked to temporarily comment out a portion of a JSON configuration file. He should know better, but in the heat of the moment, he forgets that JSON does not support comments. The configuration file is changed and saved, and the entire system crashes and burns🔥, shaking the trust of customers and diverting their allegiance to other competitors. Bankruptcy ensues.

Or how about that internal company developer who builds her configuration files using pure JavaScript? She makes that one last change to the production JS configuration file late Friday afternoon. Unknowingly, she accidentally deletes the closing quotation mark when modifying a property. Her phone ☎️ rings Friday night with weekend workers who can't get to the internal website since it is unavailable. She suffers a heart attack on-the-spot, resulting in a hospital stay for three weeks.

Could these incidents have been avoided? Yes, if INI configuration files had been used in these scenarios, health and wealth would have been the theme instead of (hospital) beds and bankruptcy. Friends don't let friends build Node.js apps without INI files.😜

Some have called the INI file the wonder drug of our time. Invented back in the 80s, we are seeing a resurgence of this popular format that once graced the boot.ini file in Windows before the boot.ini file was eliminated with the dawn of Windows Vista.

Article contents

What is the INI file format?

INI files utilize an extremely simple format for configuration files consisting of sections, properties, and values. Let's consider a sample INI configuration file for retrieving weather data:

; Weather configuration data
degreeType=F

[cities]
San Diego, CA
Chicago, IL
Tokyo, Japan

[fields]
temperature=Temp
skytext=Text
windspeed=Wind Speed

Comments begin with a semicolon as shown in line 1.

In line 2, we declare a property and its associated value. I did say this format was simple, didn't I?

Section declarations are surrounded by square brackets as shown in line 4. Sections enable us to combine similar configuration items together in their own groups.

Why use INI files for configuration?

I hope the introduction above has thoroughly convinced you of the inherent dangers of not using INI files.😄 Seriously, I am very much an advocate of JS and JSON configuration files; however, I also believe there are contexts where INI files can be extremely valuable too. Let's consider an identical modeling of our weather INI configuration above using a JSON format:

{
  "degreeType": "F",
  "cities": ["San Diego, CA", "Chicago, IL", "Tokyo, Japan"],
  "fields": [
    {
      "temperature": "Temp"
    },
    {
      "skytext": "Text"
    },
    {
      "windspeed": "Wind Speed"
    }
  ]
}

What if we want to update the configuration file temporarily so we don't receive Chicago data? This poses a challenge since JSON does not support comments. It looks like we'll need to copy and paste "Chicago" and move it into a temporary file, or create duplicate configuration files.

We could also employ a pure JavaScript configuration format instead:

module.exports = {
  degreeType: 'F',
  cities: ['San Diego, CA', 'Chicago, IL', 'Tokyo, Japan'],
  fields: [
    {
      temperature: 'Temp'
    },
    {
      skytext: 'Text'
    },
    {
      windspeed: 'Wind Speed'
    }
  ]
};

The situation has not improved much in terms of commenting out a city in the array. How about adding a city? We run the risk of forgetting a comma or failing to include an opening or closing quotation mark. Has this ever happened to you as a JavaScript developer? It has certainly happened to me.😢

Furthermore, what if we must rely on others who are less intimate with JavaScript to update our configuration files? We run a high risk of corrupting the JS/JSON configuration syntax, so the configuration file cannot be parsed. Although corruption is still possible with INI files, the likelihood is greatly reduced!

Oh, and let's not kid ourselves thinking XML configuration files will come save the day either:

<weather>
    <degreeType>F</degreeType>
    <cities>
        <city>San Diego, CA</city>
        <city>Chicago, IL</city>
        <city>Tokyo, Japan</city>
    </cities>
    <fields>
        <field>
            <name>temperature</name>
            <colName>Temp</colName>
        </field>
        <field>
            <name>temperature</name>
            <colName>Temp</colName>
        </field>
        <field>
            <name>skytext</name>
            <colName>Text</colName>
        </field>
        <field>
            <name>windspeed</name>
            <colName>Wind Speed</colName>
        </field>
    </fields>
</weather>

The angle bracket tax is very evident here.

Let's look again at our simple INI file and bask in the beauty of its simple, malleable syntax where items can easily be commented out and added:

; Weather configuration data
degreeType=F

[cities]
San Diego, CA
Chicago, IL
Tokyo, Japan

[fields]
temperature=Temp
skytext=Text
windspeed=Wind Speed

There are definite benefits with the INI file format! Let's get started using the INI syntax and write some Node.js code.

Initial Setup

To get started, we'll install the excellent ini package created by Isaac Schlueter, the inventor and CEO of NPM.

First, create a default package.json file, if you don't already have one:

$ npm init -y

Next, install the INI package and save it as a dependency in the package.json file:

$ npm install --save ini

Creating and reading our first INI file

We're planning to retrieve data from a weather service and we'll start by creating a configuration file we can use to specify if we want the temperature in degrees Fahrenheit or degrees Celsius. Let's call it config.ini:

; Weather configuration data
degreeType=F

Our very basic configuration file includes a comment and a property for degreeType.

Let's read and parse the INI configuration file and retrieve the value of degreeType:

const fs = require('fs');
const ini = require('ini');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));
const degreeType = config.degreeType;

console.log(degreeType);

We use the require function in lines 1-2 to import the fs and ini modules and make them available to our program.

In line 4, we read our INI file and parse it into an object named config.

Voilà! We now have a config object containing the information we stored in our configuration file. Retrieving the degreeType in line 5 is trivial.

Let's get the current weather!

Let's make it more interesting and use our INI configuration to retrieve the current weather. Here we go!

First, we'll get the weather-js module to help us:

$ npm install --save weather-js

Next, we'll create a file called weather.js to make our own weather module to use for the next exercises:

const weather = require('weather-js');

const getCurrent = (city, degreeType) =>
  new Promise((resolve, reject) => {
    weather.find({ search: city, degreeType }, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result[0].current);
      }
    });
  });
module.exports = getCurrent;

Notice how we create a Promise to retrieve the weather and return a JavaScript object containing the current weather data for a given city.

Using our same INI config file, let's test our function and ensure it works:

const fs = require('fs');
const ini = require('ini');
const weather = require('./weather');

const city = 'San Diego, CA';

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));
const degreeType = config.degreeType;

console.log(degreeType);

weather(city, degreeType)
  .then(result => console.log(result))
  .catch(error => console.log(error));

We hardcode the city to retrieve (San Diego) in line 5 and then invoke our newly created weather function in line 12. The following JavaScript object is returned:

{ temperature: '67',
  skycode: '30',
  skytext: 'Partly Sunny',
  date: '2018-01-12',
  observationtime: '14:20:00',
  observationpoint: 'San Diego, CA',
  feelslike: '67',
  humidity: '66',
  winddisplay: '6 mph Northwest',
  day: 'Friday',
  shortday: 'Fri',
  windspeed: '6 mph',
  imageUrl: 'http://blob.weather.microsoft.com/static/weather4/en-us/law/30.gif' }

Very good - this gives us a clear picture of the various properties we can display with our results.

As a bonus exercise, change the degreeType in the configuration file to "C" and rerun the program. You should see the temperature in degrees Celsius this time around!

Retrieve INI section data

Let's expand our INI file to include a section for cities:

; Weather configuration data
degreeType=F

[cities]
San Diego, CA
Chicago, IL
Tokyo, Japan

We can now get the cities from the INI configuration:

const fs = require('fs');
const ini = require('ini');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));

console.log(config.cities);

This program returns the following results:

{ 'San Diego, CA': true,
  'Chicago, IL': true,
  'Tokyo, Japan': true }

Very interesting! The INI file object returned is an object containing fields for each city under [cities], all set to a value of true.

We'd rather receive an array of cities back rather than an object. We can use the Object.keys method to accomplish this goal:

const cities = Object.keys(config.cities);

We'll revise our program to utilize the [cities] section data and return the weather for each city:

const fs = require('fs');
const ini = require('ini');
const weather = require('./weather');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));
const degreeType = config.degreeType;

const cities = Object.keys(config.cities);

for (const city of cities) {
  weather(city, degreeType)
    .then(current => {
      const temperature = current.temperature;
      console.log(`${city}\t${temperature} deg${degreeType}`);
    })
    .catch(error => console.log(error));
}

The program output will look something like this:

Tokyo, Japan    36 degF
Chicago, IL     21 degF
San Diego, CA   66 degF

Perfect. Let's expand our INI configuration file to include a list of fields to return from the Weather service:

; Weather configuration data
degreeType=F

[cities]
San Diego, CA
Chicago, IL
Tokyo, Japan

[fields]
temperature
skytext
windspeed

We can then modify our code to retrieve those specific field values for each city:

const fs = require('fs');
const ini = require('ini');
const weather = require('./weather');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));
const degreeType = config.degreeType;

const cities = Object.keys(config.cities);
const fields = Object.keys(config.fields);

for (const city of cities) {
  weather(city, degreeType)
    .then(current => {
      current.temperature += ` deg${degreeType}`;
      const data = fields.map(f => current[f]).join('\t');
      console.log(`${city}\t${data}`);
    })
    .catch(error => console.log(error));
}

Excellent! Our new output now looks like this:

Tokyo, Japan    36 degF 9 mph   Mostly Sunny
Chicago, IL     20 degF 22 mph  Light Snow
San Diego, CA   67 degF 8 mph   Partly Sunny

What if we don't want to see the Chicago data temporarily? We can simply update our INI file as follows:

; Weather configuration data
degreeType=F

[cities]
San Diego, CA
;Chicago, IL
Tokyo, Japan

[fields]
temperature
windspeed
skytext

Run the program again and you will no longer see the Chicago weather. That configuration file update was easy!

Retrieve fields and value from a section

We'd like to include custom heading titles for each of the weather fields returned from our cities. We'll start by updating the INI configuration file:

; Weather configuration data
degreeType=F

[cities]
San Diego, CA
Chicago, IL
Tokyo, Japan

[fields]
temperature=Temp
windspeed=Wind
skytext=Text

We are then able to use, for example, "Temp" as the column heading for the temperature field returned, etc. Here's how it's done:

const fs = require('fs');
const ini = require('ini');
const weather = require('./weather');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));
const degreeType = config.degreeType;

const cities = Object.keys(config.cities);
const fields = Object.keys(config.fields);

const fieldHeadings = fields.map(f => config.fields[f]).join('\t');
console.log(`City\t\t${fieldHeadings}`);

for (const city of cities) {
  weather(city, degreeType)
    .then(current => {
      current.temperature += ` deg${degreeType}`;
      const data = fields.map(f => current[f]).join('\t');
      console.log(`${city}\t${data}`);
    })
    .catch(error => console.log(error));
}

We use the fields array returned in line 9 to accomplish the magic. And...here are the results returned (including our custom heading names):

City            Temp    Wind    Text
San Diego, CA   67 degF 8 mph   Partly Sunny
Chicago, IL     20 degF 22 mph  Light Snow
Tokyo, Japan    36 degF 9 mph   Mostly Sunny

Write data to an INI file

While arguably not as useful, we are also able to write configuration data back to an INI file using the INI stringify method. Let's give it a try:

const fs = require('fs');
const ini = require('ini');
const weather = require('./weather');

const city = 'San Diego, CA';
const configOut = './weather-data.ini';

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));
const degreeType = config.degreeType;

weather(city, degreeType)
  .then(current => {
    const iniText = ini.stringify(current, { section: city });
    fs.writeFileSync(configOut, iniText);
    console.log(`Data written to ${configOut}`);
  })
  .catch(error => console.log(error));

We call the stringify method in the highlighted line above to marshal the current weather data into an INI format for San Diego. The second argument of the stringify function enables us to specify the name of the section in the INI file which will render as [San Diego, CA], in our case. The resulting INI file looks like this:

[San Diego, CA]
temperature=67
skycode=21
skytext=Partly Sunny
date=2018-01-13
observationtime=15:20:00
observationpoint=San Diego, CA
feelslike=67
humidity=67
winddisplay=8 mph Northwest
day=Saturday
shortday=Sat
windspeed=8 mph
imageUrl=http://blob.weather.microsoft.com/static/weather4/en-us/law/21.gif

It works!

Using arrays in INI files

To complete our tutorial, let's consider the different options we have available for modeling arrays in our INI configuration files. We'll use an array of baseball players from the days of yore to help us.

Method 1: Object.keys

Given this INI file:

[players]
Reggie Jackson
Joe Rudi
Sal Bando

We can utilize the Object.keys method we have already been using to retrieve a JavaScript array as follows:

const fs = require('fs');
const ini = require('ini');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'))

// Retrieve the array
const players = Object.keys(config.players);

console.log(players);

There are no real surprises here.

Method 2: Use the INI file array syntax

We can also use a specific INI array syntax to accomplish the goal by creating an INI file with these contents:

[team]
players[]=Reggie Jackson
players[]=Joe Rudi
players[]=Sal Bando

We are then able to retrieve the values from the players array with the following code:

const fs = require('fs');
const ini = require('ini');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));

const players = config.team.players;

console.log(players);

Once again, an array is returned. As the Perl hackers always say: TMTOWTD (There's More Than One Way to Do It). 😉

Bonus - Modeling more complex objects

We can get creative and model even more complex objects with the INI configuration syntax. We might quickly reach a point where it makes more sense to use JSON/JS, but let's keep having fun with INI files. 😎

Let's retrieve some additional information about each of our players using the vertical bar (|) character as a field delimiter.

[players]
;Name             Position         Batting Avg
Reggie Jackson  | Right Field   |         .253
Joe Rudi        | First Base    |         .278
Sal Bando       | Third Base    |         .230

This INI file looks very readable and clean. Let's parse it and create a JavaScript object:

const fs = require('fs');
const ini = require('ini');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));
const playersRaw = Object.keys(config.players);

function splitPlayer(player) {
  return player.split('|').map(i => i.trim());
}

const players = playersRaw.map(p => splitPlayer(p));

console.log(players);

We utilize our typical Object.keys method in line 5 to retrieve an array of each player.

We create a function in line 7 to split each line using the | character and trim the empty space. This function is called as we iterate through the array of players in line 11.

The resulting output looks like this:

[ [ 'Reggie Jackson', 'Right Field', '.253' ],
  [ 'Joe Rudi', 'First Base', '.278' ],
  [ 'Sal Bando', 'Third Base', '.230' ] ]

If we want to be even more concise, we can rewrite the code as follows and eliminate the splitPlayers function altogether:

const fs = require('fs');
const ini = require('ini');

const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'));
const playersRaw = Object.keys(config.players);

const players = playersRaw.map(p => p.split('|').map(i => i.trim()));

console.log(players);

INI files can be very versatile with a little bit of coaxing!

Conclusion

INI files provide a very simple and versatile format, and you are now equipped! Your call of action is to go find some contexts to use the INI file format in your Node.js applications. Health and profit awaits.😀

Additional articles

Guide to Using Redis with Node.js
Getting Started with YAML in Node.js
Making Interactive Node.js Console Apps That Listen for Keypress Events

Last updated Jan 23 2018

Follow @thisDaveJ (Dave Johnson) on Twitter to stay up to date with the latest tutorials and tech articles.

Share

Do I Need an Umbrella Today? Google Sheets and JavaScript to the Rescue!

gsheets-umbrellaOK, Google Sheets! Send me an email if I need an umbrella today.

Send an email using Google Sheets? Yes, that’s right! Sheets can do a lot more than add numbers and calculate your mortgage payments. In our last article, we learned how to consume JSON web data using Google Sheets and we built templates to display and sort weather forecast data. Today, we’ll build on what we covered last time and learn how to send email using Google Sheets based on the JSON data we retrieve.

Let’s get practical and build something useful while we’re at it. Have you ever found yourself outside somewhere and it starts to rain and you realize you forgot your umbrella? This has certainly happened to me! We have been experiencing drought conditions in Southern California for the last few years, and so I don’t always think about bringing an umbrella since rain is somewhat infrequent. (Thankfully, I’m hearing rain outside my window right now and we are receiving a lot more rain this year which is great!) Let’s use Google Sheets to improve our situational awareness by retrieving the weather forecast for the day and sending us an email before we leave in the morning if we should bring an umbrella. Let’s get started! Continue reading

Share

Consuming JSON Web Data Using Google Sheets

article image
I’ve said it before, and I’ll say it again.  JSON has become the lingua franca for exchanging data on the web, and we (as developers) need to know how to process JSON data received from RESTful web services so we can be positioned for the present and for the future.

My article on Consuming Node.js Microservices Created with stdlib provided a high-level tour covering several methods of parsing and processing JSON data. Last time, we focused on how to consume JSON data using curl and jq. Today, we’re zooming in and learning more about consuming Web API JSON data using Google Sheets. Once again, we’ll be consuming data from a microservice created with the excellent stdlib platform. Our learning, however, will be universally applicable for consuming Web API JSON data from any http endpoint. I am very impressed with the power and versatility of the Google Sheets platform for consuming JSON data (among many other things).  Let’s get started and experience it firsthand! Continue reading

Share

Consuming Web API JSON Data Using curl and jq

curl-jq

Hey everyone!  I decided to put a few extra batteries in the background color of the article image above. 🙂  I’m actually pretty charged up about our topic today, particularly about jq, which is a lightweight and flexible command-line JSON processor with “batteries included”.

JSON has become the lingua franca for exchanging data on the web, and we (as developers) need to know how to process JSON data so we can be positioned for the present and for the future. We recently learned about Consuming Node.js Microservices Created with Stdlib which provided a high-level tour covering several methods for parsing and processing JSON data. Today, we’re zooming in and learning more about consuming Web API JSON data from bash using curl and jq. Once again, we’ll be consuming data from a microservice created with the excellent stdlib platform. Our learning, however, will be universally applicable for consuming Web API JSON data from any http endpoint. Most importantly, we’re going to have fun in the process! 🙂 Continue reading

Share

Creating a Raspberry Pi Pandora Player with Remote Web Control

pi pandora
In our last article, we learned how to control a Raspberry Pi from a mobile device and, as a bonus, we created a menu to make it even easier to issue commands and control our Pi. We’re back today to put that remote menu to good use because we’re building an amazing Pandora music player we can connect to a stereo in our living room, for example, without a monitor, keyboard, or mouse. Our system will include a web interface served through Node.js so we can control our Pandora player remotely from a mobile phone—all from the comfort of our couch. 🙂 Follow along with me and let’s make this happen!

Article Contents

Complete Prerequisites

Prepare your Raspberry Pi

If you have not already followed my Beginner’s Guide to Installing Node.js on a Raspberry Pi, I recommend that you take a look at that now. In this guide, I equip you with the knowledge needed to build an awesome Raspberry Pi system that can also run in a headless mode. We’re able to avoid a dedicated monitor, keyboard, and mouse, and this is exactly what we need to maximize the awesomeness of our Pandora player.

At the very least, make sure you follow the steps to install Node.js because Node.js will be powering the built-in Pandora web server we’ll be accessing from our mobile devices.

Create Pandora account

If you do not already have a Pandora account, go ahead and visit Pandora.com and create one now. Pandora is “free personalized radio that plays the music you love”. What’s not to love about that? 🙂

While you are logged into Pandora, go ahead and create a couple of stations. Our remote web interface will ultimately need stations in order to to play music for us.

Install additional packages

We’ll need a couple of additional packages to utilize our Pandora player. First, let’s install pianobar, an amazing console-based pandora.com player underpinning our entire project:

$ sudo apt install pianobar

Next, we’ll install screen, a terminal-based window manager that our software will use behind the scenes in conjunction with pianobar:

$ sudo apt install screen

Install Patiobar

As a next step, we’ll install and configure Patiobar, a web frontend for pianobar created by Kyle Johnson. I was gearing up to create a web interface to pianobar and came across Kyle’s project. He has done an excellent job, and we’ll be leveraging his work to create this fantastic music system.

Go ahead and ensure your terminal session is in your home directory:

$ cd ~

Also, these very next steps require Node.js so make sure you have that installed.

Ok, let’s install Patiobar.  Execute this next set of commands one by one from the terminal:

$ git clone https://github.com/kylejohnson/Patiobar.git
$ cd Patiobar
$ bash install.sh

Excellent! We just made some serious progress and installed the foundational elements needed for our Patiobar web server.

Configure pianobar so it’s ready to be used by Patiobar

Our Patiobar web frontend ultimately uses pianobar behind the scenes to serve up music from Pandora so we need to make sure pianobar is configured and in working order first.

Launch the pianobar configuration file for editing using the Leafpad text editor:

$ leafpad ~/.config/pianobar/config &

The contents will look something like this:

user = you@user.com
user = user@example.com
password = password
#autostart_station = 123456
audio_quality = high
event_command = /home/pi/Patiobar/eventcmd.sh
fifo = /home/pi/Patiobar/ctl
tls_fingerprint = FC2E6AF49FC63AEDAD1078DC22D1185B809E7534

Substitute the user parameter with your Pandora user account email, and substitute the password with your Pandora password, and save your changes.

Great work! We’re making excellent progress and we’re ready to launch pianobar by itself to confirm it works. Connect a set of earbuds or speakers to the audio output jack of your Raspberry Pi. Get ready to hear some music!

Issue the following command to launch pianobar:

$ pianobar

At this point, pianobar should log into Pandora and prompt you to select a station for listening. Go ahead and select the default station that you would like to start each time the Raspberry Pi music player starts. You will be able to change this station from the web interface that is provided, but we need a starting station to bootstrap our player each time it starts. The pianobar station selection dialog will look something like this:

[?] Select station: 2
|> Station "David Nevue (Holiday) Radio" (3749270268933429865)

After selecting a station, hit Ctrl+C to quit. You will need the long station number listed in parentheses, 3749270268933429865, as shown in our example above, for the next step.

Edit the pianobar configuration file one more time:

$ leafpad ~/.config/pianobar/config &

Replace the existing autostart_station line with the long station number you obtained above. Be sure to remove the “#” from the beginning of the line so pianobar can read the autostart_station configuration information.

autostart_station = 3749270268933429865

Save your changes and launch pianobar again.

$ pianobar

This time, pianobar should jump right in and begin playing your default station without intervention required by you. Assuming this is the case, you are ready to finish the installation so we can use pianobar with the awesome Patiobar web frontend powered by Node.js.

Create Patiobar start and stop scripts

Okay, let’s create a couple of scripts to make it easy to start and stop Patiobar.

Create bin directory to make our menu command accessible from any directory

First, we will create a directory so our scripts will be accessible from any directory on our system without the need to provide a full path name to our script.

From the terminal, return to your home directory if you are not already there.

$ cd ~

Next, create a directory called bin:

$ mkdir bin

We will need to edit our .bashrc file to include this bin directory in our PATH variable. This will ensure that any scripts located in our newly created bin directory can be invoked from any directory on our system without needing to include the full path to the script. Let’s first launch a text editor so we can edit the .bashrc file:

$ leafpad .bashrc &

Add the following line to the bottom of this file:

export PATH="$HOME/bin:$PATH"

Save your changes and close the leafpad editor.

To ensure our changes to the .bashrc file are processed right away, invoke this command:

$ source .bashrc

Perfect! We are now positioned to create our Patiobar start and stop scripts.

Create pbstart (Patiobar start) command

We’ll use leafpad once again to create our new command. We’ll call our command pbstart (Patiobar start):

$ leafpad ~/bin/pbstart &

Add the following contents and save the file.

#!/bin/bash

cd /home/pi/Patiobar

# Kill any old Patiobar processes running
pbstop 1> /dev/null

screen -S pianobar -d -m bash -c 'pianobar'

# The "patiobar" in the end is not needed and is used as an identifier so we can kill it when stopping.
# Otherwise, we might kill other "node index.js" processes.
node index.js patiobar > /dev/null 2>&1 &

echo "Go to http://$(hostname):3000/ to launch the Web interface."

Create pbstop (Patiobar stop) command

Using leafpad once again, create a second command called pbstop (Patiobar stop):

$ leafpad ~/bin/pbstop &

Add the following contents and save the file.

#!/bin/bash

echo Closing out Pandora processes...
pkill -xf "SCREEN -S pianobar -d -m bash -c pianobar"
pkill -xf "node index.js patiobar"
echo done

Set user execute bit on the pbstart/stop scripts

Finally, set the user execute bit on both files so the pi user has permission to run the scripts:

$ chmod u+x ~/bin/pbstart
$ chmod u+x ~/bin/pbstop

Launch Patiobar and start listening to music!

Okay, here’s the moment of truth. Let’s try out our pbstart command and see if we can start up Patiobar. Remember that we can invoke this command from any directory on our system since it is part of our PATH. Here we go!

$ pbstart

Patiobar should start up pianobar, and you should both hear music and be able to navigate to the Patiobar web interface running on port 3000. For example, my Raspberry Pi host name is called “pandora” so I would navigate to http://pandora:3000. It’s December and I’m listening to Christmas music so this is what I see when launching the web interface from my mobile phone:
David Nevue
The interface is excellent and we can change stations using the “hamburger” menu in the top right, and pause, skip songs, etc. There’s even album art as you can see!

When you are done listening, stop the Patiobar player so you do not consume all your Pandora minutes:

$ pbstop

Install SSH client on mobile phone to control Pianobar start/stop remotely (optional)

We learned how to control a Raspberry Pi from a mobile device] in our last article and I encourage you to read that article to learn how to install an SSH client on your mobile device so you can invoke terminal commands from your phone. It’s pretty amazing and useful to not be tethered to a laptop or desktop and still log in and interact with your Pi!

After the mobile SSH client is installed you will be able to invoke pbstart from your mobile device to start Patiobar, stop Pianobar with pbstop, and safety shut down your Pi using the following command:

$ sudo poweroff

Utilizing a mobile SSH client is a much better option for shutting down your headless Pi gracefully rather than yanking the power cord out and risking potential corruption of your microSD card.

Add menu for remote control from mobile phone (optional)

To make it even easier to invoke commands, our last article on controlling a Raspberry Pi from a mobile device also included a bonus section on creating a menu system. You can follow the steps in that article to create a menu system that looks something like this for our current Pandora player project:
rmenu pandora
After following the steps in the controlling a Raspberry Pi from a mobile device article, you can create a file called menu2.sh in the same directory as the rmenu command like this:

$ leafpad ~/bin/menu2.sh &

…and add the following contents:

show_menu () {
    # We show the host name right in the menu title so we know which Pi we are connected to
    OPTION=$(whiptail --title "Menu (Host:$(hostname))" --menu "Choose your option:" 12 36 5 \
    "1" "Uptime" \
    "2" "Start Pandora" \
    "3" "Stop Pandora" \
    "4" "Reboot Pi" \
    "5" "Shut down Pi"  3>&1 1>&2 2>&3)
 
    BUTTON=$?

    # Check if user pressed cancel or escape
    if [[ ($BUTTON -eq 1) || ($BUTTON -eq 255) ]]; then
        exit 1
    fi

    if [ $BUTTON -eq 0 ]; then
        case $OPTION in
        1)
            MSG="$(uptime)"
            whiptail --title "Uptime info" --msgbox "$MSG" 8 36
            show_menu
            ;;
        2)
            pbstart
            msg="Go to http://$(hostname):3000/ to launch the Web interface."
            whiptail --title "Note" --msgbox "$msg" 8 36
            show_menu
            ;;
        3)
            pbstop
            sleep 2
            show_menu
            ;;
        4)
            confirmAnswer "Are you sure you want to reboot the Pi?"
            if [ $? = 0 ]; then
                echo Rebooting...
                sudo reboot
            else
                show_menu
            fi
            ;;
        5)
            confirmAnswer "Are you sure you want to shut down the Pi?"
            if [ $? = 0 ]; then
                echo Shutting down...
                sudo poweroff
            else
                show_menu
            fi
            ;;
        esac
    fi
}

This new menu (menu2.sh) can then be invoked from rmenu as follows:

$ rmenu -m 2

As a final step (as outlined in the previous article), you can add this rmenu -m 2 command to ~/.profile so the menu starts automatically when you SSH into the Pi from your mobile device.

Launch Patiobar automatically on startup (optional)

If you have a dedicated microSD card for your Pandora player, you might want to start Patiobar automatically each time you power on your Raspberry Pi. This makes it easier to, for example, power on a Pi connected to speakers in your living room without the need to SSH into the Pi upon boot to issue a pbstart command. The downside is that you might be playing music and burning Pandora time when you don’t even realize music is playing. 🙂

To start Patiobar automatically every time you boot, use sudo to edit the following file since this file requires root privileges:

$ sudo leafpad /etc/rc.local &

Add the following line just above the last line in the file with the contents of exit 0:

sleep 15 && sudo -iu pi /home/pi/bin/pbstart

The /etc/rc.local script runs every time the Pi boots up. We sleep for 15 seconds before invoking the pbstart command under the aegis of the pi user. The 15 second “nap” 🙂 gives time for the network to stabilize before the pbstart script connects to Pandora.com and begins to stream music.

Save the file and close leafpad. Finally, reboot your Pi.

$ sudo reboot

After the Pi finishes its boot cycle (and another 15 seconds after that), you should start hearing music streaming from Pandora automatically!

Troubleshooting in the future (if music does not play)

Sometime down the road, you may be faced with a situation where music does not play.  This could very well be caused by the fact that the “TLS fingerprint” associated with the Pandora website has expired. When Pandora updates their SSL certificates, a new TLS fingerprint is needed. Keep this in mind in the future when Patiobar stops working and you can’t figure out why. Revisit this section and update the TLS fingerprint as we will describe now.

First, run pianobar as a standalone program separate from Patiobar to verify this is the issue:

$ pianobar

If you do not hear music, but instead see an error message indicating a “TLS fingerprint mismatch”, proceed with the next steps to resolve this issue.

We first need to copy over a handy script included with the pianobar distribution to our current directory so we can retrieve the latest TLS fingerprint:

$ cp /usr/share/doc/pianobar/contrib/tls_fingerprint.sh .

Next, set the user execute bit to make sure we can run the script:

$ chmod u+x tls_fingerprint.sh

Go ahead and invoke this script to retrieve the TLS fingerprint:

$ bash tls_fingerprint.sh

You should see something like this: FC2E6AF49FC63AEDAD1078DC22D1185B809E7534

Next, edit the pianobar configuration file:

$ leafpad ~/.config/pianobar/config &

Replace the existing tls_fingerprint configuration parameter with the new TLS fingerprint obtained:

tls_fingerprint = FC2E6AF49FC63AEDAD1078DC22D1185B809E7534

Run pianobar to verify that it now works before issuing a pbstart to restart Patiobar:

$ pianobar

You should hear music playing and be back in business!

Conclusion

There you have it! We created an amazing Pandora player that can be controlled remotely from a mobile device. We can control our Pandora listening experience through a web interface. As a bonus, we can also start and stop our Pandora player through an SSH interface on a mobile device with a handy menu as well. We are ready to enjoy music with family and friends—and we learned some cool technology tricks in the process!

Follow @thisDaveJ (Dave Johnson) on Twitter to stay up to date with the latest tutorials and tech articles.

Additional articles

Controlling a Raspberry Pi from a Mobile Device with Bonus Menu Too
Beginner’s Guide to Installing Node.js on a Raspberry Pi
Connecting a Raspberry Pi Using an Ethernet Crossover Cable and Internet Connection Sharing
Upgrading to more recent versions of Node.js on the Raspberry Pi

Share

Controlling a Raspberry Pi from a Mobile Device with Bonus Menu Too

rmain main

In my Beginner’s Guide to Installing Node.js on a Raspberry Pi, I equipped you with the knowledge needed to build an awesome Raspberry Pi system that could also run in a headless mode. We’re able to avoid a dedicated monitor, keyboard, and mouse, and this opens a whole new world of possibilities!

This brings us to today’s scenario: you’ve deployed your headless Raspberry Pi in the living room and connected it to your speaker system, soaking in the full stereo sound of your favorite music using pianobar, the console-based Pandora player. It’s eventually time for bed and you’re tired. Should you yank the power cord on your Raspberry Pi and call it a night? Probably not – you might risk corrupting the microSD card. Should you walk upstairs and re-open your laptop so you can SSH into the Pi and safety shut it down? That’s a lot of work! Wouldn’t it be fantastic if you could connect to your Pi from the mobile phone sitting next to you and issue that shutdown -h now command? Controlling your Pi from a mobile device could be very useful in other contexts too beyond listening to music such as IoT applications, computer vision systems, Magic Mirrors, etc.

In this guide, I will teach you how to control your Raspberry Pi from a mobile device. As a bonus, we will create a menu application to make it easier to issue commands since typing complicated command-line syntax on a small screen can prove to be challenging! Let’s jump right in! Continue reading

Share

Consuming Node.js Microservices Created with Stdlib

stdlib-consumers
In our last article, we learned how to create Node.js microservices using Polybit’s stdlib platform. We created a fabulous (IMHO 🙂 ) GPS service that enabled us to retrieve the name of a city based on its GPS coordinates. Today, we’re going to learn how to consume data returned from this stdlib GPS microservice using several methods. While the information presented here is specific to consuming Polybit stdlib microservices, many aspects of this article will be generally applicable for consuming Web API JSON data from any http endpoint. Strap on your seatbelts as we embark on a whirlwind tour to learn about consuming JSON data from a variety of contexts…and I’m talking about some serious variety! Continue reading

Share

Node.js: Playing Sounds to Provide Notifications

playing sounds

In a previous tutorial, we learned how to send email notifications Using Nodemailer and Gmail. In today’s session, we will learn how to play sounds using Node.js. As a bonus, we will learn how to continue to play a sound until our notification has been acknowledged by pressing a key on the keyboard. How does that sound? 🙂 Enough bad puns! 🙂 Let’s get started! Continue reading

Share

Creating Node.js Microservices with Ease Using Stdlib

stdlibMicroservices and serverless architectures are all the rage in the software industry. After working with Polybit’s amazing stdlib platform, I am clearly seeing the value of this promising technology! Today, I will introduce you to stdlib. I encourage you to work alongside me as we leverage stdlib to build a microservice that we can consume in a variety of contexts. Let’s get started with this fabulous technology! Continue reading

Share