Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Raspberry-Pi

How To Link A Cell Phone To A Raspberry Pi

4.95/5 (4 votes)
6 Feb 2017CPOL9 min read 38.9K   350  
How to set up a two-way interface between a remote browser-enabled device and a Raspberry Pi

Introduction

This piece shows how to set up a two-way interface between a remote browser-enabled device, such as a cell phone, and a Raspberry Pi. The example app is a simple, domestic environment, controller. The Pi sends temperature information to a subscriber’s browser and responds to requests from the browser by switching on or off remote radio controlled plug sockets. It’s written in node.js.

View

Configuring the Pi

The project uses a Raspberry Pi 3 running the latest version of Raspbian with internet enabled WiFi. You can use earlier versions of the Pi, however, the port pin numbers may not correspond to the ones mentioned here. The Pi needs to have a few interfacing connections enabled. This used to be a long and tedious business but it has now been greatly simplified. The easiest way is to connect the Pi’s HDMI cable to a monitor, attach an internet connected Ethernet cable, a usb keyboard and mouse and boot to Raspbian. From the raspberry icon on the top left of the screen, select preferences/raspberry Pi configuration/interfaces and enable SSH, 1-Wire and Remote GPIO, then close the pop up window. To enable WiFi, click on the up/ down arrows icon on the top right of the screen and follow the prompts . Remove the Ethernet connection, then, click on the command window icon and enter the command ‘hostname –I’. Make a note of the IP address that’s displayed, you'll need it to connect to the Pi. Then reboot the Pi (enter ‘reboot’).

Installing and Updating the Software

There are a couple of software packages that need to be installed. The first is the Node.js package manager npm. This manages the packages needed by an application. It uses a package.json file to maintain, load and update the required packages. The second is Mocha. Mocha is a useful node.js test framework, it facilitates the building and running of tests. Finally, the pre-installed software should be updated to the latest Pi compatible versions. The best way to do this is to is to update the package list, then upgrade to the latest versions detailed in the updated package list. Enter ’Ctrl V' to continue, if the installation stops after displaying a page of installation log details, enter 'q',when prompted, to return to the input terminal . Skiing off-piste by downloading direct from a provider’s web site is not recommended because of possible incompatibility issues -some of the packages are Pi specific. Here are the commands to enter.

C#
sudo apt-get update
sudo apt-get upgrade
sudo apt install npm
sudo npm install --global mocha

Setting Up the Server

The server needs to respond to both GET and PUT requests from the remote browser . The GET request simply uploads the HTML web page. PUT requests are sent when a button is pressed on the web page. The last word of the Url for the PUT request, relates to the name of the button. The GPIO port of the Pi is interfaced to the remote controller of the plug sockets so that, when the voltage level of a particular port pin changes in response to a button press, the remote control sends a radio pulse to the appropriate plug socket to switch it to either on or off.

C#
app.get('/', function (req, res) {
    res.sendFile(__dirname + '/Views/home.html');

});

// '/:pinName' traps  post requests to /alarm, /tv etc
//and places the last word in a req parameter 'pinName'
app.post('/:pinName', function (req, res) {
    var pinName = req.params.pinName;
    var portNumber = portDictionary[pinName];
    if (portNumber) {
        //The pin is pulsed asynchronously
        //but only one pin can be pulsed at a time
        //because the remote control can only send one
        //pulse at a time. So calls to this are queued.
        gpio.queuePulse(portNumber, pulseLength);
        //code 204 = No content
        res.status(204).end();
    }
    else {
        console.log("port requested is not found ");
        res.status(404).end();
    }
});

Updating the Web Page

The temperature displayed on the web page is updated every 5 seconds. This is achieved by utilising Server Sent Events (SSE). The client subscribes to the events by sending a GET request to the SSE end point. The server responds by establishing an open connection with the client and starts sending message packets containing the updated temperature. The client unwraps the messages and displays the updated information. When the client closes the web page, the request's Close event fires and server unsubscribes the client. Clients will automatically try to reconnect after about three seconds if their connection is unexpectedly terminated.

C#
//this is the server sent event socket
//the client calls this to both subscribe and unsubscribe
app.get('/sse', function (req, res) {
    if (req.headers.accept && req.headers.accept == 'text/event-stream') {
        //the following runs synchronously
        //when the client's close event is emitted (fired)
        req.on('close', function () {
            sse.removeSubscriber(res);
            res = null;
        });
        if (res != null) {
            sse.addSubscriber(req, res);
        }

    } else {
        res.writeHead(404);
        res.end();
    }
});

The addSubcriber function adds the subscriber’s response object to an array and starts the message pump, if necessary. The response object is actually a stream and has a constant reference value for a given connection, so it can be stored and reused. Messages need to be separated from each other by a pair of newline characters.

