2.2.0 • Published 3 years ago

@wizzilab/wizzigate-api v2.2.0

Weekly downloads
-
License
BSD-2-Clause
Repository
-
Last release
3 years ago

This library implements the Wizzilab gateway MQTT API. It relies on the mqtt.js and the alp-lib libraries.

API

Gateway related classes

class Gateway

Static methods

from_mqtt_client(mqtt_client, configuration): GatewayAggregator
connect(mqtt_configuration, configuration): Gateway

Attributes

<<<<<<< HEAD

  • control(Control): Gateway distant control API.
  • cup(Cup): Gateway upgrade manager API.
  • modems({<UID>: Modem}): Hash map containing the gateway's modems. This hash map is populated upon reception of packets from the gateway modems.
  • status:
    • watchdog(Watchdog): Gateway watchdog informations.
    • bridges(Bridges): Gateway bridges informations.
  • version(State<[AppVersion]>]): version State of the gateway.
  • full_version(State<[AppVersion]>]): version State of the gateway.
  • anomalies(Event<[ErrorChain]>): aggregation of the anomalies

    caught by the gateway submodules.

  • control(Control): Gateway distant control API.

  • cup(Cup): Gateway upgrade manager API.
  • modems({<UID>: Modem}): Hash map containing the gateway's modems. This hash map is populated upon reception of packets from the gateway modems.
  • status:
    • watchdog(Watchdog): Gateway watchdog informations.
    • bridges(Bridges): Gateway bridges informations.
  • version(Version): Last received version of the gateway host.
  • full_version (FullVersion): Last received version of the gateway modem.

    622dad3... gateway Add FullVersion class, and methods

Methods

add_all_modems_listener(callback)

Registers a callback that will be called once on each modem of the gateway (currently known and the ones discovered afterwards).

  • callback ((modem: Modem) => void): Callback to register.
remove_all_modems_listener(callback)

Unsubscribes a callback from add_all_modems_listener.

version():Option<AppVersion>

Returs the version of the gateway if available.

Parameters

interface IConfiguration

Description of the configuration object to pass to the gateway constructor.

  • username (string): MQTT username used. Necessary to respect the MQTT ACL of the gateway (else your request might end up being ignored by the gateway).
  • id_root (string): Optional. A token ID that will be inserted in all MQTT request channels ID used (to be able to track your request if you are watching them on a separate MQTT client).
  • no_default_random_id_root (boolean): Optional. Prevents the lib from auto-adding a random token in the request IDs. Beware of the request id collisions.
  • no_user_id_prefix (boolean): Optional. Prevents using the username in the request topic for special use cases (like emulating request from multiple users through access of the gateway via a gateway aggregator).
  • mqtt_root_topic: Optional. Defines the root topic where the gateway streams are. On the gateways' brokers, the root topic is "/" (default configuration). If you forward the traffic of the gateway via a bridge to an external broker (and for some reason you prefer not to use the GatewayAggregator class), the root topic will be "/gw/<UID>/".

class Modem

Class representing one of the gateway modems.

Remarks: if no listener has been set for the report event, the library won't be parsing the incoming data (reducing the parsing load). The library will still parse the gateway version reports which are on separated MQTT topics.

<<<<<<< HEAD

Attributes

  • uid (string): UID of the modem (uppercase).
  • version(State<[AppVersion]>]): version State of the modem.
  • reports(Event<[report, meta]>): - report (any[]): Parsed ALP packet. - meta: - mqtt (IMqttPacketMeta): For internal use. - special (string): Optional. Set on special report such as hw_report and sw_report (modem and host version reports).
  • raw_reports(Event<[payload, meta]>): - report (Buffer): Mqtt payload. - meta (IReportMeta): Report metadata

  • uid (string): UID of the modem (uppercase).
  • version(Version): Last received version of the gateway modem.
  • full_version(FullVersion): Last received version of the gateway modem.

    622dad3... gateway Add FullVersion class, and methods

Methods

request(request, options): AsyncResult<AlpPacket[]>

Sends an ALP request to the modem and returns the response.

  • request (Buffer): ALP request.
  • options:
    • to (number): Request timeout. This corresponds the maximum time allowed between 2 response packet.
    • id (string): Optional. ID to use to send this request.
    • ignore_alp_error: Optional. Don't treat ALP error statuses as request errors.
request_with_realtime_responses(request, options, callback): AsyncResult<void>

Low level API to send an ALP request to the modem. Sends the request and then calls the callback for each individual response packet received. The return promise is resolved upon reception of the ALP end of packet.

  • request (Buffer): ALP request.
  • options:
    • to (number): Request timeout. This corresponds the maximum time allowed between 2 response packet.
    • id (string): Optional. ID to use to send this request.
  • callback: (pkt, meta) => void
    • pkt (object): Alp packet object.
    • meta:
      • sn (number): Response packet seqnum.
      • error (boolean): Means this response was sent using the modem driver error stream (modem driver error).
      • end (boolean): Means this is the last response packet for this request.
      • mqtt (IMqttPacketMeta): For internal use.

class Control

Methods

ping(options): AsyncResult<[]>

