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

usp-js

v0.4.9

Published

Library for easy usp(TR-369) communication using mqtt or ws.

Downloads

487

Readme

usp-js

Helper library for easy usp communication using mqtt over tcp or ws.

Installation

npm install usp-js

Quick Example

To connect provide necessary info to the default export. Note: for browser usage make sure to import the library from usp-js/web.

const connect = require("usp-js").default;

const run = async () => {
  // Connect
  const usp = await connect({
    host: "my.ip.here",
    username: "username",
    password: "password",
    port: 9001,
    protocol: "ws",
  });

  // Get property
  await usp.get("Device.WiFi.").then(console.log);

  // Disconnect
  await usp.disconnect();
};

run();

API

connect

The most commonly used connection options (usp-js extends the connection options provided by mqtt.js):

  • host (string): host address,
  • protocol (string): protocol to use (i.e. ws, wss, mqtt, etc),
  • port (number): port number,
  • username (string): log in username,
  • password (string): log in password,
  • useLatestUSPVersion (boolean): if set to true will automatically retrieve and use latest version of usp protocol available on the device (can instead be set manually by using the "version" option)

The connect function also has several events that can be used to keep track of the connection state:

  • onError ((err: string) => void): Handler for client errors, will not trigger on usp error (for instance trying to get a non-existent path),
  • onOffline (() => void): triggers when connection goes offline,
  • onReconnect (() => void): triggers when connection reconnects (can use options like "reconnectsBeforeClosing" to control reconnection attempts),
  • onClose (() => void): triggers when connection is closed

Example

const usp = await connect(
  {
    host: "host.ip",
    protocol: "ws",
    port: 9001,
    username: "username",
    password: "password",
    useLatestUSPVersion: true,
  },
  {
    onError: (err) => console.error("connection error:", err),
    onOffline: () => console.log("connection offline"),
    onReconnect: () => console.log("connection reconnecting"),
    onClose: () => console.log("connection closed"),
  }
);

get

  • get single item
const result = await usp.get("Device.Time.");
  • get raw message

Returns full USP message as sent by the device, mostly useful for debugging purposes.

const result = await usp.get("Device.Time.", { raw: true });
  • get with max depth (requires usp version 1.2)

Max depth determines how granular the result is for large objects, defaults to 2.

const result = await usp.get("Device.Time.", { max_depth: 2 });
  • get nested

Results include "__query__" variable which holds the full path

await usp.get("Device.NAT.PortMapping.");
const standardGet = [
  {
    Status: "Disabled",
    RemoteHost: "",
    Protocol: "TCP",
    LeaseDuration: "0",
    InternalPort: "0",
    InternalClient: "",
    Interface: "",
    ExternalPortEndRange: "0",
    ExternalPort: "0",
    Enable: "0",
    Description: "cat-1",
    AllInterfaces: "0",
    Alias: "cpe-1",
  },
];

await usp.getNested("Device.NAT.PortMapping.");
const nestedGet = {
  __query__: "Device.NAT.PortMapping.",
  result: [
    {
      __query__: "Device.NAT.PortMapping.1.",
      Alias: "cpe-1",
      AllInterfaces: "0",
      Description: "cat-1",
      Enable: "0",
      ExternalPort: "0",
      ExternalPortEndRange: "0",
      Interface: "",
      InternalClient: "",
      InternalPort: "0",
      LeaseDuration: "0",
      Protocol: "TCP",
      RemoteHost: "",
      Status: "Disabled",
    },
  ],
};
  • get with error

USP errors will be thrown from the get call, while connection errors can be caught using the onError handler when connecting (example above)

await usp.get("Fake.Path").then(console.log).catch(console.error);
  • get multiple paths

Can retrieve multiple paths at the same time, result will always be in an array.

await usp.get(["Device.WiFi.Radio.1.Alias", "Device.WiFi.Radio.2.Alias"]);
  • get using pattern

Can use search patterns to search for specific object, results are always in an array (patterns are based on USP implementation, they are not handled by usp-js).

await usp.get('Device.Ethernet.Interface.[Alias=="WAN"].CurrentBitRate');
  • resolve references in get

