Helper library for easy usp communication using mqtt over tcp or ws.
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.
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();
The most commonly used connection options (usp-js extends the connection options provided by mqtt.js):
The connect function also has several events that can be used to keep track of the connection state:
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"),
}
);
const result = await usp.get("Device.Time.");
Returns full USP message as sent by the device, mostly useful for debugging purposes.
const result = await usp.get("Device.Time.", { raw: true });
Max depth determines how granular the result is for large objects, defaults to 2.
const result = await usp.get("Device.Time.", { max_depth: 2 });
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",
},
],
};
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);
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"]);
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');
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",
};
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",
},
},
];
await usp.set("Device.WiFi.Radio.1.", {
Name: { required: true, value: "radio-1" },
allowPartial: true,
});
await usp.set("Device.WiFi.Radio.1.Name", "radio-1");
await usp.set(
["Device.WiFi.Radio.1.Name", "Device.WiFi.Radio.2."],
["radio-1", { Name: "radio-2" }]
);
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)
const [reset] = await usp.operate("Device.FactoryReset()", {
isAsync: false,
});
const results = await reset();
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();
await usp.add("Device.NAT.PortMapping."); // => "Device.NAT.PortMapping.3."
await usp.add("Device.IP.Interface.*.IPv4Address."); // => ['Device.IP.Interface.1.IPv4Address.2.', ... ]
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."
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."
await usp.del("Device.NAT.PortMapping.4.");
// returns list of affected paths
const result = ["Device.NAT.PortMapping.4."];
Retrieves supported domain model.
await usp.supportedDM("Device.WiFi.");
await usp.supportedDM("Device.", {
firstLevelOnly: false,
returnCommands: true,
returnEvents: true,
returnParams: true,
});
Retrieves supported protocols.
await usp.supportedProto("Device.");
await usp.instances("Device.WiFi.");
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();
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 allows extending commands with additional functionality:
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 },
});
Retrieves which usp version is currently being used by usp-js.
usp.getUSPVersion(); // => "1.2"
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");
Close connection.
await usp.disconnect();
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);
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"
);
});
const decodeID = (data: any) => String(data);
usp-js provides a quickjs compatible version. To use it, simply import from usp-js/qjs.
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.
Generated using TypeDoc