Pings the control service of the gateway. Commonly used to test the MQTT link to the gateway.

restart_dmons(options): AsyncResult<[]>

Restarts the deamons of the gateway.

restart_modems(options): AsyncResult<[]>

Restarts all the gateway modems.

reboot(options): AsyncResult<[]>

Reboots the gateway.

register(credentials, options): AsyncResult<[]>

Registers the gateway to the server.

  • credentials:
    • username (string): Server account username.
    • password (string): Server account password.
    • serial (string): Gateway serial number.
  • options (IJsonRequestOptions): Request options.
setup(config, options): AsyncResult<[]>

Sets the configuration of the gateway. All of the configuration parts are tagged optional because this commands allows partial configuration to be set.

ISystemConfig
  • hostname (string): Optional. Hostname of the gateway (this will change the avahi name on the network, the gateway hostname and the web interface gateway name).
  • web_credentials: Optional. Credentials to loging on the web interface of the gateway.
    • username (string)
    • password (string)
IMqttConfig
  • broker: Optional. Configuration regarding the gateway MQTT broker.
    • credentials: Optional. Sets the credentials of a user on the gateway.
      • username (string)
      • password (string)
  • bridge: Optional. Configuration of the MQTT secondary bridge.
    • address (string): Address of the broker targeted by the bridge.
    • port (number): Optional. MQTT port of the broker (default: 1883).
    • username (string): Optional.
    • password (string): Optional.
    • topic_prefix (string): Optional. Topic header used for the topic mapping on the distant broker.
    • cafile (string): Optional. Broker certificate for SSL connections.
IEthernetConfig
  1. DHCP
    • type = "dhcp"
  2. Static IP configuration
    • type = "static"
    • ip_address (string)
    • subnetmask (string)
    • broadcast (string)
    • gateway (string)
IWifiConfig
  1. Client with dhcp

    • type = "client"
    • ssid (string): SSID of the targeted wifi network.
    • password (string): Password of the targeted wifi network.
    • ip_config:
      • type = "dhcp"
  2. Client with static IP configuration

    • type = "client"
    • ssid (string): SSID of the targeted wifi network.
    • password (string): Password of the targeted wifi network.
    • ip_config:
      • type = "static"
      • ip_address (string)
      • subnetmask (string)
      • broadcast (string)
      • gateway (string)
  3. Access point

    • type = "access_point"
    • password (string): Password the clients will need to connect to the gateway wifi.
    • enable_dhcp (boolean): Whether the gateway should act as a DHCP server for the clients.
IGsmConfig
  • apn (string): APN.
  • pin (string): Optional. PIN code of the SIM card.
  • username (string): Optional.
  • password (string): Optional.
IDnsConfig
  • addresses (string[]): List of DNS IP addresses. The current gateways only support up to 2 DNS.

class Cup

Methods

clean(options): AsyncResult<[]>

Cleans the CUP download directory.

fetch(file, options): AsyncResult<[]>

Downloads a file from an HTTP(S) URL to the gateway.

  • file:
    • name (string): Name of the file that will be saved.
    • md5sum (string): Md5sum signature of the file.
    • url (string): URL from which the file can be fetched.
  • options (IJsonRequestOptions): Request options.
gateway_update(source, options): AsyncResult<[]>

Updates the gateway host using the described file.

  • source:
    • name (string): Name of the file that will be used for the upgrade.
    • md5sum (string): Md5sum signature of the file to use.
    • full_system (boolean): Indicates whether this is a full system update (as opposed to a package update).
  • options (IJsonRequestOptions): Request options.
devices_update(command, options): AsyncResult<[]>

Updates a list of distant devices using the given configuration and the selected pre downloaded files. The update files transfer will target the devices matching the specified filters (using Dash7 queries).

  • command: - gateway_modem_uid (string): Gateway modem to use. - xcls (number[]): List of the listening access classes of the devices to reach. - filter: (Even though each filter is optional, specifying them is highly recommended for safety reasons) - host(IDeviceFilter): Optional (recommended). Host version filter. - modem(IDeviceFilter): Optional (recommended). Modem version filter. - uids: string[]: List of the target devices' UIDs in hexadecimal. - target_type ("host" | "modem"): Target type of the code upgrade. - cup_version (number): Cup protocol version to use. - 1: (Deprecated) Uses the old CUP command file to switch the devices into CUP mode. Executes both the file transfer and execution phases in broadcast. - 2: (Current) Uses the new CUP broadcast file to switch the devices into CUP mode. Executes the file transfer in broadcast. The execution phase (sending the upgrade execution command) is executed for each device as a unicast procedure (targeting each UID specified in uids). This is safer than the version 1 of the protocol. - files (Array<{name: string, md5sum: string}>): List of the CUP files to use (must already have been downloaded by the gateway via cup.fetch). - max_size (number): Max file size available to transfer the new firmware. This information is available on the device version file (file 2 for the modem | file 65 for the host). It should be identical for all the devices of the same firmware version, therefore allowing broadcast file transfers. - cfg_fid (number): Optional. Overrides used CUP configuration file id. - code_fid (number): Optional. Overrides used CUP code file id. - page_size (number): Optional. Specifies target page size. - query (string: hex): Optional. Overrides the procedure filter query with this raw ALP query.
  • options (IJsonRequestOptions): Request options.