Many objects in the model hold references to other objects. To resolve those quickly, use the resolve function. Same functionality can be achieved by manually retrieving references and using get on them, resolve is just for convenience. Resolve also provides an optional level argument (defaults to 1) which allows retrieving multiple levels of references. Can be useful for complicated objects but it is best to avoid setting it to values above 3 as there can be circular references.

await usp.get("Device.NAT.InterfaceSetting.1.");
const normalResult = {
  Status: "Disabled",
  Interface: "Device.IP.Interface.1",
  Enable: "0",
  Alias: "cpe-1",
};

await usp.get("Device.NAT.InterfaceSetting.1.").then(usp.resolve);
// some values skipped for brevity
const resolvedResult = {
  Status: "Disabled",
  Interface: {
    ULAEnable: "1",
    Type: "Normal",
    TWAMPReflectorNumberOfEntries: "0",
    Status: "Down",
    Enable: "1",
    Alias: "cpe-1",
  },
  Enable: "0",
  Alias: "cpe-1",
};

set

  • set object - does not need to have all attributes, but some may be required (check USP Reference)
await usp.set("Device.NAT.PortMapping.1.", { Description: "a very nice port" });
// returns array of affected paths
const result = [
  {
    affectedPath: "Device.NAT.PortMapping.1.",
    updatedParams: {
      Description: "a very nice port",
    },
  },
];
  • set object with allowPartial and required attributes
await usp.set("Device.WiFi.Radio.1.", {
  Name: { required: true, value: "radio-1" },
  allowPartial: true,
});
  • set property
await usp.set("Device.WiFi.Radio.1.Name", "radio-1");
  • set multiple
await usp.set(
  ["Device.WiFi.Radio.1.Name", "Device.WiFi.Radio.2."],
  ["radio-1", { Name: "radio-2" }]
);

operate

  • Async command

Operate assumes the command is async by default.

const [ping, cleanPing] = await usp.operate("Device.IP.Diagnostics.IPPing()");
const results = await ping({ Host: "iopsys.eu" });
await cleanPing(); // clears ping subscription (optional)
  • Sync command
const [reset] = await usp.operate("Device.FactoryReset()", {
  isAsync: false,
});
const results = await reset();
  • Command with subscription

The returned operate command provides a way to subscribe to call results.

const [ping, cleanPing] = await usp.operate("Device.IP.Diagnostics.IPPing()");
ping.subscribe((msg) => console.log(msg));
await ping({ Host: "iopsys.eu" });
await ping({ Host: "google.se" });
await cleanPing();

add

  • add with no arguments - adds a new default object
await usp.add("Device.NAT.PortMapping."); // => "Device.NAT.PortMapping.3."
  • add with multiple responses
await usp.add("Device.IP.Interface.*.IPv4Address."); // => ['Device.IP.Interface.1.IPv4Address.2.', ... ]
  • add with arguments - adds a new object with provided values
await usp.add("Device.NAT.PortMapping.", {
  Description: "webserver1-set",
  ExternalPort: "80",
  Protocol: "TCP",
  Interface: "Device.IP.Interface.1",
  Enable: "true",
  InternalClient: "192.168.1.125",
  InternalPort: "5000",
}); // => "Device.NAT.PortMapping.4."
  • add with with allowPartial and required attributes
await usp.add("Device.NAT.PortMapping.", {
  allowPartial: true,
  Description: {
    required: true,
    value: "webserver1-set",
  }
  ExternalPort: "80",
  Protocol: "TCP",
  Interface: "Device.IP.Interface.1",
  Enable: "true",
  InternalClient: "192.168.1.125",
  InternalPort: "5000",
}); // => "Device.NAT.PortMapping.4."

del

await usp.del("Device.NAT.PortMapping.4.");
// returns list of affected paths
const result = ["Device.NAT.PortMapping.4."];

supportedDM

Retrieves supported domain model.

  • standard call
await usp.supportedDM("Device.WiFi.");
  • retrieve entire domain model
await usp.supportedDM("Device.", {
  firstLevelOnly: false,
  returnCommands: true,
  returnEvents: true,
  returnParams: true,
});

