Aims to provide a well-structured API to the Wireless Sensor Tag platform by interfacing with its JSON Web Service API. It is primarily intended, designed, and tested for server-side use through NodeJS. (However, making it usable within a browser is a future goal, and corresponding contributions are welcome too.)
$ npm install wirelesstags
The library (specifically, the platform.signin()
method, see
below) will need authentication information. The library supports two
default ways to pick up this information:
- A file
$HOME/.wirelesstags
in JSON format, with the necessary authentication information (currently keysusername
andpassword
). This file should obviously be readable only by the user running the app. - Environment variables
WIRELESSTAG_API_USER
andWIRELESSTAG_API_PASSWORD
, if set, will override whatever is found in the default options file.
It is strongly recommended to create a separate account as a "limited user" for using this library rather than your main account(s) at Wirelesstag.com. This makes it easy to change the password or delete the account altogether if the password happens to leak out, and allows controlling which tag managers and tags are visible to the account. Note also that wirelesstag.com stores your password in clear text (you can verify by recovering it), and hence never use a password there that you use anywhere else.
The principle object hierarchy is the following:
-
WirelessTagPlatform
: Top-level object representing the cloud interface to the platform. Emitsdiscover
events forWirelessTagManager
instances upon callingplatform.discoverTagManagers()
. -
WirelessTagManager
: Object representing a Tag Manager. Discovered through the platform object. Emitsdiscover
events for Wireless Tags associated with the tag manager upon callingtagManager.discoverTags()
. -
WirelessTag
: Object representing a Wireless Tag. Discovered through the tag manager object. Tags have sensor capabilities, which can be queried usingtag.hasHumiditySensor()
etc methods, or as an array of strings viatag.sensorCapabilities()
. Sensor objects can be discovered by callingtag.discoverSensors()
, which emits adiscover
event for each newly found sensor. They can be iterated over usingtag.eachSensor()
, which takes a callback. -
WirelessTagSensor
: Object abstracting a sensor that is part of a Wireless Tag. Sensor objects are of a type (sensor.sensorType
), and usually have areading
and aneventState
property. They can be armed or disarmed (sensor.arm()
andsensor.disarm()
), and their monitoring and notification configuration is available as properties of the object returned bysensor.monitoringConfig()
.
The platform object can be created using its constructor, or using the
static method WirelessTagPlatform.create()
.
var WirelessTagPlatform = require('wirelesstags');
// Passing a config object is optional. Default for apiBaseURI is
// https://www.mytaglist.com
var platform = new WirelessTagPlatform({ apiBaseURI: 'https://my.wirelesstag.com' });
When using the static create()
method, it will try to load
configuration options from ~/.wirelesstags
, or from the environment:
var WirelessTagPlatform = require('wirelesstags');
var platform = WirelessTagPlatform.create();
Platform instances emit a connect
event after successful
logging in, and a discover
event for each tag manager object. The
signin()
and discoverTagManagers()
methods also return promises,
the latter with an array of tag manager objects.
Define or obtain connection options:
var opts = { username: '[email protected]', password: 'supersecret' };
// or load from default configuration file or environment variables:
opts = WirelessTagPlatform.loadConfig();
platform.on('connect', () => {
console.log("connected to Wireless Tag cloud");
platform.discoverTagManagers();
});
platform.on('discover', (tagManager) => {
console.log("found tag manager", tagManager.name, tagManager.mac);
});
// once the listeners are set up we can connect
platform.signin(opts)
A platform instance (since v0.6.0) caches tag manager objects
resulting from a call to discoverTagManagers()
. Subsequent calls
will not update properties of these objects, but emit discover
events only for newly found (not previously cached) tag managers. This
allows an application to scan periodically for new tag managers,
without receiving discover
events redundantly for the same objects.
platform.connect(opts).then(() => {
return platform.discoverTagManagers();
}).then((tagManagers) => {
tagManagers.forEach((tagManager) => {
console.log("found tag manager", tagManager.name, tagManager.mac);
});
});
The method always promises all tag manager objects found (hence the
number of discover
events fired will only on the first call be the
same as the number of objects promised). Since v0.6.0, if called
repeatedly the objects promised for tag managers discovered previously
will be the same (but with updated properties), allowing an
application to scan periodically for new tag managers without losing
the application's state of prviously returnd objects.
The tag manager object emits discover
events for each tag associated
with it after starting discovery by calling tagManager.discoverTags()
.
In the same way, tag objects emit discover
events for each of their
newly found sensors after initiating discovery with
tag.discoverSensors()
.
The discovery methods also promise arrays of tags and sensors, respectively. Either approach (promises or events) can be used.
The tag.discoverSensors()
method always promises an array of all
its sensors, whereas it emits discover
events only for newly found
sensors. Subsequent tag.discoverSensors()
calls will promise the
same sensor objects (unless there were new sensors, but the current
generation of Wireless Tags cannot dynamically gain sensors).
In contrast, tagManager.discoverTags()
always emits the same number
of events as there are elements in the promised array of tag objects,
and the tag objects are always new objects, because tag manager
objects don't cache their associated tag objects. Indeed in practice
tags can be dynamically associated with or disassociated from tag
managers.
tagManager.on('discover', (tag) => {
console.log("Tag", tag.name, "(slaveId=" + tag.slaveId + ")", tag.uuid);
console.log(".. last updated", tag.lastUpdated());
tag.on('discover', (sensor) => {
console.log("..", sensor.sensorType, "of", sensor.wirelessTag.name);
console.log(" reading:", sensor.reading);
console.log(" state:", sensor.eventState);
console.log(" armed:", sensor.isArmed());
});
tag.discoverSensors();
});
tagManager.discoverTags();
tagManager.discoverTags().then((tags) => {
tags.forEach((tag) => {
console.log("Tag", tag.name, "(slaveId=" + tag.slaveId + ")", tag.uuid);
});
return Promise.all(tags.map((tag) => { return tag.discoverSensors(); }));
}).then((sensorLists) => {
sensorLists.forEach((sensors) => {
var tag = sensors[0].wirelessTag;
console.log("Sensors of tag", tag.name, tag.uuid);
sensors.forEach((sensor) => {
console.log("..", sensor.sensorType, "sensor");
console.log(" reading:", sensor.reading);
console.log(" state:", sensor.eventState);
console.log(" armed:", sensor.isArmed());
});
});
});
Since v0.6.0, tag objects can be discovered directly in one go from the platform object, without first finding the tag manager objects.
In terms of performance as determined by the sequence of cloud API calls, there is no difference to finding the tag managers first if only one tag manager is accessible to the connected account. However, in the case of multiple tag managers under the account, the difference can be notable (because currently the cloud API does not support filtering tags by tag manager at the server).
platform.discoverTags().then((tags) => {
tags.forEach((tag) => {
console.log("Tag", tag.name, "of", tag,wirelessTagManager.name,
"(slaveId=" + tag.slaveId + ")", tag.uuid);
});
// the following may need rate-limiting if there are many tags
// (e.g., see package p-limit for rate-limiting promises)
return Promise.all(tags.map((tag) => { return tag.discoverSensors(); }));
}).then((sensorLists) => {
sensorLists.forEach((sensors) => {
var tag = sensors[0].wirelessTag;
console.log("Sensors of tag", tag.name, tag.uuid);
sensors.forEach((sensor) => {
console.log("..", sensor.sensorType, "sensor");
console.log(" reading:", sensor.reading);
console.log(" state:", sensor.eventState);
console.log(" armed:", sensor.isArmed());
});
});
});
Each tag is uniquely identified by a UUID (available as tag.uuid
). This
could be used to pass a query to platform.discoverTags()
:
var uuidOfTag = 'DESIRED UUID VALUE';
platform.discoverTags({ uuid: uuidOfTag }).then((tags) => {
if (tags.length === 0) throw new Error("tag not found");
return tags[0].discoverSensors();
}).then((sensorList) => {
var tag = sensorList[0].wirelessTag;
console.log("Sensors of tag", tag.name, tag.uuid);
sensorList.forEach((sensor) => {
console.log("..", sensor.sensorType, "sensor");
console.log(" reading:", sensor.reading);
console.log(" state:", sensor.eventState);
console.log(" armed:", sensor.isArmed());
});
});
It should be noted that this has no performance advantage over filtering the
list of tag objects from platform.discoverTags()
, because the
JSON Web Service API has no server-side support for querying by UUID.
Another way to uniquely (at a moment in time) specify a tag is by tag manager
(as identified by its MAC) and the tag's slaveId
(a consecutive numbering
for the tags associated with a tag manager):
var MAC = '123456789ABCDEF' // replace with MAC of tag manager;
var slaveId = 12; // replace with slave ID of desired tag;
platform.findTagManager(MAC).then((tagMgr) => {
if (! tagMgr) throw new Error("tag manager not found");
return tagMgr.findTagById(slaveId); // rejects if not found
}).then((tag) => tag.discoverSensors()).then((sensorList) => {
var tag = sensorList[0].wirelessTag;
console.log("Sensors of tag", tag.name, tag.uuid);
sensorList.forEach((sensor) => {
console.log("..", sensor.sensorType, "sensor");
console.log(" reading:", sensor.reading);
console.log(" state:", sensor.eventState);
console.log(" armed:", sensor.isArmed());
});
}).catch((err) => console.error(err.stack ? err.stack : err));
For an account with access to many tags this may perform noticeably better
than filtering by UUID, because once the tag manager is found (and the number
of tag managers is likely at least an order of magnitude smaller than the
number of tags), obtaining the tag's data by slaveId
is supported server-side.
Once the promise returned from tag.discoverSensors()
is fulfilled,
sensor objects can also be accessed through the methods of the tag
object (because tag objects cache their sensors, and in practice
sensors can't be associated with or disassociated from a tag
dynamically). Either use tag.eachSensor()
with a callback that
accepts a sensor object, or use each sensor's individual accessor
property, which are all named zzzzSensor
, where zzzz is the type of
sensor, for example tag.lightSensor
. The method tag.sensorCapabilities()
returns an array of strings denoting the possible zzzz
values.
By default temperature sensors give their readings and monitoring thresholds
in ºC. The unit can be changed by setting sensor.monitoringConfig().unit
:
if (sensor.sensorType === "temp") {
// temperature reading and thresholds in ºC:
sensor.monitoringConfig().unit = "degC";
// temperature reading and thresholds in ºF:
sensor.monitoringConfig().unit = "degF";
// if desired, the change can be saved to become persistent:
sensor.monitoringConfig().save().then((mconfig) => {
console.log("saved monitoring config of temp of", sensor.wirelessTag.name);
console.log("... unit is now", mconfig.unit);
});
}
Updating the tag object's data also updates its sensors. tag.update()
updates a tag object's data from the cloud, and tag.liveUpdate()
requests
that the tag immediately post its latest information.
To regularly update a tag object's data whenver the tag posts its latest
information, a loop can be started with tag.startUpdateLoop()
, and
stopped by tag.stopUpdateLoop()
.
The library attempts to support both returning Promises from API-calling asynchronous functions, and the traditional callback mechanism.
Callbacks are generally called with an error as the first argument if
one occurred, and as the second argument with an object that has keys
object
(the object in which the operation was performed), and
value
(the resulting value from that operation) if the operation had
a result that is not saving or updating a property value of the
object. For example, in platform.isConnected(cb)
, cb
will be
called with { object: platform, value: false }
if the instance
wasn't yet connected.
It is considered a bad idea, and not supported (even if it may often work) to mix passing callbacks and using the returned Promises.
Note that callback behaviour is not currently tested as part of the test suite, so there could be bugs.
As of v0.7.0, API documentation is fairly complete:
- Online API documentation
- See the
examples/
directory for tutorial scripts that give basic, but fully working demonstrations of how the library can be used.
This library should be considered beta. Use at your own peril. Aside from the API documentation, consider looking at the (fairly extensive) test suite for guidance.
Aside from reporting issues and contributing pull requests, if you plan to buy from Wireless Sensor Tag, please consider using this link to their website. If more than 10 people do so, and some end up buying, I stand to receive a discount on a past purchase of mine, which will allow me to buy other types of tags in the future and support those too. (See #45 for an example.)
Available under the MIT License.