Skip to content
Snippets Groups Projects
README.md 13.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Sukru Senli's avatar
    Sukru Senli committed
    # usp-js
    
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    Helper library for easy usp communication using mqtt over tcp or ws.
    
    
    - [API documentation for usp-js](https://iopsys.se/usp-js/index.html)
    
    - [BBF's USP reference documentation](https://usp-data-models.broadband-forum.org/tr-181-2-14-0-usp.html)
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    # Installation
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    `npm install usp-js`
    
    
    To connect provide necessary info to the default export.
    Note: for browser usage make sure to import the library from usp-js/web.
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    ```javascript
    
    const connect = require("usp-js").default;
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    
    const run = async () => {
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
      // Connect
      const usp = await connect({
    
        host: "my.ip.here",
        username: "username",
        password: "password",
    
        port: 9001,
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    
      // Get property
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
      await usp.get("Device.WiFi.").then(console.log);
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    
      // Disconnect
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
      await usp.disconnect();
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    };
    
    run();
    ```
    
    ### connect
    
    The most commonly used connection options (usp-js extends the connection options provided by [mqtt.js](https://github.com/mqttjs/MQTT.js#mqttconnecturl-options)):
    
    - 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
    
    ```typescript
    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
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    ```javascript
    
    const result = await usp.get("Device.Time.");
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    ```
    
    
    Returns full USP message as sent by the device, mostly useful for debugging purposes.
    
    ```javascript
    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.
    
    ```javascript
    const result = await usp.get("Device.Time.", { max_depth: 2 });
    ```
    
    Results include "\_\_query\_\_" variable which holds the full path
    
    ```javascript
    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",
        },
      ],
    };
    ```
    
    USP errors will be thrown from the get call, while connection errors can be caught using the onError handler when connecting (example above)
    
    ```javascript
    await usp.get("Fake.Path").then(console.log).catch(console.error);
    ```
    
    Can retrieve multiple paths at the same time, result will always be in an array.
    
    ```javascript
    await usp.get(["Device.WiFi.Radio.1.Alias", "Device.WiFi.Radio.2.Alias"]);
    ```
    
    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).
    
    ```javascript
    await usp.get('Device.Ethernet.Interface.[Alias=="WAN"].CurrentBitRate');
    ```
    
    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.
    
    ```javascript
    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 object - does not need to have all attributes, but some may be required (check USP Reference)
    
    ```javascript
    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
    
    ```javascript
    await usp.set("Device.WiFi.Radio.1.", {
      Name: { required: true, value: "radio-1" },
      allowPartial: true,
    });
    ```
    
    ```javascript
    await usp.set("Device.WiFi.Radio.1.Name", "radio-1");
    ```
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    ```javascript
    
    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.
    
    ```javascript
    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
    
    ```javascript
    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.
    
    ```javascript
    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
    
    ```javascript
    await usp.add("Device.NAT.PortMapping."); // => "Device.NAT.PortMapping.3."
    ```
    
    - add with multiple responses
    
    ```javascript
    await usp.add("Device.IP.Interface.*.IPv4Address."); // => ['Device.IP.Interface.1.IPv4Address.2.', ... ]
    ```
    
    - add with arguments - adds a new object with provided values
    
    ```javascript
    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
    
    ```javascript
    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
    
    ```javascript
    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
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    ```javascript
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    ```
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    ```javascript
    
    await usp.supportedDM("Device.", {
      firstLevelOnly: false,
      returnCommands: true,
      returnEvents: true,
      returnParams: true,
    });
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    
    ```javascript
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    
    ```javascript
    
    ### 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.
    
      { id: "1234", notif: "ObjectCreation", reference: "Device.NAT.PortMapping." },
    
      (msg, fullMsg) => console.log({ msg, fullMsg })
    
    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.
    
    Marin Karamihalev's avatar
    Marin Karamihalev committed
    
    ```javascript
    
    const clear = usp.on(/NOTIFY.*/, (msg, rawMsg) => console.log({ msg, rawMsg }));
    
    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("Device.DeviceInfo.SerialNumber");
    
    
    To apply the options globally add the call to the connect function.
    
    ```javascript
    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.
    
    ```javascript
    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.
    
    ```javascript
    usp.setUSPVersion("1.2");
    ```
    
    ### disconnect
    
    Close connection.
    
    ```javascript
    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:
    
    ```typescript
    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:
    
    ```typescript
    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:
    
    ```typescript
    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.