devices_file_transfer(command, options): AsyncResult<[]>

Transfer the selected files (pre uploaded on the gateway) to the target devices.

  • command: - gateway_modem_uid (string): Gateway modem to use. - xcls (number[]): List of the listening access classes of the devices to reach. - filter: - host(IDeviceFilter): Optional. Host version filter - modem(IDeviceFilter): Optional. Modem version filter - uids: string[]: List of the target devices' UIDs in hexadecimal. - target_type ("host" | "modem"): Target type of the code upgrade. - files (Array<{name: string, md5sum: string}>): List of the CUP files to use (must already have been downloaded by the gateway via cup.fetch). - max_size (number): Max file size available to transfer the new firmware. This information is available on the device version file (file 2 for the modem | file 65 for the host). It should be identical for all the devices of the same firmware version, therefore allowing broadcast file transfers. - cfg_fid (number): Optional. Overrides used CUP configuration file id. - code_fid (number): Optional. Overrides used CUP code file id. - page_size (number): Optional. Specifies the target page size. - query (string: hex): Optional. Overrides the procedure filter query with this raw ALP query.
  • options (IJsonRequestOptions): Request options.
devices_action(command, options): AsyncResult<[]>

Executes a list of actions on a list of devices.

  • command:
    • gateway_modem_uid (string): Gateway modem to use.
    • xcls (number[]): List of the listening access classes of the devices to reach.
    • filter:
    • uids: string[]: List of the target devices' UIDs in hexadecimal.
    • query (string: hex): Optional. Overrides the procedure filter query with this raw ALP query.
    • cmds (Array<ICupCommand>): ICupCommand definition:
      • cmd ("wr" | "wf"): Command type. (write file / write flush)
      • file_id (number)
      • file_offset (number)
      • data (string)
  • options (IJsonRequestOptions): Request options.

Parameters

interface IDeviceFilter

class Watchdog

Attributes

  • processes(HashMap<State<[ProcessState]>>): states of the gateway processes.
  • new_processes(Event<[string]>): returns the name of the newly detected processes.

Methods

add_all_processes_listener(callback)

Registers a callback to monitor the states of all the processes. The callback will be registered to an internal Event that will behave as if the callback had been registered to all each processes state individually (receiving the initial state for example).

  • callback: (name, state) => void
    • name (string): Process name.
    • state (enumProcessState): State of the process.
remove_all_processes_listener(callback)

Unsubscribes a callback from add_all_processes_listener.

Parameters

enum ProcessState

Enumeration of the available cup protocols exported by the library as ProcessState:

  • Unknown
  • Ready
  • Init
  • Dead

class Bridges

Attributes

  • bridges (HashMap<State<[ProcessState]>>): states of the gateway bridges.
  • new_bridges(Event<[string]>): returns the name of the newly detected bridges.

Methods

add_all_bridges_listener(callback)

WARNING: this currently doesn't work for the secondary bridge.

Registers a callback to monitor the states of all the bridges. The callback will be registered to an internal Event that will behave as if the callback had been registered to all each bridge state individually (receiving the initial state for example).

  • callback: (name, state) => void
    • name (string): Process name.
    • state(BridgeState): State of the bridge.
remove_all_processes_listener(callback)

Unsubscribes a callback from add_all_bridges_listener.

Parameters

enum BridgeState

Enumeration of the available cup protocols exported by the library as BridgeState:

  • Up
  • Down
  • Unknown

Gateway Aggregator

A gateway aggregator represents a external MQTT broker to which the wizzilab gateways are connected through an MQTT bridge (like the Secondary Bridge configurable on the MQTT web page of the gateways).

###class Gateway Aggregator

Static methods

from_mqtt_client(mqtt_client, configuration): GatewayAggregator
connect(mqtt_conf, aggregator_configuration): GatewayAggregator

Attributes

  • gateways({<UID>: NamedGateway}): Hash map containing the aggregator's gateways. This hash map is populated upon reception of packets from the gateway.
  • new_gateways(Event<[NamedGateway]>): returns the name of the newly detected gateways.
  • anomalies(Event<[ErrorChain]>): aggregation of the anomalies caught by owned gateways + aggregator anomalies.

Methods

add_all_gateways_listener(callback)

Executes the callback once on each of the aggregator's gateways and on each gateway that might appear afterwards.

  • callback: (gateway) => void
remove_all_gateways_listener(callback)

Unsubscribes a callback from add_all_gateways_listener.

Parameters

interface IConfiguration