C#
function addSubscriber(req, res) {
  subscribers.push(res);
  res.writeHead(200,
    {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'
    }
  );
  //send new client's first message  immediately
  writeMessage(res);
  //if cancellationToken is null or undefined
  if (!cancellationToken) {
    //The message pump is not running so
    //start it.  The timer's interval  function
    //runs until cancelled
    cancellationToken = setInterval(function () {
      messageAllSubscribers();
    }, sendInterval);
  }
}

function messageAllSubscribers() {
  subscribers.forEach(function (subscriberRes) {
    writeMessage(subscriberRes);
  });
}

function writeMessage(res) {
  sensor.get(sensorId, function (err, temp) {
    if (err) {
      console.log(err);
      return;
    }
    res.write("data: " + temp + '\n\n');
  });
}

// The response object is actually a stream and has
// a constant reference value for a given connection.
// So it can be stored and reused.
function removeSubscriber(subscriberRes) {
  var i = subscribers.indexOf(subscriberRes);
  //if found
  if (i !== -1) {
    //remove from array
    subscribers.splice(i, 1);
  }
  //if no subscribers remain
  if (subscribers.length == 0 && cancellationToken) {
    //clear (stop) the interval timer
    clearInterval(cancellationToken);
    cancellationToken = null;
  }
}

Client-Side Code

All that’s necessary to process the server sent event is a few lines of JavaScript inside the HTML document.

C#
if(typeof(EventSource) !== "undefined") {
    var source = new EventSource("./sse");
    source.onmessage = function(event) {
        document.getElementById("result").innerHTML ="Temperature "+ event.data+" °C" ;
    };
} else {
    document.getElementById("result").innerHTML = "Unable to update the temperature";
}

There is also a function to send the POST request to the server. The button-click event calls it with the button’s id set as the input parameter.

C#
function postRequest(id) {
var x=  document.getElementById(id);
x.disabled = true;
//disable the button for a short time to avoid spurious posts
setTimeout(function(){ x.disabled=false }, 750);
var xhr = new XMLHttpRequest();
xhr.open('POST', "./"+id, true);
xhr.timeout = 2500; // time in milliseconds
xhr.send();
xhr.ontimeout = function (e) {
  // XMLHttpRequest timed out.
 alert("Request timed out");
};
}

Interfacing with the Pi’s GPIO Port

The general purpose input output port (GPIO) is controlled using the superb wiring-pi package.

C#
var wpi = require('wiring-pi');
//Associate pin numbers with their
//physical position on the chip
wpi.setup('phys');

function setGpioPins(portDictionary) {
  for (var key in portDictionary) {
    //configure all pins as outputs
    wpi.pinMode(portDictionary[key], wpi.OUTPUT);
  }
}

function pulsePin(pin, delay,cb) {
  wpi.digitalWrite(pin, 1);
  setTimeout(function () {
    wpi.digitalWrite(pin, 0);
    cb();
  }, delay);
}

Throttling Requests to the Output Pins

The remote control for the plug sockets can only switch one socket at a time, so it’s important that only one pin is pulsed at a time. This is achieved by using a synchronous queue where tasks are executed sequentially. When one asynchronous task completes, another starts. In this case, the task is the pulsing of the pins.

C#
module.exports = TaskQueue;
var taskWorker;
var queue = [];

function TaskQueue(worker) {
    taskWorker = worker;
    }

var processTask = function () {
    var taskParams = queue[0];
     taskWorker(taskParams, dequeue);
}
//This is passed to the taskWorker as
//its callback function.
var dequeue = function () {
    queue.splice(0, 1);
    if (queue.length > 0) {
        processTask();
    }
}

TaskQueue.prototype.queueTask = function (taskParams) {
    //Add task to queue
    var length = queue.push(taskParams);
    //if queue was empty
    if (length == 1) {
        processTask();
    }
    //else, wait for the current task to call dequeue
    //when it completes
}

Reading the Temperature Sensor

The sensor used is a DS18B20. This sends information as a package using a single data line that's connected to physical pin 7 on the Pi. Each sensor has a unique id and several can share the one port. The input pin should have its internal resistor set to ‘pull-up’. This results in the pin going to the high state when it is not being driven.

The package used to read the sensor is ds18x20. It’s very easy to use.

C#
var sensor = require('ds18x20');
//***need to add your own sensor's Id****
// The Id of the sensor can be found in  /sys/bus/w1/devices/ 
// The Id will start with  28- as in the example below
var sensorId = '28-000007d4684f';
function writeMessage(res) {
  sensor.get(sensorId, function (err, temp) {
    if (err) {
      console.log(err);
      return;
    }
    res.write("data: " + temp + '\n\n');
  });
}

