Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

RF Communication Using nrf24l01 Modules and Node.js

DZone's Guide to

RF Communication Using nrf24l01 Modules and Node.js

When it comes to communicating with your boards, radio is an option. Setting up RF communication between Arduinos and Raspberry Pis just takes a handy Node.js addon.

· IoT Zone
Free Resource

Learn how Bluetooth mesh helps you create industrial-grade networks. Download the mesh overview.

Recently, I started experimenting with radio communication with low cost nrf24L01 modules. These modules are very cheap compared to XBee modules, which I used earlier. With these nrf24 modules, we can enable wireless communication between Arduinos and Raspberry Pis very effectively and economically. For my experiment, I used two nrf24 modules, one connected to an Arduino Uno and another to a Raspberry Pi 1. Here are the pin connection details

Seq

NRF24L01

RPi

Arduino Uno

1

GND

25 (GND)

GND

2

VCC

17 (3.3v)

3.3v

3

CE

15

7

4

CSN

24

8

5

SCK

23

13

6

MOSI

19

11

7

MISO

21

12

8

IRQ


To the communication, I used the RF24Network library, which is very good and has good documentation. Also, it comes with examples for both Arduino and RPi. I didn’t write any code — I just used the examples and was able to see the communication working. Initially, I had some trouble, but by the end, everything worked well. I could see the data coming from the Arduino to my Pi.

My intention was to use these modules in my RPi and write code in Node.js. Unfortunately, there is no Node.js support for this library. So last night, I decided to write a Node.js addon for this C/C++ library. I didn’t have any experience in writing a Node.js addon, I just spent an hour understanding the Nan and creating very simple addons. Then I started writing the addon for RF24Network. This was the hard part — definitely harder than a simple hello world addon.

Node-gyp kept on failing when it tried to compile the RFNetwork modules. In my searches, I realized that node-gyp uses the make utility, and I needed to add the C/C++ files from that library. In the end, I could compile the node addon. See the binding.gyp file

{
    "targets": [
        {
            "target_name": "nrf24Node",
            "sources": [ 
                "nrf24Node.cc",
                "RF24/RF24.cpp", 
                "RF24/utility/RPi/spi.cpp",
                "RF24/utility/RPi/bcm2835.c",
                "RF24/utility/RPi/interrupt.cpp",
                "RF24Network/RF24Network.cpp",
                "RF24Network/Sync.cpp"
            ],
            "include_dirs": [
                "<!(node -e \"require('nan')\")",
                "RF24Network",
                "RF24"
            ],
            "link_settings": {
                "libraries": [
                    "-RF24",
                    "-RFNetwork"
                ]
            }   
        }
    ]
}

I should say that I am just a beginner at node-gyp and this binding.gyp might need some improvements. Anyway, with this GYP file, the compilation succeeded.

Next was creating the addon file. Here, I had to learn more about the data types of Nan and callbacks. I started with simple functions and compiled again, then moved onto the next one. I took more time in understanding callbacks, which allows the addon to call JavaScript callback functions. I also spent a lot of time understanding threading and creating a module for continuous listening of incoming messages and triggering the callback function — so that Node.js can process those incoming messages. I use libuv for threading. It seems easier to understand than Async worker modules in Nan.

I spent that whole night learning and writing and refactoring the addon. I finished the module by early morning. By that time, I could write a Node.js app and could listen to incoming messages.

Here is the sample code in Node.js to listen to and acknowledge the message back to the sender.

var rf24 = require('./build/Release/nrf24Node.node');
rf24.begin(90,00);
rf24.printDetails();
rf24.write(1,"Ack");

rf24.readAsync(function(from, data){
    console.log(from);
    console.log(data);
    rf24.write(from,"Ack");
});


process.on('SIGINT', exitHandler);

function exitHandler() {
    process.exit();
    rf24.close();
}

Here is the complete addon. The code is uploaded to GitHub with the steps to compile and use it in your own Node.js applications.

#include <nan.h>
#include <v8.h>
#include <RF24.h>
#include <RF24Network.h>
#include <iostream>
#include <ctime>
#include <stdio.h>
#include <time.h>
#include <string>
using namespace Nan;
using namespace v8;

RF24 radio(RPI_V2_GPIO_P1_15, BCM2835_SPI_CS0, BCM2835_SPI_SPEED_8MHZ);
RF24Network network(radio);

Nan::Callback *cbPeriodic;
uv_async_t* async;

struct payload_t {                  // Structure of our payload
  char msg[24];
};

struct payload_pi {                  
  uint16_t fromNode;
  char msg[24];
};