Description of the configuration object to pass to the gateway aggregator constructor.

  • gateway_configuration: - username (string): MQTT username used. Necessary to respect the MQTT ACL of the gateway (else your request might end up being ignored by the gateway). - id_root (string): Optional. A token ID that will be inserted in all MQTT request channels ID used (to be able to track your request if you are watching them on a separate MQTT client). - no_default_random_id_root (boolean): Optional. Prevents the lib from auto-adding a random token in the request IDs. Beware of the request id collisions. - no_user_id_prefix (boolean): Optional. Prevents using the username in the request topic for special use cases (like emulating request from multiple users through access of the gateway via a gateway aggregator).
  • root_topic (string): Topic that is considered the aggregator root. The aggregator gateway are expected to map their topics 1 level under this root topic.
  • name_regex (string): Optional. Regex that the MQTT nodes must match in order to be considered gateways by the aggregator. Defaults to the UID regex.
  • accept_root_packet (boolean): Optional. Stop considering as anomalies packets sent on our root topic.

class NamedGateway

Class inheriting from Gateway, with the additionnal attribute name (string). Allows to find the gateways in the aggregator gateways hash map.

Common library types

type HashMap<T>

Represents a hash map, mapping strings to a type T. This is basically just a Javascript object of which all the properties have the same type T.

class Event

Object representing an event.

WARN: The object will not check for duplicate subscriptions. Thus registering twice to the same event with the same callback will get the callback called twice on each event. The unsub method will also remove a single occurence on the callback from the listening list per call.

Methods

sub(callback): void

Subscribes a callback to the event represented by this object.

  • callback: (...args: T) => void: Callback receiving the event.
unsub(callback): void

Removes a callback from the listener list of this event.

  • callback: (...args: T) => void: Callback to remove.
map<U extends unknown[]>(convert): Event<U>

Converts an event stream into another event stream.

  • convert: (...args: T) => U: Event converter.
filter(accept): Event

Creates a new event stream filtering some events.

  • accept: (...args: T) => boolean: Condition to accept events.
filter_map<U extends unknown[]>(accept): Event<U>

Creates an event stream filtering the original event and converting the accepted events.

  • convert: (...args: T) => Option<U>: Event converter.
pub(...args): void

WARN: You should probably never call this function unless you need to fake input for testing purpose for example. Triggers the event: sends the arguments to all the callbacks listening to this event.

  • ...args (T): Event type variadic argument (depends on the event).

class State extends Event

Object representing a state. It is an Event with the last event being saved as the current state. The only notable difference in behavior between State and Event is that if sub called after the State has been initialized with a value, the callback passed will be called immediately once with the current state.

Also, a state, contrary to an event, will never allow more than 1 argument to be emitted (because we consider that the events associated with the state is necessarily a unique object: the next state).

  • value (Option<[...args]>): If no event have been received yet, this will equal None. If an event has been received, it will contain Some(event), with event being an array containing the event variadic argument. For example, the [Gateway].version.value will either be a None or a Some(AppVersion).

interface IMqttConfiguration

  • url (string): URL of the gateway.
  • options (mqtt.IClientOptions): MQTT options of the MQTT client to connect to the gateway.
  • subscribe_topics (Array<{path: string, qos: mqtt.QoS}>): Topics to register to upon connection. Example: subscribe_topics: [{"path": "/md/+/report", qos: 2}]

interface IJsonRequestOptions

Mandatory:

  • to (number): Request timeout. This corresponds the maximum allowed time between 2 response packet.

Optional:

  • id (string): ID to use to send the request.
  • callback: (msg, meta) => void : Callback called on each response packet.
    • msg: any: Received message.
    • meta: any: Response metadata.

<<<<<<< HEAD

class AppVersion

Object representing an entity version.

  • id (number)
  • major (number)
  • minor (number)
  • patch (number)

  • id (string): ID to use to send the request.
  • callback: (msg, meta) => void : Callback called on each response packet.
    • msg: any: Received message.
    • meta: any: Response metadata.

class Version

Object representing an entity short version.

  • id (number)
  • major (number)
  • minor (number)
  • patch (number)

    622dad3... gateway Add FullVersion class, and methods

class FullVersion

Object representing an entity full version.

  • device_type (Buffer)
  • hardware (Buffer)
  • id (number)
  • major (number)
  • minor (number)
  • patch (number)
  • hash (Buffer | undefined)
  • maxsize (number | undefined)

Methods

constructor(id, major, minor, patch)
toString(): string

String representation of the version (X.X.X X).

Static methods

from(data, offset): AppVersion
  • data (Buffer): Data from which to parse the version (minimum of 6 bytes).
  • offset (number): Optional. Offset at which the version is in the data.

type AsyncResult<T>

This type is equivalent to Promise<Result<T,GatewayError.Any>>. It is used in this library to return asynchronous return values of procedures that can fail.

For example, if you send an ALP READ request on a distant device with the Modem.request method, and the gateway couldn't reach the device, the library will receive a NO_ACK stack status and the library will return an error of type AlpStatus containing the property stack: {code: ALP.STACK_ERORR.NO_ACK}.

module GatewayError

Class used to represent an Error from the library. Methods of this library that can fail will return a GatewayError.Any which is a discriminated union of classes inheriting from the GatewayError.GatewayError class.

The discriminated union is based on the code attribute of the errors.

class GatewayError.GatewayError

Attributes
  • code (GatewayError.Code): Error code. Each of these error codes are accessible through the enum GatewayError.Code exposed by the library. For example, the error code of an error issued by a StreamWrite error is GatewayError.Code.StreamWrite.