supportedProto

Retrieves supported protocols.

await usp.supportedProto("Device.");

instances

await usp.instances("Device.WiFi.");

subscribe

The subscription callback provides both the formatted message and the raw USP message. The returned function provides an easy way to clear the subscription.

const clearSub = await usp.subscribe(
  { id: "1234", notif: "ObjectCreation", reference: "Device.NAT.PortMapping." },
  (msg, fullMsg) => console.log({ msg, fullMsg })
);

await clearSub();

on

The "on" function is internal to usp-js. It provides a way to subscribe to incoming messages without directly interacting with the device (it will not add a subscription to the local agent). It is mostly used for debugging purposes. The first argument is the id of the message to subscribe to. It can be a string or a regexp. The general message id has the shape: COMMAND@random_string (i.e. NOTIFY@12345). The second argument is a callback that optionally provides access to the raw USP message.

const clear = usp.on(/NOTIFY.*/, (msg, rawMsg) => console.log({ msg, rawMsg }));

options

Options allows extending commands with additional functionality:

  • timeout: timeout after given ms (throws error)
  • preCall: called before each command, provides command name and arguments
  • postCall: called after each command, provides command name, arguments and result/error
  • get.retainPath: sets "retainPath" option to true for all get calls, equivalent to replacing getNeset everywhere
await usp
  .options({
    timeout: 1000,
    preCall: (name, args) => console.log(name, args),
    postCall: (name, args, result) => console.log(name, args, result),
    get: { retainPath: true },
  })
  .get("Device.DeviceInfo.SerialNumber");

To apply the options globally add the call to the connect function.

const usp = (await connect(connectionOptions)).options({
  timeout: 1000,
  preCall: (name, args) => console.log(name, args),
  postCall: (name, args, result) => console.log(name, args, result),
  get: { retainPath: true },
});

getUSPVersion

Retrieves which usp version is currently being used by usp-js.

usp.getUSPVersion(); // => "1.2"

setUSPVersion

Change which usp version is currently being used. This will only change which version usp-js is using, not the usp agent (mostly for debugging purposes). Generally, it is preferable to use the "useLatestUspVersion" option when connecting instead of setting the version manually.

usp.setUSPVersion("1.2");

disconnect

Close connection.

await usp.disconnect();

Making a new connect function

To allow more customization, new connect functions can be made to fit specific environments. By default, usp-js provides a version of connect for mqtt, ws and quickjs. To create a new connect, use the buildConnect function:

const myConnect = buildConnect({
  connectClient,
  decodeID,
  loadProtobuf,
});

const usp = await myConnect(connectionOptions);
  • connectClient The connectClient function is an async function that accepts connection options and returns a connection object. Below is an example of how the websocket client can be adapted:
const connectClient: ConnectClientFn = (opts: any) =>
  new Promise((resolve, reject) => {
    const client = new WebSocketClient();

    client.on("connectFailed", (error) => {
      reject(error);
    });

    client.on("connect", (ws) => {
      resolve({
        on: (key, responseFn) => ws.on(key, (data) => responseFn(key, data)),
        subscribe: (to) => null,
        unsubscribe: (from) => null,
        publish: (endpoint, msg) => ws.send(msg),
        end: ws.close,
      });
    });

    client.connect(
      `${opts.protocol}://${opts.host}:${opts.port}/usp/endpoint`,
      "v1.usp"
    );
  });
  • decodeID The decodeID function is used when a message arrives to retrieve the id from the raw data. The default function simply converts the data to a string:
const decodeID = (data: any) => String(data);
  • loadProtobuf The loadProtobuf function is used to load the protobuf files used to convert incoming messages into javascript legible data. It is provided as an option to buildConnect, since some environment can be restrictive about how files are loaded. It is possible to import the loadProtobuf function from configurations/mqtt if no alterations are required.

Quickjs

usp-js provides a quickjs compatible version. To use it, simply import from usp-js/qjs.

Quickjs development

Run the following command after making any changes to the base code:

yarn run qjs

This script needs to be run because compilation for quickjs requires several direct changes to the code.