npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

lorano

v1.0.2

Published

Compact and opinionated LoRa communications library

Downloads

25

Readme

High-level Node.js module for reading and writing LoRa packets, with support for over-the-air activation (OTAA) of devices.

It uses streams supplied by node-lora-comms and Anthony Kirby’s excellent packet decoder.

Your application is given a single Duplex stream operating in object mode for reading and writing packets. Replying to a packet typically involves just supplying a payload, though your application can override the default transmission settings if required.

Tested on a Raspberry Pi 3 Model B with an IMST iC880A-SPI.

API documentation is available here.

Example

This program works in conjunction with a corresponding Arduino sketch (tested on a SODAQ Explorer).

It reads 12-byte packets from the LoRa radio, leaves the first 6 bytes unchanged and randomizes the last 6 bytes. It then sends the packet back to the radio. The Explorer does the same but randomizes the first 6 byes it receives, leaving the last 6 bytes unchanged.

Each side then checks whether it gets back the bytes it randomized in the previous packet it sent.

example.js.

const path = require('path');
const crypto = require('crypto');
const { Link } = require('lorano');
const lora_comms = require('lora-comms');
const { Model } = require('objection');

// Start radio
process.on('SIGINT', lora_comms.stop);
lora_comms.start_logging();
lora_comms.log_info.pipe(process.stdout);
lora_comms.log_error.pipe(process.stderr);
lora_comms.start();

// Connect to database
const knex = require('knex')({
    client: 'sqlite3',
    useNullAsDefault: true,
    connection: {
        filename: path.join(__dirname, 'lorano.sqlite3')
    }
});
Model.knex(knex);
lora_comms.on('stop', () => knex.destroy());

const link = new Link(Model, lora_comms.uplink, lora_comms.downlink, {
    // USE YOUR OWN IDS!
    appid: Buffer.alloc(8),
    netid: crypto.randomBytes(3) // 7 lsb = NwkId
});

link.on('ready', async () =>
{
    // Add device (usually you'll seed the database as a separate task)
    const nwk_addr = crypto.randomBytes(4); // 25 lsb
    nwk_addr[0] &= 0x01; // 7 msb must be 0
    await knex('OTAADevices').insert({
        // USE YOUR OWN VALUES!
        NwkAddr: nwk_addr,
        DevEUI: Buffer.from('0000000000000000', 'hex'),
        AppKey: Buffer.alloc(16)
    });

    // Receive and send packets until we get a match

    const duplex = require('awaitify-stream').createDuplexer(link);
    const payload_size = 12;
    let send_payload = crypto.randomBytes(payload_size);

    while (true) {
        const recv_data = await duplex.readAsync();
        if (recv_data === null) {
            return;
        }
        if (recv_data.payload.length !== payload_size) {
            continue;
        }
        if (recv_data.payload.equals(send_payload)) {
            // Shouldn't happen because send on reverse polarity
            console.error('ERROR: Received packet we sent');
            continue;
        }

        if (recv_data.payload.compare(send_payload,
                                      payload_size/2,
                                      payload_size,
                                      payload_size/2,
                                      payload_size) === 0) {
            console.log('SUCCESS: Received matching data');
            return lora_comms.stop();
        }

        send_payload = Buffer.concat([recv_data.payload.slice(0, payload_size/2),
                                      crypto.randomBytes(payload_size/2)]);
        recv_data.reply.payload = send_payload;
        await duplex.writeAsync(recv_data.reply);
    }
});

First you need to modify at least DevEUI with the unique identifier of your device. You can then run the example like this:

grunt example

Installation

npm install lorano

Database

In the top-level directory you’ll find a file called lorano.empty.sqlite3. This contains an empty copy of the database lorano uses to store information about your devices (addresses, keys, activation state etc).

You should use a copy of this file in your application and pass its location when setting up Knex.js. By default the database is SQLite but Knex.js supports many other databases. If you want to use a different database, use the lorano migrations to populate it and edit knexfile.js accordingly.

Seeding device data

You need to add your devices to the database otherwise lorano will ignore packets it receives from them.

Over-The-Air Activation (OTAA) devices

For each of your devices, you must add a row to the OTAADevices table containing the following columns:

  • NwkAddr: Unique network address of the device. This can be random as long as it’s unique. It must be a binary value 4 bytes long but only the 25 least significant bits are used and the 7 most significant must be 0.

  • DevEUI: Unique global device ID in the IEEE EUI64 address space. This must be a binary value 8 bytes long and can usually be quered from the hardware by calling a device API.

  • AppKey: The AES-128 key that you’ve assigned to the device for your application. This is a shared secret between your application and the device and is used to derive session keys specific for the device to encrypt and verify packets communication with it. It must be a binary value 16 bytes long.

An example of seeding the database for an OTAA device using Knex.js can be found in test/seeds/test_otaa.js.

Activation By Personalization (ABP) devices

For each of your devices, you must add a row to the ABPDevices table containing the following columns:

  • DevAddr: The identifier of your network (7 most significant bits) and the unique address of the device within it (25 least significant bits). This must be a binary value 4 bytes long.

  • NwkSKey: Network session key for the device. This is a shared secret between your application and the device and is used to calculate and verify the message integrity code of data messages. It must be a binary value 16 bytes long.

  • AppSKey: Application session key for the device. This is a shared secret between your application and the device and it used to encrypt and decrypt data messages. It must be a binary value 16 bytes long.

An example of seeding the database for an ABP device using Knex.js can be found in test/seeds/test_abp.js.

IMST iC880A-SPI reset

If you’re using an IMST iC880A-SPI, it needs to be reset after it’s powered up.

My iC880A-SPI is connected to a Pi via a backplane which brings the reset line out on GPIO 25. I run the following shell script to perform the reset:

iC880A-SPI_reset.sh.

#!/bin/sh
echo "25" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio25/direction
echo "1" > /sys/class/gpio/gpio25/value
sleep 5
echo "0" > /sys/class/gpio/gpio25/value
sleep 1
echo "0" > /sys/class/gpio/gpio25/value
chmod o+rw /dev/spidev0.0

Test

By default, the tests simulate LoRa packets and can be run with:

grunt test

If you have a LoRa device that can run test/lorano_test.ino then you can pass its DEVEUI as an argument like this:

grunt test --deveui=XXXXXXXXXXXXXXXX

I’ve tested this with a SODAQ Explorer.

Lint

grunt lint

Coverage

grunt coverage

Or with a LoRa device running test/lorano_test.ino:

grunt coverage --deveui=XXXXXXXXXXXXXXXX

c8 results are available here.

Licence

MIT