Methods
toString(verbosity): string
  • verbosity (number): (Defaults to 0)
    • 0 = error code + error type
    • 1 = adds readable details
    • 2 adds full error in JSON format.

Error types

StreamWrite

An error occured while trying to send an MQTT packet.

  • error (Error): Standard Javascript error issued by the MQTT.js library.
RequestIdDuplicate

The script tried to send a request with an ID that is already being used by another request currently in progress.

  • id (string): ID of the request that tried to cause an ID collision.
RequestTimeout

The request timed out.

  • id (string): ID of the request that timed out.
  • start (Date): The date at which the request started.
  • delay (number): The maximum delay authorized by the timeout between 2 packets.
JsonParse

The JSON request parsing of one of the responses failed.

  • data (Buffer): Data that couldn't be parsed.
  • error (Error): Parser error.
GatewayJson

The "MQTT request" succeeded but the gateway responded with an error.

  • error:
    • code (number): Gateway error code.
    • msg (string): Gateway error message.
    • resps (object[]): List of the responses.
ModemDriver

The "MQTT request" succeeded but the gateway modem driver responded with an error.

  • msg (string): Error message.
MissingAlpPacket

Some response packets were lost (they all have a seqnum).

  • missing (number[]): List of the lost seqnums.
  • resps (object[]): List of the received ALP responses.
IAlpParse

The ALP parsing of one of the response packet failed.

  • data (Buffer): Data that could not be parsed.
  • error (Error): Parser error.
IAlpStatus

The received response contains some bad ALP statuses. This means either some of our request actions failed, or that we had a stack error (NO_ACK from the device for example).

  • actions (Array<{id: number, code: number}>): Bad action statuses. The id corresponds to the action ID and the code to the STATUS code (see ALP spec).
  • stack: {code: number}: Optional. Error status from the gateway modem stack.
  • resps (object[]): List of the ALP responses.
IAlpTag

The packet has no status error but still has its TAG end of packet action containing an error flag set. This happened on older modem versions on which the stack errors were not sent to the host (and resulted in an error flag set).

  • resps (object[]): List of the ALP responses.

MQTT API specification

Protocol overlay

The gateway currently hosts 2 types of communications entities built over the MQTT protocols:

  • The notification streams: which provide a stream of data from the gateway to the clients.
  • The request channels: which provides an interface that allows a client to interact with the gateway.

Notifications streams ("read only" flows)

Each notification stream can generally be described by a topic on which its data will be sent and a data format.

For example, the /md/<UID>/report topics are notifications topics transporting raw ALP commands that corresponds to the reports sent by other devices and received by the gateway modem <UID>.

Requests channels

A request channel is described by 2 topics:

  • the request topic: where you can write a request.
  • the response topic: where you will receive a response to your request.

Unless specified otherwise, the channel topics use the following convention:

  • request topic = <channel_root_topic>/request/<user>/<request_id>
  • response topic = <channel_root_topic>/response/<user>/<request_id>

With:

  • channel_root_topic: A topic representing a service, like /control or /md/<UID>
  • user: The mqtt username. This is enforced by the gateway MQTT broker ACL which will forbid you from publishing any request outside of your username sub-topics.
  • request_id: An ID to identify the request in case your user is sending more than one at the same time. You are to ensure the uniqueness of your request_id inside your /<user> sub topic (which is already done by default in this library).

An extensive description of those topics is given on the Wizzilab wiki MQTT topic page.

Anomaly streams

All of the classes here maintain an anomaly stream. This allows the library to track gateway abnormal states that would explain unexpected behaviors (external request id collisions, unparseable ALP payloads, etc ...).

To watch theses anomalies, these classes implement the following method:

  • on_anomaly(callback): Registers a callback to the anomaly detection stream.
    • callback ((anomaly) => void)

A good practice would be to register to each gateway's anomaly stream and print them in the logs.

ErrorChain

Methods
toString(): string

Converts the error chain into a printable string.

Examples

Gateway report watcher

NodeJS

const { Gateway } = require("@wizzilab/wizzigate-api");

const gateway_mqtt_conf = {
    url: "mqtt://wizzigate.local:8883",
    options: {
        username: "user",
        password: "user",
    },
};

const { mqtt_client, gateway } = Gateway.connect(gateway_mqtt_conf, {
    username: gateway_mqtt_conf.options.username,
});

mqtt_client.on("connect", () => {
    console.log("Connected");
});

mqtt_client.on("reconnect", () => {
    console.log("reconnect");
});

mqtt_client.on("close", () => {
    console.log("close");
});

mqtt_client.on("offline", () => {
    console.log("offline");
});

mqtt_client.on("end", () => {
    console.log("end");
});

gateway.add_all_modems_listener((modem) => {
    console.log("Found gateway modem: " + modem.uid);
    modem.reports.add_listener((report) => {
        console.log("Report:", report);
    });
});

gateway.anomalies.add_listener((anomaly) => {
    console.log("ANOMALY: " + anomaly);
});

Typescript

import { Gateway } from "@wizzilab/wizzigate-api";

