llrpjs
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.1 • Public • Published

    llrpjs

    Low Level Reader Protocol (LLRP) library for node.


    Synopsis & Motivation

    LLRP (Low Level Reader Protocol) is a binary protocol published by EPCglobal Inc. to define the standard communication interface between RFID readers (servers) and clients. For more infomration, check out the LLRP standard definition.

    The LLRP ToolKit (LTK) project was created back in 2008 by a group of companies and universities to help the development of LLRP-based applications. The toolkit provided support for many languages including C, C++, Java, Perl, etc. A year later, however, Node.js came around and opened the door to countless possibilities and proved to be one of the easiest and fastest ways to IoT development.

    LLRP makes the perfect use case for JavaScript, given that it's a message-oriented protocol with many messages that are best handled asynchronously. See reader reports handling in the example below.

    Example

    llrpjs helps create programs for both servers (readers) and clients. The following example shows how to connect to a reader and collect EPC data of RFID tags.

    const { LLRPClient, LLRPCore } = require("llrpjs");
    const ADD_ROSPEC = require("./ADD_ROSPEC.json");
    
    // create a client
    const reader = new LLRPClient({ host: "192.168.7.2" });
    
    // print RFID tags
    reader.on("RO_ACCESS_REPORT", msg => {
        let tagReportDataList = msg.getTagReportData();
        if (!Array.isArray(tagReportDataList)) {
            tagReportDataList = [tagReportDataList]
        }
        for (let tagReportData of tagReportDataList) {
            let epc = tagReportData.getEPCParameter();
            console.log(`EPC: ${epc.getEPC()}`);
        }
    });
    reader.on("READER_EVENT_NOTIFICATION", msg => {
        // handle reader event notification messages here
    });
    
    reader.on("error", err => {
        // handle errors
    });
    reader.on("connect", () => {
        console.log("connected");
    })
    reader.on("disconnect", () => {
        console.log("disconnected");
    });
    
    /** Main */
    (async () => {
        try {
            // connect to reader
            await reader.connect();
            // wait for connection confirmation
            await checkConnectionStatus();
    
            // delete all existing ROSpecs (if any)
            await reader.transact(new LLRPCore.DELETE_ROSPEC({
                data: { ROSpecID: 0 /** all */ }
            }));
    
            // factory reset
            await reader.transact(new LLRPCore.SET_READER_CONFIG({
                data: { ResetToFactoryDefault: true }
            }));
    
            // add ROSpec defined in "./ADD_ROSPEC.json"
            await reader.transact(new LLRPCore.ADD_ROSPEC(ADD_ROSPEC));
    
            // enable ROSpec
            const { ROSpecID } = ADD_ROSPEC.data.ROSpec;
            await reader.transact(new LLRPCore.ENABLE_ROSPEC({
                data: { ROSpecID }
            }));
    
            // start ROSpec
            await reader.transact(new LLRPCore.START_ROSPEC({
                data: { ROSpecID }
            }));
    
            console.log("Press ctrl+c to exit");
        } catch (e) {
            console.error(e);
            process.exit(1);
        }
    })();
    
    /**
     * wait for a READER_EVENT_NOTIFICATION message
     */
    const checkConnectionStatus = async () => {
        let msg = await reader.recv(7000);
        if (!(msg instanceof LLRPCore.READER_EVENT_NOTIFICATION)) {
            throw new Error(`connection status check failed - unexpected message ${msg.getName()}`);
        }
        const status = msg.getReaderEventNotificationData().getConnectionAttemptEvent()?.getStatus();
        if (status != "Success") {
            throw new Error(`connection status check failed ${status}`);
        }
        return;
    }
    
    process.on("SIGINT", async () => {
        // request termination
        const rsp = await reader.transact(new LLRPCore.CLOSE_CONNECTION);
        if (rsp instanceof LLRPCore.CLOSE_CONNECTION_RESPONSE) {
            const status = rsp.getLLRPStatus();
            console.log(`${status.getErrorDescription()} - ${status.getStatusCode()}`)
        }
        // make sure it's disconnected
        if (reader.socketWritable) await reader.disconnect();
    });

    Static typing

    llrpjs is written in TypeScript and provides static typing support:

    JSON and JSON Schema support

    llrpjs commits to the same goals set by the LTK project. However, llrpjs attempts at accomplishing some of these goals differently:

    JSON over XML

    The LTK project used XML to represent human-readable LLRP documents. However, given that JSON has out-of-the-box support in Node.js as well as many APIs and document-oriented databases (e.g. MongoDB), it makes sense to use it over XML in llrpjs. llrpjs uses a one-to-one mapping of the same XML format used in LTK:

    llrpjs JSON (full example)

    {
      "$schema": "https://llrpjs.github.io/schema/core/encoding/json/1.0/llrp-1x0.schema.json",
      "id": 103, "type": "ADD_ROSPEC",
      "data": {
        "ROSpec": {
          "ROSpecID": 1,
          "Priority": 0,
          "CurrentState": "Disabled",
          // ...
        }
      }
    }

    LTK XML (full example)

    <ADD_ROSPEC MessageID='103'
      xmlns:llrp='http://www.llrp.org/ltk/schema/core/encoding/xml/1.0'
      xmlns='http://www.llrp.org/ltk/schema/core/encoding/xml/1.0'>
      <ROSpec>
        <ROSpecID>1</ROSpecID>
        <Priority>0</Priority>
        <CurrentState>Disabled</CurrentState>
        <!-- ... -->
      </ROSpec>
    </ADD_ROSPEC>

    JSON Schema over XSD

    The LLRP JSON schema is generated from the source LTK definition and supports all types and formats. VSCode supports JSON schema out of the box:

    CLI Tools

    On installation, the package makes two CLI programs available: llrp2json and json2llrp. These commands can be used to convert binary llrp messages to JSON and the other way around. Use --help flag to get information on how to use.

    Development

    llrpjs uses the same definition llrp-1x0-def.xml set by the LTK project to generate runtime type definitions and schema. This library is written in TypeScript and allows dynamic static typing using a given LLRP definition. That said, llrpjs in itself is definition-agnostic, meaning that the code itself only conforms to the meta schema that was set by the LTK project (llrpdef.xsd) and any definition that complies with the meta schema can be used to generate types for llrpjs.

    Check out llrpjs-tools for more information.

    Author

    Haytham Halimeh

    Contributing

    Contributions, issues and feature requests are welcome!
    Feel free to check issues page.

    TODO

    • Tool to convert from llrpjs JSON to LTK XML and vice versa
    • Vendor definitions support
    • Documentation

    Disclaimer

    This project is independent.

    Install

    npm i llrpjs

    DownloadsWeekly Downloads

    65

    Version

    1.0.1

    License

    MIT

    Unpacked Size

    897 kB

    Total Files

    157

    Last publish

    Collaborators

    • haytham43