//--------------------------------------------------------------------------
//Below functions are just replica of RF24Network functions.
//No need to use these functions in you app.
NAN_METHOD(BeginRadio) {
  radio.begin();
}

NAN_METHOD(BeginNetwork){
  uint16_t channel = info[0]->Uint32Value();
  uint16_t thisNode = info[0]->Uint32Value();
  network.begin(channel,thisNode);
}

NAN_METHOD(Update) {
  network.update();
}

NAN_METHOD(Available) {
  v8::Local<v8::Boolean> status = Nan::New(network.available());
  info.GetReturnValue().Set(status);
}

NAN_METHOD(Read) {
  payload_t payload;
  RF24NetworkHeader header;
  network.read(header,&payload,sizeof(payload));
  info.GetReturnValue().Set(Nan::New(payload.msg).ToLocalChecked());
}
//--------------------------------------------------------------------------------

NAN_METHOD(Begin){
  if (info.Length() < 2)
      return Nan::ThrowTypeError("Should pass Channel and Node id");

  uint16_t channel = info[0]->Uint32Value();
  uint16_t thisNode = info[1]->Uint32Value();

    radio.begin();
    delay(5);
    network.begin(channel, thisNode);
}

NAN_METHOD(Write){
  if (info.Length() < 2)
      return Nan::ThrowTypeError("Should pass Receiver Node Id and Message");  

  uint16_t otherNode = info[0]->Uint32Value();
  v8::String::Utf8Value message(info[1]->ToString());
  std::string msg = std::string(*message);
  payload_t payload;
  strncpy(payload.msg, msg.c_str(),24);

  RF24NetworkHeader header(otherNode);
  bool ok = network.write(header,&payload, sizeof(payload));
  info.GetReturnValue().Set(ok);
}


void keepListen(void *arg) {
    while(1)
    {
        network.update();
        while (network.available()) { 
              RF24NetworkHeader header; 
               payload_t payload;
              network.read(header,&payload,sizeof(payload));

        payload_pi localPayload;
        localPayload.fromNode = header.from_node;
        strncpy(localPayload.msg, payload.msg, 24);
        async->data = (void *) &localPayload;
        uv_async_send(async);
        }
        delay(2000);
    }
}

void doCallback(uv_async_t *handle){
  payload_pi* p = (struct payload_pi*)handle->data;
  v8::Handle<v8::Value> argv[2] = {
      Nan::New(p->fromNode),
      Nan::New(p->msg).ToLocalChecked()
    };
  cbPeriodic->Call(2, argv);
}  

NAN_METHOD(ReadAsync){
  if (info.Length() <= 0)
    return Nan::ThrowTypeError("Should pass a callback function");
  if (info.Length() > 0 && !info[0]->IsFunction())
      return Nan::ThrowTypeError("Provided callback must be a function");

  cbPeriodic = new Nan::Callback(info[0].As<Function>());
  async = (uv_async_t*)malloc(sizeof(uv_async_t));
  uv_async_init(uv_default_loop(), async, doCallback);
  uv_thread_t id;
  uv_thread_create(&id, keepListen, NULL);
  uv_run(uv_default_loop(), UV_RUN_DEFAULT);
}

NAN_METHOD(PrintDetails) {
  radio.printDetails();
}

NAN_METHOD(Close){
  uv_close((uv_handle_t*) &async, NULL);
}


NAN_MODULE_INIT(Init){
    Nan::Set(target, New<String>("beginRadio").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(BeginRadio)).ToLocalChecked());

    Nan::Set(target, New<String>("beginNetwork").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(BeginNetwork)).ToLocalChecked());

    Nan::Set(target, New<String>("update").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(Update)).ToLocalChecked());

    Nan::Set(target, New<String>("printDetails").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(PrintDetails)).ToLocalChecked());

    Nan::Set(target, New<String>("available").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(Available)).ToLocalChecked());

    Nan::Set(target, New<String>("read").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(Read)).ToLocalChecked());         

    Nan::Set(target, New<String>("readAsync").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(ReadAsync)).ToLocalChecked());

    Nan::Set(target, New<String>("write").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(Write)).ToLocalChecked());        

    Nan::Set(target, New<String>("close").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(Close)).ToLocalChecked()); 

    Nan::Set(target, New<String>("begin").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(Begin)).ToLocalChecked());                           
}

NODE_MODULE(nrf24Node, Init)

All the credit goes to the developers of RF24 and RF24Network library, I just created an addon for the great library. Along the way I learned a lot and could finish the nodejs addon.

For a deeper look into Bluetooth mesh, check out this technical insight for developers.

Topics:
node.js ,rf communication ,arduino ,raspberry pi ,tutorial ,iot

Published at DZone with permission of Sony Arouje, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}