const gateway_mqtt_conf = {
    url: "mqtt://wizzigate.local:8883",
    options: {
        username: "user",
        password: "user",
    },
};

const { mqtt_client, gateway } = Gateway.connect(gateway_mqtt_conf, {
    username: gateway_mqtt_conf.options.username,
});

mqtt_client.on("connect", () => {
    console.log("Connected");
});

mqtt_client.on("reconnect", () => {
    console.log("reconnect");
});

mqtt_client.on("close", () => {
    console.log("close");
});

mqtt_client.on("offline", () => {
    console.log("offline");
});

mqtt_client.on("end", () => {
    console.log("end");
});

gateway.add_all_modems_listener((modem) => {
    console.log("Found gateway modem: " + modem.uid);
    modem.reports.add_listener((report) => {
        console.log("Report:", report);
    });
});

gateway.anomalies.add_listener((anomaly) => {
    console.log("ANOMALY: " + anomaly);
});

Aggregator report watcher

NodeJS

const { GatewayAggregator } = require("@wizzilab/wizzigate-api");

const aggregator_mqtt_conf = {
    url: "mqtt://mybroker.com",
    options: {
        username: "admin",
        password: "pass",
    },
};

const { mqtt_client, aggregator } = GatewayAggregator.connect(
    aggregator_mqtt_conf,
    {
        root_topic: "/gw",
        gateway_configuration: {
            username: aggregator_mqtt_conf.options.username,
        },
    },
);

mqtt_client.on("connect", () => {
    console.log("Aggregator connected");
});

aggregator.add_all_gateways_listener((gateway) => {
    gateway.add_all_modems_listener((modem) => {
        console.log("Found gateway modem: " + modem.uid);
        modem.reports.add_listener((report) => {
            console.log("Report:", report);
        });
    });

    gateway.anomalies.add_listener((anomaly) => {
        console.log("ANOMALY: " + anomaly);
    });
});

Typescript

import { GatewayAggregator } from "@wizzilab/wizzigate-api";

const aggregator_mqtt_conf = {
    url: "mqtt://mybroker.com",
    options: {
        username: "admin",
        password: "pass",
    },
};

const { mqtt_client, aggregator } = GatewayAggregator.connect(
    aggregator_mqtt_conf,
    {
        root_topic: "/gw",
        gateway_configuration: {
            username: aggregator_mqtt_conf.options.username,
        },
    },
);

mqtt_client.on("connect", () => {
    console.log("Aggregator connected");
});

aggregator.add_all_gateways_listener((gateway) => {
    gateway.add_all_modems_listener((modem) => {
        console.log("Found gateway modem: " + modem.uid);
        modem.reports.add_listener((report) => {
            console.log("Report:", report);
        });
    });

    gateway.anomalies.add_listener((anomaly) => {
        console.log("ANOMALY: " + anomaly);
    });
});

Distant device version reader

NodeJS

const { Modem, Gateway, AppVersion } = require("@wizzilab/wizzigate-api");
const { Err, Ok } = require("@usefultools/monads");

const AT = require("alp-tools");

const REQUEST_TIMEOUT = 10 * 1000;
const FID = {
    UID: 0,
    VERSION: {
        MODEM: 2,
        HOST: 65,
    },
};
const VERSION_OFFSET = 12;

const gateway_mqtt_conf = {
    url: "mqtt://wizzigate.local:8883",
    options: {
        username: "user",
        password: "user",
    },
};

const alp_builder = new AT.PacketBuilder();
// Setting default ALP commands parameters (make the packet building less painful)
alp_builder.set_default_params({
    tag: { eop: true, tag_id: 0 },
    read_file: { resp: true, group: false, file_offset: 0 },
    write_file: { resp: true, group: false, file_offset: 0 },

    fwd: {
        resp: true,
        group: false,

        itf_id: 0xd7,

        stop_on_err: false,
        record: false,
        retry_mode: 0,
        resp_mode: 1,
        dorm_to: 0,
        te: 0,

        addr: {
            addr_type: 2,
            nls_method: 6,
            estimated_reached: 4,
        },
    },
});

const { mqtt_client, gateway } = Gateway.connect(gateway_mqtt_conf, {
    username: gateway_mqtt_conf.options.username,
});

mqtt_client.on("connect", () => {
    console.log("Connected");
});

async function read_version(modem, target) {
    // Build ALP command
    const distant_read_command = alp_builder
        .tag()
        .fwd({
            addr: {
                id: target,
                access_class: 0x01,
            },
        })
        .read_file({
            file_id: FID.VERSION.MODEM,
            file_offset: VERSION_OFFSET,
            length: AppVersion.size(),
        })
        .read_file({
            file_id: FID.VERSION.HOST,
            file_offset: VERSION_OFFSET,
            length: AppVersion.size(),
        })
        .buffer();

    // Send the request
    const result = await modem.request(distant_read_command, {
        to: REQUEST_TIMEOUT,
    });

    // Process the result
    if (result.is_ok()) {
        const responses = result.unwrap();
        // Responses packet description ---------------------------------------------
        // ITF_FULL status (payload successfully sent to the DASH7 stack of the modem)
        // const itf_full_status_packet = responses[0];

        // Response packet. This is the informations we want.
        const response_packet = responses[1];

        // End of transaction packet (TAG_RSP + STATUS)
        // const end_packet = responses[2];

        // Response packet actions descriptions -------------------------------------
        // Tag to match the response to our request
        // const tag_rsp = response_packet[0];

        // Interface status (address of the source, LB, rx_lev, etc...)
        // const istatus = response_packet[1];

        // Reads we requested
        const read_1 = response_packet[2];
        const read_2 = response_packet[3];

        // Parse the response
        return Ok({
            modem: AppVersion.from(read_1.data),
            host: AppVersion.from(read_2.data),
        });
    } else {
        return Err(result.err());
    }
}