Interfacing With the Remote Control

This is a bit off topic as it’s more about electronics than coding, so I’ve put the details in the README.html of the example app. Basically, what happens is the output pin of the Pi is connected to a simple transistor switch. When the pin goes high, the transistor switches on and shorts the same contacts that are usually connected by a button press on the remote. There’s no need for expensive relays as the switch only passes a few milliamps.

Testing

My preferred option is to carry out most of the development work and testing on a PC and then transfer the resulting files to the Pi. In order to do this, you need to download the following:

  1. A telnet client such as PuTTY so that the PC can act as a remote terminal for the Pi
  2. A file transfer utility like WinSCP. To transfer and synchronise files between Pi and PC
  3. A code editing and debugging platform. Visual Studio Code (vscode). Takes some beating.

When testing on a PC, it's a good idea to use mock versions of the modules that interface directly with the pi’s firmware. Trying to install wiring-pi on a PC will fail. The sample app is set up to run in debug mode and will use mock versions of 'wiring-pi' and 'ds18x20'. To run it hot on a Pi, change the "NODE_ENV" value in config.json from ‘debug’ to ‘production'.

Useful Hardware

An insulated case for the Pi is considered to be essential unless, of course, you wish to experience the blue-smoke farewell display. Another bit of kit worth getting hold of is a breakout ribbon cable and connector, known as a ‘cobbler’, to interface the Pi ports to a prototyping breadboard. Go for the fully assembled version. It’s not worth soldering the pins yourself, one ‘dry’ joint and it’s worse than useless. A very useful test instrument is the logic probe. These are cheap to buy and give an audiovisual indication of the logic state, either High or Low, on any port pin. They are also able to detect voltage pulses. You can use a multimeter as a replacement but, with digital circuits, the logic state is more important than the actual voltage reading.

Adding Raspberry Pi to a Network

In order to debug the Pi from a remote machine, you need to add the Pi to a shared network. To do this, install a utility called Samba then backup its samba.conf file and edit the original file as detailed below. The commands to use are:

sudo apt-get install samba samba-common-bin
cd /etc/samba
sudo cp smb.conf smb.backup
sudo nano smb.conf 

In the section [global]:

workgroup = WORKGROUP
wins support = yes

In section #=====Share Definitions=========
[homes]

readonly= yes 

Finally, copy and paste the following with the cursor positioned on a blank line immediately above the [printers] section:

[piPublic]
  comment= Pi Public
  path=/home/pi/Public
  browserable=Yes
  writeable=Yes
  guest ok=Yes
  create mask=0775
  directory mask=0775
  public=yes

Save the file and then set the password to the one that user 'pi' logs on with:

smbpasswd -a pi

Then reboot. You should see RASPBERRYPI listed on the network. The piPublic folder has read/write permissions.

Debugging with Visual Studio Code

Use the read/write network address when you first open a folder, "\\RASPBERRYPI\piPublic\foldername" so that the workspaceRoot variable is set correctly. It’s best to put break points inside the callback function when debugging http requests rather than at the beginning of the parent method. You also need to be aware of the possibility that the browser may time out as a result of the break point being hit.

If you launch an application from vscode, it will run on the local machine and not on the Pi. To debug remotely, you need to use the integrated node.js debugger. The procedure for adding break points is to simply add the key word debugger; in the source code where you want to break. You can then inspect the variables in vscode. This is how to set things up.

  1. Configure PuTTY to allow connections on port 5858. On PuTTY, select category/Connection/SSH/Tunnels. Tick Local ports accept connections from other hosts. Enter the source port as 5858 and the Destination as localhost:5858, then click Add.

    View

  2. In the Session window, enter the Pi's IP address. Make sure connection type SSH is selected. Name the session in the Saved Sessions box and save it. Double clicking on the saved session name will load it and open the terminal.

    View

  3. Navigate to the application's directory on the Pi using the network address "\\RASPBERRYPI\piPublic\HomeAuto" and enter 'node --debug index.js' to start the application. In vscode debug mode, select the 'Attach to Port' configuration from the drop down menu on the top left and then start debugging by pressing the arrow next to it. If there are no choices available in the dropdown list, the launch.json config file hasn’t been built yet; press the Debug button and follow the prompts to build it.

    View

Running the Application at Startup

This is just a matter of accessing the file /etc/rc.local as root.

sudo nano /etc/rc.local

Then, immediately above the last line, add the start command for the application. So the last two lines look something like this:

sudo node /home/pi/Public/HomeAuto/index.js
exit 0 

Conclusion

I hope this has been a useful introduction to configuring and programming the Raspberry Pi. You may like to have a go yourself, it's great fun and you can learn a lot.

History

  • 4th February, 2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)