let emitting_modem_uid;
// Catch the user's inputs
const readline = require("readline");
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
});
rl.on("line", async (input) => {
    // Check that the UID has a good format
    const filtered_input = input.match(/^[0-9A-Fa-f]{16}/);
    if (!filtered_input) {
        console.log(
            "Bad uid. The uid should consist of 16 hexadecimal digits.",
        );
        return;
    }
    // Check that we already have discovered a gateway modem to send the ALP requests
    if (!emitting_modem_uid) {
        return;
    }

    // Parse UID
    const uid = Buffer.from(filtered_input[0].slice(0, 16), "hex");

    // Try to read the versions of the device
    console.log("Reading versions of " + uid.toString("hex").toUpperCase());
    let result;
    try {
        result = await read_version(gateway.modems[emitting_modem_uid], uid);
        // Handle promise exceptions
    } catch (e) {
        console.log(e.stack);
        return process.exit(-1);
    }

    // On success, print the versions
    if (result.is_ok()) {
        const versions = result.unwrap();
        console.log("Modem: " + versions.modem.toString());
        console.log("Host : " + versions.host.toString());
        // On failure, print the error
    } else {
        console.log(
            "Could not read the versions: " + result.unwrap_err().toString(),
        );
    }
    return;
});

const known_devices = {};
gateway.add_all_modems_listener((modem) => {
    console.log("Found gateway modem: " + modem.uid);

    // Save the modem as the emitting modem
    if (!emitting_modem_uid) {
        console.log(
            "You can now read versions of distant devices by entering their UIDs in the console.",
        );
    }
    emitting_modem_uid = modem.uid;

    // Prevent the gateway modem to be declared as discovered by itself (non pertinent)
    known_devices[modem.uid] = true;

    // Print the discovery of devices sending reports to this gateway
    modem.reports.add_listener((report) => {
        const first_packet = report[0];
        // Only process report starting with an ISTATUS
        if (
            first_packet.opcode !== AT.SPEC.ACTIONS.STATUS.opcode &&
            first_packet.opcode.ext !== 1
        ) {
            return;
        }

        // Save the device and print it if it is not known
        const uid = first_packet.addr.id.toString("hex").toUpperCase();
        if (!known_devices[uid]) {
            known_devices[uid] = true;
            console.log(
                "Modem " +
                    modem.uid +
                    ": Discovered device " +
                    uid +
                    " LB " +
                    report[0].lb,
            );
        }
    });
});

gateway.anomalies.add_listener((anomaly) => {
    console.log("ANOMALY: " + anomaly);
});

Typescript

import { Modem, Gateway, AppVersion } from "@wizzilab/wizzigate-api";
import { Err, Ok } from "@usefultools/monads";

// tslint:disable:no-var-requires
const AT = require("alp-tools");
// tslint:enable:no-var-requires

const REQUEST_TIMEOUT = 10 * 1000;
const FID = {
    UID: 0,
    VERSION: {
        MODEM: 2,
        HOST: 65,
    },
};
const VERSION_OFFSET = 12;

const gateway_mqtt_conf = {
    url: "mqtt://wizzigate.local:8883",
    options: {
        username: "user",
        password: "user",
    },
};

const alp_builder = new AT.PacketBuilder();
// Setting default ALP commands parameters (make the packet building less painful)
alp_builder.set_default_params({
    tag: { eop: true, tag_id: 0 },
    read_file: { resp: true, group: false, file_offset: 0 },
    write_file: { resp: true, group: false, file_offset: 0 },

    fwd: {
        resp: true,
        group: false,

        itf_id: 0xd7,

        stop_on_err: false,
        record: false,
        retry_mode: 0,
        resp_mode: 1,
        dorm_to: 0,
        te: 0,

        addr: {
            addr_type: 2,
            nls_method: 6,
            estimated_reached: 4,
        },
    },
});

const { mqtt_client, gateway } = Gateway.connect(gateway_mqtt_conf, {
    username: gateway_mqtt_conf.options.username,
});

mqtt_client.on("connect", () => {
    console.log("Connected");
});

async function read_version(modem: Modem, target: Buffer) {
    // Build ALP command
    const distant_read_command = alp_builder
        .tag()
        .fwd({
            addr: {
                id: target,
                access_class: 0x01,
            },
        })
        .read_file({
            file_id: FID.VERSION.MODEM,
            file_offset: VERSION_OFFSET,
            length: AppVersion.size(),
        })
        .read_file({
            file_id: FID.VERSION.HOST,
            file_offset: VERSION_OFFSET,
            length: AppVersion.size(),
        })
        .buffer();

    // Send the request
    const result = await modem.request(distant_read_command, {
        to: REQUEST_TIMEOUT,
    });

    // Process the result
    if (result.is_ok()) {
        const responses = result.unwrap();
        // Responses packet description ---------------------------------------------
        // ITF_FULL status (payload successfully sent to the DASH7 stack of the modem)
        // const itf_full_status_packet = responses[0];

        // Response packet. This is the informations we want.
        const response_packet = responses[1];

        // End of transaction packet (TAG_RSP + STATUS)
        // const end_packet = responses[2];

        // Response packet actions descriptions -------------------------------------
        // Tag to match the response to our request
        // const tag_rsp = response_packet[0];

        // Interface status (address of the source, LB, rx_lev, etc...)
        // const istatus = response_packet[1];

        // Reads we requested
        const read_1 = response_packet[2];
        const read_2 = response_packet[3];

        // Parse the response
        return Ok({
            modem: AppVersion.from(read_1.data),
            host: AppVersion.from(read_2.data),
        });
    } else {
        return Err(result.err());
    }
}

let emitting_modem_uid: string | undefined;
// Catch the user's inputs
import * as readline from "readline";
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
});
rl.on("line", async (input: string) => {
    // Check that the UID has a good format
    const filtered_input = input.match(/^[0-9A-Fa-f]{16}/);
    if (!filtered_input) {
        console.log(
            "Bad uid. The uid should consist of 16 hexadecimal digits.",
        );
        return;
    }
    // Check that we already have discovered a gateway modem to send the ALP requests
    if (!emitting_modem_uid) {
        return;
    }

    // Parse UID
    const uid = Buffer.from(filtered_input[0].slice(0, 16), "hex");

    // Try to read the versions of the device
    console.log("Reading versions of " + uid.toString("hex").toUpperCase());
    let result;
    try {
        result = await read_version(gateway.modems[emitting_modem_uid], uid);
        // Handle promise exceptions
    } catch (e) {
        console.log(e.stack);
        return process.exit(-1);
    }

    // On success, print the versions
    if (result.is_ok()) {
        const versions = result.unwrap();
        console.log("Modem: " + versions.modem.toString());
        console.log("Host : " + versions.host.toString());
        // On failure, print the error
    } else {
        console.log(
            "Could not read the versions: " + result.unwrap_err().toString(),
        );
    }
    return;
});

const known_devices: { [name: string]: boolean } = {};
gateway.add_all_modems_listener((modem) => {
    console.log("Found gateway modem: " + modem.uid);

    // Save the modem as the emitting modem
    if (!emitting_modem_uid) {
        console.log(
            "You can now read versions of distant devices by entering their UIDs in the console.",
        );
    }
    emitting_modem_uid = modem.uid;

    // Prevent the gateway modem to be declared as discovered by itself (non pertinent)
    known_devices[modem.uid] = true;

    // Print the discovery of devices sending reports to this gateway
    modem.reports.add_listener((report) => {
        const first_packet = report[0];
        // Only process report starting with an ISTATUS
        if (
            first_packet.opcode !== AT.SPEC.ACTIONS.STATUS.opcode &&
            first_packet.opcode.ext !== 1
        ) {
            return;
        }

        // Save the device and print it if it is not known
        const uid = first_packet.addr.id.toString("hex").toUpperCase();
        if (!known_devices[uid]) {
            known_devices[uid] = true;
            console.log(
                "Modem " +
                    modem.uid +
                    ": Discovered device " +
                    uid +
                    " LB " +
                    report[0].lb,
            );
        }
    });
});

gateway.anomalies.add_listener((anomaly) => {
    console.log("ANOMALY: " + anomaly);
});

Changelog

v2.1

  • Add Gateway.from_mqtt_client static method

v2

  • Use @usefultools/monads instead of deprecated @threestup/monads package. This is a breaking change as the Result type API has changed: - The old .ok() is equivalent to the new .unwrap() - The old .err() is equivalent to the new .unwrap_err()
  • Refactor the event system. This is a breaking change as this removes all the previous hook methods. - The gateway modules can now have Event and/or State attributes. Those attributes are the new hooks. - The entities aggregating multiple other entities (the GatewayAggregator aggregating gateways, the Gateway aggregating modems, etc ...) still have a convenient method to register a callback on all of the sub entities, but it has been renamed add_all_*_listener and now has a dual method allowing unregistering the callback.
  • Renaming: - The old Version object is now called AppVersion. The new Version object does not include the id field (which might not always be relevant or present).
1.2.1

3 years ago

2.2.0

3 years ago

1.2.0

3 years ago

1.1.2

3 years ago

1.1.1

4 years ago

1.1.0

4 years ago

2.1.1

4 years ago

2.1.0

4 years ago

2.0.1

4 years ago

2.0.0

4 years ago

1.0.11

5 years ago

1.0.10

5 years ago

1.0.9

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago