diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..feaf865
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..e759d29
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,2 @@
+AWS IoT JavaScript SDK for Embedded Devices
+Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
diff --git a/README.md b/README.md
index a748a16..d6c214b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,646 @@
-# aws-iot-thing-sdk-js
-SDK for connecting to AWS IoT from a device using Javascript / NodeJS.
+# AWS IoT SDK for JavaScript
+The aws-iot-device-sdk.js package allows developers to write JavaScript
+applications which access the AWS IoT Platform; it is intended for use in
+embedded devices which support Node.js, but it can be used in other Node.js
+environments as well.
+
+* [Overview](#overview)
+* [Installation](#install)
+* [Examples](#examples)
+* [API Documentation](#api)
+* [Example Programs](#programs)
+* [License](#license)
+* [Support](#support)
+
+
+## Overview
+This document provides instructions on how to install and configure the AWS
+IoT device SDK for Node.js and includes examples demonstrating use of the
+SDK APIs.
+
+### MQTT connection
+This package is built on top of [mqtt.js](https://github.com/mqttjs/MQTT.js/blob/master/README.md) and provides two classes: 'device'
+and 'thingShadow'. The 'device' class loosely wraps [mqtt.js](https://github.com/mqttjs/MQTT.js/blob/master/README.md) to provide a
+secure connection to the AWS IoT platform and expose the [mqtt.js](https://github.com/mqttjs/MQTT.js/blob/master/README.md) interfaces
+upward via an instance of the mqtt client.
+
+### Thing Shadows
+The 'thingShadow' class implements additional functionality for accessing Thing Shadows via the AWS IoT
+API; the thingShadow class allows devices to update, be notified of changes to,
+get the current state of, or delete Thing Shadows from AWS IoT. Thing
+Shadows allow applications and devices to synchronize their state on the AWS IoT platform.
+For example, a remote device can update its Thing Shadow in AWS IoT, allowing
+a user to view the device's last reported state via a mobile app. The user
+can also update the device's Thing Shadow in AWS IoT and the remote device
+will synchronize with the new state. The 'thingShadow' class supports multiple
+Thing Shadows per mqtt connection and allows pass-through of non-Thing-Shadow
+topics and mqtt events.
+
+
+## Installation
+
+Installing with npm:
+
+```sh
+npm install aws-iot-device-sdk
+```
+
+Installing from github:
+
+```sh
+git clone https://github.com/aws/aws-iot-device-sdk-js.git
+cd aws-iot-device-sdk-js
+npm install mqtt
+npm install blessed
+npm install blessed-contrib
+```
+
+Note that the dependencies on 'blessed' and 'blessed-contrib' are required
+only for the [temperature-control.js example program](#temp-control) and
+will not be necessary in most application environments.
+
+
+## Examples
+
+### Device Class
+```js
+var awsIot = require('aws-iot-device-sdk');
+
+var device = awsIot.device({
+ keyPath: '~/awsCerts/private.pem.key',
+ certPath: '~/awsCerts/certificate.pem.crt',
+ caPath: '~/awsCerts/root-CA.crt',
+ clientId: 'myAwsClientId',
+ region: 'us-east-1'
+});
+
+//
+// Device is an instance returned by mqtt.Client(), see mqtt.js for full
+// documentation.
+//
+device
+ .on('connect', function() {
+ console.log('connect');
+ device.subscribe('topic_1');
+ device.publish('topic_2', JSON.stringify({ test_data: 1}));
+ });
+
+device
+ .on('message', function(topic, payload) {
+ console.log('message', topic, payload.toString());
+ });
+```
+### Thing Shadow Class
+```js
+var awsIot = require('aws-iot-device-sdk');
+
+var thingShadows = awsIot.thingShadow({
+ keyPath: '~/awsCerts/private.pem.key',
+ certPath: '~/awsCerts/certificate.pem.crt',
+ caPath: '~/awsCerts/root-CA.crt',
+ clientId: 'myAwsClientId',
+ region: 'us-east-1'
+});
+
+//
+// Thing shadow state
+//
+var rgbLedLampState = {"state":{"desired":{"red":187,"green":114,"blue":222}}};
+
+//
+// Client token value returned from thingShadows.update() operation
+//
+var clientTokenUpdate;
+
+thingShadows.on('connect', function() {
+//
+// After connecting to the AWS IoT platform, register interest in the
+// Thing Shadow named 'RGBLedLamp'.
+//
+ thingShadows.register( 'RGBLedLamp' );
+//
+// 2 seconds after registering, update the Thing Shadow named
+// 'RGBLedLamp' with the latest device state and save the clientToken
+// so that we can correlate it with status or timeout events.
+//
+// Note that the delay is not required for subsequent updates; only
+// the first update after a Thing Shadow registration using default
+// parameters requires a delay. See API documentation for the update
+// method for more details.
+//
+ setTimeout( function() {
+ clientTokenUpdate = thingShadows.update('RGBLedLamp', rgbLedLampState );
+ }, 2000 );
+ });
+
+thingShadows.on('status',
+ function(thingName, stat, clientToken, stateObject) {
+ console.log('received '+stat+' on '+thingName+': '+
+ JSON.stringify(stateObject));
+ });
+
+thingShadows.on('delta',
+ function(thingName, stateObject) {
+ console.log('received delta '+' on '+thingName+': '+
+ JSON.stringify(stateObject));
+ });
+
+thingShadows.on('timeout',
+ function(thingName, clientToken) {
+ console.log('received timeout '+' on '+operation+': '+
+ clientToken);
+ });
+```
+
+
+## API Documentation
+
+ * awsIot.device()
+ * awsIot.thingShadow()
+ * awsIot.thingShadow#register()
+ * awsIot.thingShadow#unregister()
+ * awsIot.thingShadow#update()
+ * awsIot.thingShadow#get()
+ * awsIot.thingShadow#delete()
+ * awsIot.thingShadow#publish()
+ * awsIot.thingShadow#subscribe()
+ * awsIot.thingShadow#unsubscribe()
+
+-------------------------------------------------------
+
+### awsIot.device(options)
+
+Returns an instance of the [mqtt.Client()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#client)
+class, configured for a TLS connection with the AWS IoT platform and with
+arguments as specified in `options`. The awsIot-specific arguments are as
+follows:
+
+ * `region`: the AWS IoT region you will operate in (default 'us-east-1')
+ * `clientId`: the client ID you will use to connect to AWS IoT
+ * `certPath`: path of the client certificate associated with your AWS account
+ * `keyPath`: path of the private key file for your client certificate
+ * `caPath`: path of your CA certificate
+
+`options` also contains arguments specific to mqtt. See [the mqtt client documentation]
+(https://github.com/mqttjs/MQTT.js/blob/master/README.md#client) for details
+of these arguments.
+
+Supports all events emitted by the [mqtt.Client()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#client) class.
+
+-------------------------------------------------------
+
+### awsIot.thingShadow(options)
+
+The `thingShadow` class wraps an instance of the `device` class with additional
+functionality to operate on Thing Shadows via the AWS IoT API. The
+arguments in `options` include all those in the [device class](#device), with
+the addition of the following arguments specific to the `thingShadow` class:
+
+* `operationTimeout`: the timeout for thing operations (default 30 seconds)
+* `postSubscribeTimeout`: the time to wait after subscribing to an operation's sub-topics prior to publishing on the operation's topic (default 2.2 seconds)
+
+Supports all events emitted by the [mqtt.Client()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#client) class; however, the semantics for the
+`message` event are slightly different and additional events are available
+as described below:
+
+### Event `'message'`
+
+`function(topic, message) {}`
+
+Emitted when a message is received on a topic not related to any Thing Shadows:
+* `topic` topic of the received packet
+* `message` payload of the received packet
+
+### Event `'status'`
+
+`function(thingName, stat, clientToken, stateObject) {}`
+
+Emitted when an operation `update|get|delete` completes.
+* `thingName` name of the Thing Shadow for which the operation has completed
+* `stat` status of the operation `accepted|rejected`
+* `clientToken` the operation's clientToken
+* `stateObject` the stateObject returned for the operation
+
+Applications can use clientToken values to correlate status events with the
+operations that they are associated with by saving the clientTokens returned
+from each operation.
+
+### Event `'delta'`
+
+`function(thingName, stateObject) {}`
+
+Emitted when a delta has been received for a registered Thing Shadow.
+* `thingName` name of the Thing Shadow that has received a delta
+* `stateObject` the stateObject returned for the operation
+
+### Event `'timeout'`
+
+`function(thingName, clientToken) {}`
+
+Emitted when an operation `update|get|delete` has timed out.
+* `thingName` name of the Thing Shadow that has received a timeout
+* `clientToken` the operation's clientToken
+
+Applications can use clientToken values to correlate timeout events with the
+operations that they are associated with by saving the clientTokens returned
+from each operation.
+
+-------------------------------------------------------
+
+### awsIot.thingShadow#register(thingName, [options] )
+
+Register interest in the Thing Shadow named `thingName`. The thingShadow class will
+subscribe to any applicable topics, and will fire events for the Thing Shadow
+until [awsIot.thingShadow#unregister()](#unregister) is called with `thingName`. `options`
+can contain the following arguments to modify how this Thing Shadow is processed:
+
+* `ignoreDeltas`: set to `true` to not subscribe to the `delta` sub-topic for this Thing Shadow; used in cases where the application is not interested in changes (e.g. update only.) (default `false`)
+* `persistentSubscribe`: set to `false` to unsubscribe from all operation sub-topics while not performing an operation (default `true`)
+* `discardStale`: set to `false` to allow receiving messages with old version numbers (default `true`)
+
+The `persistentSubscribe` argument allows an application to get faster operation
+responses at the expense of potentially receiving more irrelevant response
+traffic (i.e., response traffic for other clients who have registered interest
+in the same Thing Shadow). When `persistentSubscribe` is set to `true` (the default),
+`postSubscribeTimeout` is forced to 0 and the `thingShadow` class will publish
+immediately on any update, get, or delete operation for this registered Thing Shadow.
+When set to `false`, operation sub-topics are only subscribed to during the scope
+of that operation; note that in this mode, update, get, and delete operations will
+be much slower; however, the application will be less likely to receive irrelevant
+response traffic.
+
+*Note that when `persistentSubscribe` is set to `true` (the default), you must wait the `postSubscribeTimeout` (default 2.2 seconds) between registering interest in the Thing Shadow and performing the first update to it. After this time interval has expired, you can update the Thing Shadow without waiting.*
+
+The `discardStale` argument allows applications to receive messages which have
+obsolete version numbers. This can happen when messages are received out-of-order;
+applications which set this argument to `false` should use other methods to
+determine how to treat the data (e.g. use a time stamp property to know how old/stale
+it is).
+
+-------------------------------------------------------
+
+### awsIot.thingShadow#unregister(thingName)
+
+Unregister interest in the Thing Shadow named `thingName`. The thingShadow class
+will unsubscribe from all applicable topics and no more events will be fired
+for `thingName`.
+
+-------------------------------------------------------
+
+### awsIot.thingShadow#update(thingName, stateObject)
+
+Update the Thing Shadow named `thingName` with the state specified in the
+JavaScript object `stateObject`. `thingName` must have been previously
+registered
+using [awsIot.thingShadow#register()](#register). The thingShadow class will subscribe
+to all applicable topics and publish `stateObject` on the update sub-topic.
+
+This function returns a `clientToken`, which is a unique value associated with
+the update operation. When a 'status' or 'timeout' event is emitted,
+the `clientToken` will be supplied as one of the parameters, allowing the
+application to keep track of the status of each operation. The caller may
+create their own `clientToken` value; if `stateObject` contains a `clientToken`
+property, that will be used rather than the internally generated value. Note
+that it should be of atomic type (i.e. numeric or string).
+
+-------------------------------------------------------
+
+### awsIot.thingShadow#get(thingName, [clientToken])
+
+Get the current state of the Thing Shadow named `thingName`, which must have
+been previously registered using [awsIot.thingShadow#register()](#register). The
+thingShadow class will subscribe to all applicable topics and publish on the
+get sub-topic.
+
+This function returns a `clientToken`, which is a unique value associated with
+the get operation. When a 'status or 'timeout' event is emitted,
+the `clientToken` will be supplied as one of the parameters, allowing the
+application to keep track of the status of each operation. The caller may
+supply their own `clientToken` value (optional); if supplied, the value of
+`clientToken` will be used rather than the internally generated value. Note
+that this value should be of atomic type (i.e. numeric or string).
+
+-------------------------------------------------------
+
+### awsIot.thingShadow#delete(thingName, [clientToken])
+
+Delete the Thing Shadow named `thingName`, which must have been previously
+registered using [awsIot.thingShadow#register()](#register). The thingShadow class
+will subscribe to all applicable topics and publish on the delete
+sub-topic.
+
+This function returns a `clientToken`, which is a unique value associated with
+the delete operation. When a 'status' or 'timeout' event is emitted,
+the `clientToken` will be supplied as one of the parameters, allowing the
+application to keep track of the status of each operation. The caller may
+supply their own `clientToken` value (optional); if supplied, the value of
+`clientToken` will be used rather than the internally generated value. Note
+that this value should be of atomic type (i.e. numeric or string).
+
+-------------------------------------------------------
+
+### awsIot.thingShadow#publish(topic, message, [options], [callback])
+
+Identical to the [mqtt.Client#publish()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#publish)
+method, with the restriction that the topic may not represent a Thing Shadow.
+This method allows the user to publish messages to topics on the same connection
+used to access Thing Shadows.
+
+-------------------------------------------------------
+
+### awsIot.thingShadow#subscribe(topic, [options], [callback])
+
+Identical to the [mqtt.Client#subscribe()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#subscribe)
+method, with the restriction that the topic may not represent a Thing Shadow.
+This method allows the user to subscribe to messages from topics on the same
+connection used to access Thing Shadows.
+
+-------------------------------------------------------
+
+### awsIot.thingShadow#unsubscribe(topic, [options], [callback])
+
+Identical to the [mqtt.Client#unsubscribe()](https://github.com/mqttjs/MQTT.js/blob/master/README.md#unsubscribe)
+method, with the restriction that the topic may not represent a Thing Shadow.
+This method allows the user to unsubscribe from topics on the same
+used to access Thing Shadows.
+
+
+## Example Programs
+
+The 'examples' directory contains several programs which demonstrate usage
+of the AWS IoT APIs:
+
+* device-example.js: demonstrate simple MQTT publish and subscribe
+operations.
+
+* echo-example.js: test Thing Shadow operation by echoing all delta
+state updates to the update topic; used in conjunction with the [AWS
+IoT Console](https://console.aws.amazon.com/iot) to verify connectivity
+with the AWS IoT platform.
+
+* thing-example.js: use a Thing Shadow to automatically synchronize
+state between a simulated device and a control application.
+
+* thing-passthrough-example.js: demonstrate use of a Thing Shadow with
+pasthrough of standard MQTT publish and subscribe messages.
+
+* temperature-control.js: an interactive device simulation which uses
+Thing Shadows.
+
+The example programs use command line parameters to set options. To see
+the available options, run the program and specify the '-h' option as
+follows:
+
+```sh
+node examples/ -h
+```
+
+
+### Certificates
+
+The example programs require certificates (created using either the [AWS
+IoT Console](https://console.aws.amazon.com/iot) or the
+[AWS IoT CLI](https://aws.amazon.com/cli/)) in order to authenticate with
+AWS IoT. Each example program uses command line options to specify the
+names and/or locations of certificates as follows:
+
+#### Specify a directory containing default-named certificates
+
+```sh
+ -f, --certificate-dir=DIR look in DIR for certificates
+```
+
+The --certificate-dir (-f) option will read all certificates from the
+directory specified. Default certificate names are as follows:
+
+* certificate.pem.crt: your AWS IoT certificate
+* private.pem.key: the private key associated with your AWS IoT certificate
+* root-CA.crt: the root CA certificate [(available from Symantec here)](https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem)
+
+#### Specify certificate names and locations individually
+
+```sh
+ -k, --private-key=FILE use FILE as private key
+ -c, --client-certificate=FILE use FILE as client certificate
+ -a, --ca-certificate=FILE use FILE as CA certificate
+```
+
+The '-f' (certificate directory) option can be combined with these so that
+you don't have to specify absolute pathnames for each file.
+
+#### Use a configuration file
+
+The [AWS IoT Console](https://console.aws.amazon.com/iot) can generate JSON
+configuration data specifying the parameters required to connect a device
+to the AWS IoT Platform. The JSON configuration data includes pathnames
+to certificates, the hostname and port number, etc... The command line
+option '--configuration-file (-F)' is used when reading parameters from a
+configuration file.
+
+```sh
+ -F, --configuration-file=FILE use FILE (JSON format) for configuration
+```
+
+The configuration file is in JSON format, and may contain the following
+properties:
+
+* host - the host name to connect to
+* port - the port number to use when connecting to the host (8883 for AWS IoT)
+* clientId - the client ID to use when connecting
+* privateKey - file containing the private key
+* clientCert - file containing the client certificate
+* caCert - file containing the CA certificate
+* thingName - thing name to use
+
+The '-f' (certificate directory) and '-F' (configuration file) options
+can be combined so that you don't have to use absolute pathnames in the
+configuration file.
+
+### device-example.js
+
+device-example.js is run as two processes which communicate with one
+another via the AWS IoT platform using MQTT publish and subscribe.
+The command line option '--test-mode (-t)' is used to set which role
+each process performs. It's easiest to run each process in its own
+terminal window so that you can see the output generated by each. Note
+that in the following examples, all certificates are located in the
+~/certs directory and have the default names as specified in the
+[Certificates section](#certificates).
+
+#### _Terminal Window 1_
+```sh
+node examples/device-example.js -f ~/certs --test-mode=1
+```
+
+#### _Terminal Window 2_
+```sh
+node examples/device-example.js -f ~/certs --test-mode=2
+```
+
+### thing-example.js
+Similar to device-example.js, thing-example.js is also run as two
+processes which communicate with one another via the AWS IoT platform.
+thing-example.js uses a Thing Shadow to synchronize state between the
+two processes, and the command line option '--test-mode (-t)' is used
+to set which role each process performs. As with device-example.js,
+it's best to run each process in its own terminal window. Note
+that in the following examples, all certificates are located in the
+~/certs directory and have the default names as specified in the
+[Certificates section](#certificates).
+
+#### _Terminal Window 1_
+```sh
+node examples/thing-example.js -f ~/certs --test-mode=1
+```
+
+#### _Terminal Window 2_
+```sh
+node examples/thing-example.js -f ~/certs --test-mode=2
+```
+
+### thing-passthrough-example.js
+Similar to thing-example.js, thing-passthrough-example.js is also run
+as two processes which communicate with one another via the AWS IoT platform.
+thing-passthrough-example.js uses a Thing Shadow to synchronize state
+from one process to another, and uses MQTT publish/subscribe to send
+information in the other direction. The command line option '--test-mode (-t)'
+is used to set which role each process performs. As with thing-example.js,
+it's best to run each process in its own terminal window. Note
+that in the following examples, all certificates are located in the
+~/certs directory and have the default names as specified in the
+[Certificates section](#certificates).
+
+#### _Terminal Window 1_
+```sh
+node examples/thing-passthrough-example.js -f ~/certs --test-mode=1
+```
+
+#### _Terminal Window 2_
+```sh
+node examples/thing-passthrough-example.js -f ~/certs --test-mode=2
+```
+
+### echo-example.js
+echo-example.js is used in conjunction with the
+[AWS Iot Console](https://console.aws.amazon.com/iot) to verify
+connectivity with the AWS IoT platform and to perform interactive
+observation of Thing Shadow operation. In the following example, the
+program is run using the configuration file '../config.json', and
+the certificates are located in the '~/certs' directory. Here, the
+'-f' (certificate directory) and '-F' (configuration file) options
+are combined so that the configuration file doesn't need to contain
+absolute pathnames.
+
+```sh
+node examples/echo-example.js -F ../config.json -f ~/certs --thing-name testThing1
+```
+
+
+### temperature-control.js
+temperature-control.js is an interactive simulation which demonstrates
+how Thing Shadows can be used to easily synchronize applications
+and internet-connected devices.
+
+Like thing-example.js, temperature-control.js runs in two
+separate terminal windows and is configured via command-line options;
+in the following example, all certificates are located in the ~/certs
+directory and have the default names as specified in the
+[Certificates section](#certificates). The process running
+with '--test-mode=2' simulates an internet-connected temperature control
+device, and the process running with '--test-mode=1' simulates a mobile
+application which is monitoring/controlling it. The processes may be
+run on different hosts if desired.
+
+temperature-control.js
+uses the [blessed.js](https://github.com/chjj/blessed) and [blessed-contrib.js](https://github.com/yaronn/blessed-contrib) libraries to provide an
+interactive terminal interface; it looks best on an 80x25 terminal with a
+black background and white or green text and requires UTF-8 character
+encoding.
+#### _Terminal Window 1_
+```sh
+node examples/temperature-control.js -f ~/certs --test-mode=1
+```
+![temperature-control.js, 'mobile application' mode](https://s3.amazonaws.com/aws-iot-device-sdk-js-supplemental/images/temperature-control-mobile-app-mode.png)
+
+#### _Terminal Window 2_
+```sh
+node examples/temperature-control.js -f ~/certs --test-mode=2
+```
+![temperature-control.js, 'device' mode](https://s3.amazonaws.com/aws-iot-device-sdk-js-supplemental/images/temperature-control-device-mode.png)
+
+#### _Using the simulation_
+The simulated temperature control device has two controls; _Setpoint_ and
+_Status_. _Status_ controls whether or not the device is active, and
+_Setpoint_ controls the interior temperature the device will attempt to
+achieve. In addition, the device reports the current interior and exterior
+temperatures as well as its operating state (_heating_, _cooling_, or
+_stopped_).
+
+Two Thing Shadows are used to connect the simulated device and mobile
+application; one contains the controls and the other contains the
+measured temperatures and operating state. Both processes can update the
+controls, but only the device can update the measured temperatures and
+the operating state.
+
+Controlling the simulation is done using the up,
+down, left, right, and
+Enter keys as follows:
+
+* up increase the Setpoint
+* down decrease the Setpoint
+* left move left on the menu bar
+* right move right on the menu bar
+* Enter select the current menu option
+
+##### Operating State
+
+The operating state of the device is indicated by the color of the
+Interior temperature field as follows:
+
+* Red: _heating_
+* Cyan: _cooling_
+* White: _stopped_
+
+The following example shows the temperature control simulation in 'device' mode
+while the operating state is 'heating'.
+
+![temperature-control.js, 'device' mode, 'heating' operating state](https://s3.amazonaws.com/aws-iot-device-sdk-js-supplemental/images/temperature-control-device-mode-heating.png)
+
+##### Log
+
+The log window displays events of interest, e.g. network connectivity,
+_Status_ toggles, re-synchronization with the Thing Shadow, etc...
+
+##### Menu Options
+
+* Mode: Toggle the device _Status_. _Status_ can be controlled from both
+the simulated device and the mobile application.
+* Network: Toggle the network connectivity of the device or mobile
+application; this can be used to observe how both sides re-synchronize
+when connectivity is restored.
+
+In this example, the mobile application is disconnected from the network. Although it has
+requested that the Setpoint be lowered to 58 degrees, the command can't be sent to
+the device as there is no network connectivity, so the operating state still shows as
+'stopped'. When the mobile application is reconnected to the network, it will attempt
+to update the Thing Shadow for the device's controls; if no control changes have been
+made on the device side during the disconnection period, the device will synchronize to
+the mobile application's requested state; otherwise, the mobile application will re-
+synchronize to the device's current state.
+
+![temperature-control.js, 'mobile application' mode, network disconnected](https://s3.amazonaws.com/aws-iot-device-sdk-js-supplemental/images/temperature-control-mobile-app-mode-network-disconnected.png)
+
+##### Exiting the Simulation
+
+The simulation can be exited at any time by pressing q,
+Ctrl+c, or by selecting 'exit' on the menu bar.
+
+
+
+## License
+
+This SDK is distributed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0), see LICENSE.txt and NOTICE.txt for more information.
+
+## Support
+If you have technical questions about AWS IoT Device SDK, use the [AWS IoT Forum](https://forums.aws.amazon.com/forum.jspa?forumID=210).
+For any other questions on AWS IoT, contact [AWS Support](https://aws.amazon.com/contact-us).
diff --git a/common/lib/exceptions.js b/common/lib/exceptions.js
new file mode 100644
index 0000000..37cb3c7
--- /dev/null
+++ b/common/lib/exceptions.js
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+
+//app deps
+
+//begin module
+module.exports = {
+ INVALID_KEY_PATH_OPTION: 'Invalid "keyPath" (private key pem path) option supplied.',
+ INVALID_CERT_PATH_OPTION: 'Invalid "certPath" (certificate pem path) option supplied.',
+ INVALID_CA_PATH_OPTION: 'Invalid "caPath" (certificate authority pem path) option supplied.'
+};
diff --git a/common/lib/is-undefined.js b/common/lib/is-undefined.js
new file mode 100644
index 0000000..3ab2bef
--- /dev/null
+++ b/common/lib/is-undefined.js
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+
+//app deps
+
+//begin module
+/**
+ * This is the exposed module.
+ * This method determines if an object is undefined.
+ *
+ * @param {Object} value
+ * @access public
+ */
+module.exports = function(value) {
+ if (typeof(value) === 'undefined'
+ || typeof(value) === null) {
+ return true;
+ }
+ return false;
+}
diff --git a/common/lib/prototypes.js b/common/lib/prototypes.js
new file mode 100644
index 0000000..04ca628
--- /dev/null
+++ b/common/lib/prototypes.js
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+/**
+ * This method adds some syntatic sugar around arrays containing an object (instead of using indexOf).
+ *
+ * @param {Object} object
+ * @access public
+ */
+Array.prototype.contains = function(object) {
+ if (this.indexOf(object) !== -1) {
+ return true;
+ }
+ return false;
+};
diff --git a/common/lib/tls-reader.js b/common/lib/tls-reader.js
new file mode 100644
index 0000000..99648e1
--- /dev/null
+++ b/common/lib/tls-reader.js
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+const fs = require('fs');
+
+//npm deps
+
+//app deps
+const isUndefined = require('./is-undefined');
+
+//begin module
+/**
+ * This method is the exposed module; it validates and prepares the tls
+ * options as required for connection to the AWS IoT service.
+ *
+ * @param {Object} options
+ * @access public
+ */
+module.exports = function(options) {
+
+ // verify certificate paths
+ if (isUndefined(options.keyPath)) {
+ throw new Error(exceptions.INVALID_KEY_PATH_OPTION);
+ }
+ if (isUndefined(options.certPath)) {
+ throw new Error(exceptions.INVALID_CERT_PATH_OPTION);
+ }
+ if (isUndefined(options.caPath)) {
+ throw new Error(exceptions.INVALID_CA_PATH_OPTION);
+ }
+ //parse pem files
+ options.key = fs.readFileSync(options.keyPath);
+ options.cert = fs.readFileSync(options.certPath);
+ options.ca = fs.readFileSync(options.caPath);
+
+ // request certificate from partner
+ options.requestCert = true;
+
+ // require certificate authentication
+ options.rejectUnauthorized = true;
+
+};
diff --git a/device/index.js b/device/index.js
new file mode 100644
index 0000000..37eac69
--- /dev/null
+++ b/device/index.js
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+const mqtt = require('mqtt');
+
+//app deps
+const exceptions = require('./lib/exceptions'),
+ isUndefined = require('../common/lib/is-undefined'),
+ tlsReader = require('../common/lib/tls-reader');
+
+//begin module
+const reconnectPeriod = 3 * 1000;
+
+//
+// This method is the exposed module; it validates the mqtt options,
+// creates a secure mqtt connection via TLS, and returns the mqtt
+// connection instance.
+//
+module.exports = function(options) {
+//
+// Validate options, set default reconnect period if not specified.
+//
+ if (isUndefined(options) ||
+ Object.keys(options).length === 0 ||
+ isUndefined(options.region)) {
+ throw new Error(exceptions.INVALID_CONNECT_OPTIONS);
+ }
+ if (isUndefined(options.reconnectPeriod)) {
+ options.reconnectPeriod = reconnectPeriod;
+ }
+
+ //set port and protocol
+ options.port = 8883;
+ options.protocol = 'mqtts';
+ options.host = 'data.iot.'+options.region+'.amazonaws.com';
+
+ //read and map certificates
+ tlsReader(options);
+
+ if ((!isUndefined(options)) && (options.debug==true))
+ {
+ console.log(options);
+ console.log('attempting new mqtt connection...');
+ }
+ //connect and return the client instance to map all mqttjs apis
+ const device = mqtt.connect(options);
+
+ //handle some exceptions
+ device
+ .on('error', function(error) {
+ //certificate issue
+ if (error.code === 'EPROTO') {
+ throw new Error(error);
+ }
+ });
+ return device;
+};
diff --git a/device/lib/exceptions.js b/device/lib/exceptions.js
new file mode 100644
index 0000000..6d60219
--- /dev/null
+++ b/device/lib/exceptions.js
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+
+//app deps
+
+//begin module
+module.exports = {
+ INVALID_CONNECT_OPTIONS: 'Invalid connect options supplied.',
+ INVALID_CLIENT_ID_OPTION: 'Invalid "clientId" (mqtt client id) option supplied.'
+};
diff --git a/examples/device-example.js b/examples/device-example.js
new file mode 100644
index 0000000..ec92d28
--- /dev/null
+++ b/examples/device-example.js
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+
+//app deps
+const deviceModule = require('..').device;
+const cmdLineProcess = require('./lib/cmdline');
+
+//begin module
+
+function processTest( args, argsRemaining ) {
+//
+// The device module exports an MQTT instance, which will attempt
+// to connect to the AWS IoT endpoint configured in the arguments.
+// Once connected, it will emit events which our application can
+// handle.
+//
+const device = deviceModule({
+ keyPath: args.privateKey,
+ certPath: args.clientCert,
+ caPath: args.caCert,
+ clientId: args.clientId,
+ region: args.region,
+ reconnectPeriod: args.reconnectPeriod,
+});
+
+var timeout;
+var count=0;
+//
+// Do a simple publish/subscribe demo based on the test-mode passed
+// in the command line arguments. If test-mode is 1, subscribe to
+// 'topic_1' and publish to 'topic_2'; otherwise vice versa. Publish
+// a message every four seconds.
+//
+device
+ .on('connect', function() {
+ const minimumDelay=250;
+ console.log('connect');
+ if (args.testMode === 1)
+ {
+ device.subscribe('topic_1');
+ }
+ else
+ {
+ device.subscribe('topic_2');
+ }
+ if ((Math.max(args.delay,minimumDelay) ) != args.delay)
+ {
+ console.log( 'substituting '+ minimumDelay + 'ms delay for ' + args.delay + 'ms...' );
+ }
+ timeout = setInterval( function() {
+ count++;
+
+ if (args.testMode === 1)
+ {
+ device.publish('topic_2', JSON.stringify({
+ mode_1_process: count }));
+ }
+ else
+ {
+ device.publish('topic_1', JSON.stringify({
+ mode_2_process: count }));
+ }
+ }, Math.max(args.delay,minimumDelay) ); // clip to minimum
+ });
+device
+ .on('close', function() {
+ console.log('close');
+ clearInterval( timeout );
+ count=0;
+ });
+device
+ .on('reconnect', function() {
+ console.log('reconnect');
+ });
+device
+ .on('offline', function() {
+ console.log('offline');
+ clearInterval( timeout );
+ count=0;
+ });
+device
+ .on('error', function(error) {
+ console.log('error', error);
+ clearInterval( timeout );
+ count=0;
+ });
+device
+ .on('message', function(topic, payload) {
+ console.log('message', topic, payload.toString());
+ });
+
+}
+
+module.exports = cmdLineProcess;
+
+if (require.main === module) {
+ cmdLineProcess('connect to the AWS IoT service and publish/subscribe to topics using MQTT, test modes 1-2',
+ process.argv.slice(2), processTest );
+}
diff --git a/examples/echo-example.js b/examples/echo-example.js
new file mode 100644
index 0000000..f910d0c
--- /dev/null
+++ b/examples/echo-example.js
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+
+//app deps
+const thingShadow = require('..').thingShadow;
+const isUndefined = require('../common/lib/is-undefined');
+const cmdLineProcess = require('./lib/cmdline');
+
+//begin module
+
+function processTest( args, argsRemaining ) {
+
+if (isUndefined( args.thingName ))
+{
+ console.log( 'thing name must be specified with --thing-name' );
+ process.exit(1);
+}
+//
+// The thing module exports the thing class through which we
+// can register and unregister interest in thing shadows, perform
+// update/get/delete operations on them, and receive delta updates
+// when the cloud state differs from the device state.
+//
+const thingShadows = thingShadow({
+ keyPath: args.privateKey,
+ certPath: args.clientCert,
+ caPath: args.caCert,
+ clientId: args.clientId,
+ region: args.region,
+ reconnectPeriod: args.reconnectPeriod,
+});
+
+//
+// Register a thing name and listen for deltas. Whatever we receive on delta
+// is echoed via thing shadow updates.
+//
+thingShadows
+ .on('connect', function() {
+ console.log('connected to things instance, registering thing name');
+
+ thingShadows.register( args.thingName, { persistentSubscribe: true } );
+ });
+
+thingShadows
+ .on('close', function() {
+ thingShadows.unregister( args.thingName );
+ console.log('close');
+ });
+
+thingShadows
+ .on('reconnect', function() {
+ thingShadows.register( args.thingName, { persistentSubscribe: true } );
+ console.log('reconnect');
+ });
+
+thingShadows
+ .on('offline', function() {
+ console.log('offline');
+ });
+
+thingShadows
+ .on('error', function(error) {
+ console.log('error', error);
+ });
+
+thingShadows
+ .on('delta', function(thingName, stateObject) {
+ console.log('received delta on '+thingName+': '+
+ JSON.stringify(stateObject));
+ thingShadows.update( thingName, { state: { reported: stateObject.state } } );
+ });
+
+thingShadows
+ .on('timeout', function(thingName, clientToken) {
+ console.warn( 'timeout: '+thingName);
+ });
+}
+
+module.exports = cmdLineProcess;
+
+if (require.main === module) {
+ cmdLineProcess('connect to the AWS IoT service and perform thing shadow echo',
+ process.argv.slice(2), processTest, ' ', true );
+}
diff --git a/examples/lib/cmdline.js b/examples/lib/cmdline.js
new file mode 100644
index 0000000..6e521e3
--- /dev/null
+++ b/examples/lib/cmdline.js
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+const fs = require('fs');
+
+//npm deps
+const minimist = require('minimist');
+
+//app deps
+const isUndefined = require('../../common/lib/is-undefined');
+
+//begin module
+const clientIdDefault = process.env.USER.concat(Math.floor((Math.random() * 100000) + 1));
+module.exports = function( description, args, processFunction, argumentHelp ) {
+ doHelp = function() {
+ var progName=process.argv[1];
+ var lastSlash=progName.lastIndexOf('/');
+ if (lastSlash !=-1)
+ {
+ progName=progName.substring(lastSlash+1,progName.length);
+ }
+ if (isUndefined(argumentHelp)) {
+ console.log('Usage: ' + progName + ' [OPTION...]');
+ }
+ else
+ {
+ console.log('Usage: ' + progName + ' [OPTION...] ARGUMENTS...');
+ }
+ console.log('\n' + progName + ': ' + description + '\n\n' +
+ ' Options\n\n' +
+ ' -g, --aws-region=REGION AWS IoT region\n' +
+ ' -i, --client-id=ID use ID as client ID\n' +
+ ' -k, --private-key=FILE use FILE as private key\n' +
+ ' -c, --client-certificate=FILE use FILE as client certificate\n' +
+ ' -a, --ca-certificate=FILE use FILE as CA certificate\n' +
+ ' -f, --certificate-dir=DIR look in DIR for certificates\n' +
+ ' -F, --configuration-file=FILE use FILE (JSON format) for configuration\n' +
+ ' -r, --reconnect-period-ms=VALUE use VALUE as the reconnect period (ms)\n' +
+ ' -t, --test-mode=[1-n] set test mode for multi-process tests\n' +
+ ' -T, --thing-name=THINGNAME access thing shadow named THINGNAME\n' +
+ ' -d, --delay-ms=VALUE delay in milliseconds before publishing\n\n' +
+ ' Default values\n\n' +
+ ' aws-region us-east-1\n' +
+ ' client-id $USER\n' +
+ ' private-key private.pem.key\n' +
+ ' client-certificate certificate.pem.crt\n' +
+ ' ca-certificate root-CA.crt\n' +
+ ' reconnect-period-ms 3000ms\n' +
+ ' delay-ms 4000ms\n' +
+ ' test-mode 1\n');
+ if (!isUndefined(argumentHelp)) {
+ console.log(argumentHelp);
+ }
+ };
+ args = minimist(args, {
+ string: ['certificate-dir', 'aws-region', 'private-key', 'client-certificate',
+ 'ca-certificate', 'client-id', 'thing-name', 'configuration-file' ],
+ integer: [ 'reconnect-period-ms', 'test-mode', 'delay-ms' ],
+ boolean: ['help'],
+ alias: {
+ region: ['g', 'aws-region'],
+ clientId: ['i', 'client-id'],
+ privateKey: ['k', 'private-key'],
+ clientCert: ['c', 'client-certificate'],
+ caCert: ['a', 'ca-certificate'],
+ certDir: ['f', 'certificate-dir'],
+ configFile: ['F', 'configuration-file'],
+ reconnectPeriod: ['r', 'reconnect-period-ms'],
+ testMode: ['t', 'test-mode'],
+ thingName: ['T', 'thing-name'],
+ delay: ['d', 'delay-ms'],
+ debug: 'D',
+ help: 'h'
+ },
+ default: {
+ region: 'us-east-1',
+ clientId: clientIdDefault,
+ privateKey: 'private.pem.key',
+ clientCert: 'certificate.pem.crt',
+ caCert: 'root-CA.crt',
+ testMode: 1,
+ reconnectPeriod: 3*1000, /* milliseconds */
+ delay: 4*1000, /* milliseconds */
+ },
+ unknown: function() {console.error('***unrecognized options***'); doHelp(); process.exit(1); }
+ });
+ if (args.help) {
+ doHelp();
+ return;
+ }
+//
+// If the user has specified a directory where certificates are located,
+// prepend it to all of the certificate filenames.
+//
+ if (!isUndefined( args.certDir ))
+ {
+ args.privateKey=args.certDir+'/'+args.privateKey;
+ args.clientCert=args.certDir+'/'+args.clientCert;
+ args.caCert=args.certDir+'/'+args.caCert;
+ }
+//
+// If the configuration file is defined, read it in and set the parameters based
+// on the values inside; these will override any other arguments specified on
+// the command line.
+//
+ if (!isUndefined( args.configFile ))
+ {
+ if (!fs.existsSync( args.configFile ))
+ {
+ console.error( '\n' + args.configFile + ' doesn\'t exist (--help for usage)\n');
+ return;
+ }
+ var config = JSON.parse( fs.readFileSync( args.configFile, 'utf8' ));
+
+ if (!isUndefined( config.privateKey ))
+ {
+ if (!isUndefined( args.certDir ))
+ {
+ args.privateKey=args.certDir+'/'+config.privateKey;
+ }
+ else
+ {
+ args.privateKey = config.privateKey;
+ }
+ }
+ if (!isUndefined( config.clientCert ))
+ {
+ if (!isUndefined( args.certDir ))
+ {
+ args.clientCert=args.certDir+'/'+config.clientCert;
+ }
+ else
+ {
+ args.clientCert = config.clientCert;
+ }
+ }
+ if (!isUndefined( config.caCert ))
+ {
+ if (!isUndefined( args.certDir ))
+ {
+ args.caCert=args.certDir+'/'+config.caCert;
+ }
+ else
+ {
+ args.caCert = config.caCert;
+ }
+ }
+ if (!isUndefined( config.host ))
+ {
+ args.host = config.host;
+ }
+ if (!isUndefined( config.port ))
+ {
+ args.port = config.port;
+ }
+ if (!isUndefined( config.clientId ))
+ {
+ args.clientId = config.clientId;
+ }
+ if (!isUndefined( config.thingName ))
+ {
+ args.thingName = config.thingName;
+ }
+ }
+
+//
+// Client certificate, private key, and CA certificate must all exist.
+//
+ if (!fs.existsSync( args.privateKey ))
+ {
+ console.error( '\n' + args.privateKey + ' doesn\'t exist (--help for usage)\n');
+ return;
+ }
+ if (!fs.existsSync( args.clientCert ))
+ {
+ console.error( '\n' + args.clientCert + ' doesn\'t exist (--help for usage)\n');
+ return;
+ }
+ if (!fs.existsSync( args.caCert ))
+ {
+ console.error( '\n' + args.caCert + ' doesn\'t exist (--help for usage)\n');
+ return;
+ }
+
+ processFunction( args );
+}
diff --git a/examples/temperature-control.js b/examples/temperature-control.js
new file mode 100644
index 0000000..1354b88
--- /dev/null
+++ b/examples/temperature-control.js
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+
+//app deps
+const blessed = require('blessed');
+const contrib = require('blessed-contrib');
+const thingShadow = require('..').thingShadow;
+const isUndefined = require('../common/lib/is-undefined');
+const cmdLineProcess = require('./lib/cmdline');
+
+
+function processTest( args, argsRemaining ) {
+
+//
+// Construct user interface
+//
+var screen = blessed.screen( { enableInput: true });
+
+screen.title = 'AWS IoT Temperature Control Simulation';
+
+var grid = new contrib.grid( { rows: 8, cols: 12, screen: screen } );
+
+//
+// The temperature control device's control state contains:
+//
+// setPoint - If enabled, heat will be transferred between the interior
+// and exterior until the interior temperature equals this
+// value
+//
+// Type: Integer
+// Units: Degrees Fahrenheit
+// Read/Write
+//
+// enabled - Operational status of heat transfer device
+//
+// Type: Boolean
+// Units: N/A
+// Read/Write
+var deviceControlState={ setPoint: 72, enabled: true };
+
+//
+// The temperature control device's monitor state contains:
+//
+//
+// intTemp - Interior temperature
+//
+// Type: Integer
+// Units: Degrees Fahrenheit
+// Read Only
+//
+// extTemp - Exterior temperature
+//
+// Type: Integer
+// Units: Degrees Fahrenheit
+// Read Only
+//
+// curState - Current state of heat transfer device
+//
+// Type: Enum (heating|cooling|stopped)
+// Units: N/A
+// Read Only
+//
+var deviceMonitorState={ intTemp: 72, extTemp: 45, curState: 'stopped' };
+
+var networkEnabled=true;
+
+var lcd1 = grid.set( 1, 1, 2, 3, contrib.lcd, {
+label: "Setpoint",
+ segmentWidth: 0.06,
+ segmentInterval: 0.11,
+ strokeWidth: 0.1,
+ elements: 3,
+ elementSpacing: 4,
+ elementPadding: 2, color: 'green'
+
+} );
+
+var lcd2 = grid.set( 3, 1, 2, 3, contrib.lcd, {
+label: "Interior",
+ segmentWidth: 0.06,
+ segmentInterval: 0.11,
+ strokeWidth: 0.1,
+ elements: 3,
+ elementSpacing: 4,
+ elementPadding: 2, color: 'white'
+} );
+
+var lcd3 = grid.set( 5, 1, 2, 3, contrib.lcd, {
+label: "Exterior",
+ segmentWidth: 0.06,
+ segmentInterval: 0.11,
+ strokeWidth: 0.1,
+ elements: 3,
+ elementSpacing: 4,
+ elementPadding: 2, color: 'white'
+} );
+
+var lcd4 = grid.set( 2, 4, 2, 3, contrib.lcd, {
+label: "Status",
+ segmentWidth: 0.06,
+ segmentInterval: 0.11,
+ strokeWidth: 0.1,
+ elements: 3,
+ elementSpacing: 4,
+ elementPadding: 2, color: 'white'
+} );
+
+var lcd5 = grid.set( 4, 4, 2, 3, contrib.lcd, {
+label: "Network",
+ segmentWidth: 0.06,
+ segmentInterval: 0.11,
+ strokeWidth: 0.1,
+ elements: 3,
+ elementSpacing: 4,
+ elementPadding: 2, color: 'white'
+} );
+
+var log = grid.set( 1, 7, 6, 5, contrib.log, {
+label: "Log",
+} );
+
+
+
+screen.key(['escape', 'q', 'C-c'], function(ch, key) {
+ return process.exit(0);
+});
+
+
+lcd1.setDisplay(deviceControlState.setPoint+'F');
+lcd2.setDisplay(deviceMonitorState.intTemp+'F');
+lcd3.setDisplay(deviceMonitorState.extTemp+'F');
+lcd4.setDisplay(deviceControlState.enabled===true?' ON':'OFF');
+lcd5.setDisplay(networkEnabled===true?' ON':'OFF');
+
+screen.render()
+
+//
+// The thing module exports the thing class through which we
+// can register and unregister interest in thing shadows, perform
+// update/get/delete operations on them, and receive delta updates
+// when the cloud state differs from the device state.
+//
+const thingShadows = thingShadow({
+ keyPath: args.privateKey,
+ certPath: args.clientCert,
+ caPath: args.caCert,
+ clientId: args.clientId,
+ region: args.region,
+ reconnectPeriod: args.reconnectPeriod,
+});
+
+var opClientToken;
+
+var title;
+if (args.testMode === 2)
+{
+title = blessed.text( {
+ top: 0,
+ left: 'center',
+ align: 'center',
+ content: 'DEVICE SIMULATOR'
+} );
+}
+else
+{
+title = blessed.text( {
+ top: 0,
+ left: 'center',
+ align: 'center',
+ content: 'MOBILE APPLICATION SIMULATOR'
+} );
+}
+var help = blessed.text( {
+ top: 1,
+ left: 'center',
+ align: 'center',
+ content: '(use arrow keys to change temperature)'
+} );
+
+var bar = blessed.listbar({
+ //parent: screen,
+ bottom: 0,
+ left: 'center',
+// right: 3,
+ height: 'true' ? 'shrink' : 3,
+// mouse: true,
+ keys: true,
+ autoCommandKeys: false,
+ border: 'line',
+ vi: true,
+ style: {
+ bg: 'black',
+ item: {
+ bg: 'black',
+ hover: {
+ bg: 'blue'
+ },
+ //focus: {
+ // bg: 'blue'
+ //}
+ },
+ selected: {
+ bg: 'blue'
+ }
+ },
+ commands: {
+ 'mode': {
+ callback: function() {
+ var enabledStatus=( deviceControlState.enabled===true?'OFF':' ON');
+ deviceControlState.enabled=(deviceControlState.enabled===true?false:true);
+ log.log('temperature control '+(deviceControlState.enabled?'enabled':'disabled'));
+
+ if (networkEnabled===true)
+ {
+ opClientToken = thingShadows.update('TemperatureControl', { state: { desired: deviceControlState } });
+ }
+ lcd4.setDisplay( enabledStatus );
+ screen.render();
+ }
+ },
+ 'network': {
+ callback: function() {
+ var networkStatus=( networkEnabled===true ? 'OFF' : ' ON');
+ networkEnabled=( networkEnabled===true ? false : true);
+ lcd5.setDisplay( networkStatus );
+ log.log( 'network '+(networkEnabled?'connected':'disconnected'));
+//
+// Simulate a network connection/disconnection
+//
+ if (networkEnabled)
+ {
+ thingShadows.setConnectionStatus(true);
+
+//
+// After re-connecting, try to update with our current state; this will
+// get a 'rejected' status if another entity has updated this thing shadow
+// in the meantime and we will have to re-sync.
+//
+ thingShadows.update( 'TemperatureControl', { state: { desired: deviceControlState } } );
+ }
+ else
+ {
+ thingShadows.setConnectionStatus(false);
+ }
+ screen.render();
+ }
+ },
+ 'exit': {
+ callback: function() {
+ process.exit(0);
+ }
+ }
+ }
+});
+
+screen.append(bar);
+screen.append(help);
+screen.append(title);
+
+bar.focus();
+
+
+screen.key('up', function( ch, key ) {
+if (deviceControlState.setPoint < 90)
+{
+ deviceControlState.setPoint++;
+ lcd1.setDisplay(deviceControlState.setPoint+'F');
+ if (networkEnabled===true)
+ {
+ opClientToken = thingShadows.update('TemperatureControl', { state: { desired: deviceControlState } });
+ }
+ screen.render()
+}
+});
+screen.key('down', function( ch, key ) {
+if (deviceControlState.setPoint > 50)
+{
+ deviceControlState.setPoint--;
+ lcd1.setDisplay(deviceControlState.setPoint+'F');
+ if (networkEnabled===true)
+ {
+ opClientToken = thingShadows.update('TemperatureControl', { state: { desired: deviceControlState } });
+ }
+ screen.render()
+}
+});
+
+//
+// Simulate the interaction of a mobile application and a remote device via the
+// AWS IoT service. The simulated remote device is a temperature controller.
+// Two thing shadows are used: one for control properties (setpoint and on/off)
+// and the other for monitoring properties (interior/exterior temperature, current
+// state).
+//
+thingShadows
+ .on('connect', function() {
+ log.log('connected to AWS IoT...');
+
+ thingShadows.register( 'TemperatureControl', {
+ persistentSubscribe: true } );
+ thingShadows.register( 'TemperatureStatus', {
+ persistentSubscribe: true } );
+ log.log('registered thing shadows...');
+//
+// After registering, wait for a few seconds and then ask for the
+// current state of the thing shadow.
+//
+ setTimeout( function() {
+ opClientToken = thingShadows.get('TemperatureControl');
+ }, 2000 );
+ });
+
+thingShadows
+ .on('close', function() {
+ thingShadows.unregister( 'TemperatureControl' );
+ thingShadows.unregister( 'TemperatureStatus' );
+ log.log('close');
+ });
+
+thingShadows
+ .on('reconnect', function() {
+ log.log('reconnect/re-register');
+//
+// Upon reconnection, re-register our thing shadows.
+//
+ thingShadows.register( 'TemperatureControl', {
+ persistentSubscribe: true } );
+ thingShadows.register( 'TemperatureStatus', {
+ persistentSubscribe: true } );
+//
+// After re-registering, wait for a few seconds and then try to update
+// with our current state
+//
+ setTimeout( function() {
+ opClientToken = thingShadows.update('TemperatureControl', { state: { desired: deviceControlState } });
+ }, 2000 );
+ });
+
+thingShadows
+ .on('offline', function() {
+ log.log('offline');
+ });
+
+thingShadows
+ .on('error', function(error) {
+ log.log('error', error);
+ });
+
+thingShadows
+ .on('message', function(topic, payload) {
+ log.log('message', topic, payload.toString());
+ });
+
+thingShadows
+ .on('status', function(thingName, statusType, clientToken, stateObject) {
+ if ((networkEnabled===true) && (statusType === 'rejected'))
+ {
+//
+// If an operation is rejected it is likely due to a version conflict;
+// request the latest version so that we synchronize with the thing
+// shadow. The most notable exception to this is if the thing shadow
+// has not yet been created or has been deleted.
+//
+ if (stateObject.code !== 404)
+ {
+ log.log('resync with thing shadow');
+ opClientToken = thingShadows.get(thingName);
+ }
+ }
+ if (statusType === 'accepted')
+ {
+ if (thingName==='TemperatureControl')
+ {
+ deviceControlState = stateObject.state.desired;
+ lcd1.setDisplay(deviceControlState.setPoint+'F');
+ lcd4.setDisplay(deviceControlState.enabled===true?' ON':'OFF');
+ screen.render()
+ }
+ }
+ });
+
+thingShadows
+ .on('delta', function(thingName, stateObject) {
+ if (networkEnabled===true)
+ {
+ if (thingName==='TemperatureControl')
+ {
+ if (!isUndefined(stateObject.state.enabled) &&
+ (stateObject.state.enabled !== deviceControlState.enabled))
+ {
+ log.log('temperature control '+(stateObject.state.enabled?'enabled':'disabled'));
+ }
+ deviceControlState=stateObject.state;
+ lcd1.setDisplay(deviceControlState.setPoint+'F');
+ lcd4.setDisplay(deviceControlState.enabled===true?' ON':'OFF');
+ }
+ else if (thingName==='TemperatureStatus')
+ {
+ if (!isUndefined(stateObject.state.intTemp))
+ {
+ deviceMonitorState.intTemp=stateObject.state.intTemp;
+ }
+ if (!isUndefined(stateObject.state.extTemp))
+ {
+ deviceMonitorState.extTemp=stateObject.state.extTemp;
+ }
+ if (!isUndefined(stateObject.state.curState))
+ {
+ deviceMonitorState.curState=stateObject.state.curState;
+ }
+ }
+ screen.render()
+ }
+ });
+
+//
+// If we are acting in the device simulation role, we simulate the
+// action of the heat transfer device here.
+//
+ if (args.testMode === 2)
+ {
+ var transferRate = 0.1; // degrees Fahrenheit/second
+ var leakRate = 0.005; // degrees Fahrenheit/second
+ var interiorTemp = deviceMonitorState.intTemp;
+
+ setInterval( function() {
+ var difference;
+ var logMsg = '';
+//
+// If the device is enabled, the internal temperature will move towards
+// the setpoint; otherwise, it will move towards the external temperature.
+//
+ if (deviceControlState.enabled)
+ {
+ difference=deviceMonitorState.intTemp-deviceControlState.setPoint;
+ }
+ else
+ {
+ difference=deviceMonitorState.intTemp-deviceMonitorState.extTemp;
+ }
+ interiorTemp -= (difference * transferRate);
+ interiorTemp -= leakRate;
+ deviceMonitorState.intTemp = Math.floor(interiorTemp);
+ if (deviceControlState.enabled)
+ {
+ if (difference > transferRate)
+ {
+ deviceMonitorState.curState = 'cooling';
+ }
+ else if (difference < -transferRate)
+ {
+ deviceMonitorState.curState = 'heating';
+ }
+ else
+ {
+ deviceMonitorState.curState = 'stopped';
+ }
+ }
+ else
+ {
+ deviceMonitorState.curState = 'stopped';
+ }
+ if (networkEnabled===true)
+ {
+ opClientToken = thingShadows.update('TemperatureStatus',
+ { state: { desired: deviceMonitorState } });
+ }
+ }, 1000 );
+ }
+//
+// The interior temperature LCD also displays the status of the heat
+// transfer device.
+//
+ setInterval( function() {
+ var displayOptions = { color: 'white' };
+ if (deviceMonitorState.curState === 'cooling')
+ {
+ displayOptions = { color: 'cyan' };
+ }
+ else if (deviceMonitorState.curState === 'heating')
+ {
+ displayOptions = { color: 'red' };
+ }
+ lcd2.setDisplay(deviceMonitorState.intTemp+'F');
+ lcd3.setDisplay(deviceMonitorState.extTemp+'F');
+ lcd2.setOptions( displayOptions );
+ screen.render();
+ }, 250 );
+}
+
+module.exports = cmdLineProcess;
+
+if (require.main === module) {
+ cmdLineProcess('connect to the AWS IoT service and demonstrate thing shadow APIs, test modes 1-2',
+ process.argv.slice(2), processTest );
+}
diff --git a/examples/thing-example.js b/examples/thing-example.js
new file mode 100644
index 0000000..f77aa3c
--- /dev/null
+++ b/examples/thing-example.js
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+
+//app deps
+const thingShadow = require('..').thingShadow;
+const isUndefined = require('../common/lib/is-undefined');
+const cmdLineProcess = require('./lib/cmdline');
+
+//begin module
+
+function processTest( args, argsRemaining ) {
+//
+// The thing module exports the thing class through which we
+// can register and unregister interest in thing shadows, perform
+// update/get/delete operations on them, and receive delta updates
+// when the cloud state differs from the device state.
+//
+const thingShadows = thingShadow({
+ keyPath: args.privateKey,
+ certPath: args.clientCert,
+ caPath: args.caCert,
+ clientId: args.clientId,
+ region: args.region,
+ reconnectPeriod: args.reconnectPeriod,
+});
+
+//
+// Track operations in here using clientTokens as indices.
+//
+var operationCallbacks = { };
+
+var role='DEVICE';
+
+if (args.testMode===1)
+{
+ role='MOBILE APP'
+}
+var rgbValues={ red: 0, green: 0, blue: 0 };
+
+var mobileAppOperation='update';
+//
+// Simulate the interaction of a mobile device and a remote thing via the
+// AWS IoT service. The remote thing will be a dimmable color lamp, where
+// the individual RGB channels can be set to an intensity between 0 and 255.
+// One process will simulate each side, with testMode being used to distinguish
+// between the mobile app (1) and the remote thing (2). The mobile app
+// will wait a random number of seconds and then change the LED lamp's values;
+// the LED lamp will synchronize with them upon receipt of an .../update/delta.
+//
+thingShadows
+ .on('connect', function() {
+ console.log('connected to things instance, registering thing name');
+
+ if (args.testMode === 1)
+ {
+ thingShadows.register( 'RGBLedLamp', { ignoreDeltas: true,
+ persistentSubscribe: true } );
+ }
+ else
+ {
+ thingShadows.register( 'RGBLedLamp' );
+ }
+ var count=0;
+ var rgbLedLampState = { };
+
+ var opFunction = function() {
+ if (args.testMode === 1)
+ {
+//
+// The mobile app sets new values for the LED lamp.
+//
+ rgbValues.red = Math.floor(Math.random() * 255);
+ rgbValues.green = Math.floor(Math.random() * 255);
+ rgbValues.blue = Math.floor(Math.random() * 255);
+
+ rgbLedLampState={state: { desired: rgbValues }};
+ }
+
+ var clientToken;
+
+ if (args.testMode === 1)
+ {
+ if (mobileAppOperation === 'update')
+ {
+ clientToken = thingShadows[mobileAppOperation]('RGBLedLamp',
+ rgbLedLampState );
+ }
+ else // mobileAppOperation === 'get'
+ {
+ clientToken = thingShadows[mobileAppOperation]('RGBLedLamp' );
+ }
+ operationCallbacks[clientToken] = { operation: mobileAppOperation,
+ cb: null };
+//
+// Force the next operation back to update in case we had to do a get after
+// a 'rejected' status.
+//
+ mobileAppOperation = 'update';
+ }
+ else
+ {
+//
+// The device gets the latest state from the thing shadow after connecting.
+//
+ clientToken = thingShadows.get('RGBLedLamp');
+ operationCallbacks[clientToken] = { operation: 'get', cb: null };
+ }
+ if (args.testMode === 1)
+ {
+ operationCallbacks[clientToken].cb =
+ function( thingName, operation, statusType, stateObject ) {
+
+ console.log(role+':'+operation+' '+statusType+' on '+thingName+': '+
+ JSON.stringify(stateObject));
+//
+// If this operation was rejected, force a 'get' as the next operation; it is
+// probably a version conflict, and it can be resolved by simply getting the
+// latest thing shadow.
+//
+ if (statusType != 'accepted')
+ {
+ mobileAppOperation = 'get';
+ }
+ opFunction();
+ };
+ }
+ else
+ {
+ operationCallbacks[clientToken].cb =
+ function( thingName, operation, statusType, stateObject ) {
+
+ console.log(role+':'+operation+' '+statusType+' on '+thingName+': '+
+ JSON.stringify(stateObject));
+ };
+ }
+ };
+ opFunction();
+ });
+thingShadows
+ .on('close', function() {
+ console.log('close');
+ thingShadows.unregister( 'RGBLedLamp' );
+ });
+thingShadows
+ .on('reconnect', function() {
+ console.log('reconnect');
+ if (args.testMode === 1)
+ {
+ thingShadows.register( 'RGBLedLamp', { ignoreDeltas: true,
+ persistentSubscribe: true } );
+ }
+ else
+ {
+ thingShadows.register( 'RGBLedLamp' );
+ }
+ });
+thingShadows
+ .on('offline', function() {
+ console.log('offline');
+ });
+thingShadows
+ .on('error', function(error) {
+ console.log('error', error);
+ });
+thingShadows
+ .on('message', function(topic, payload) {
+ console.log('message', topic, payload.toString());
+ });
+thingShadows
+ .on('status', function(thingName, stat, clientToken, stateObject) {
+ if (!isUndefined( operationCallbacks[clientToken] ))
+ {
+ setTimeout( function() {
+ operationCallbacks[clientToken].cb( thingName,
+ operationCallbacks[clientToken].operation,
+ stat,
+ stateObject );
+
+ delete operationCallbacks[clientToken];
+ }, 2000 );
+ }
+ else
+ {
+ console.warn( 'status:unknown clientToken \''+clientToken+'\' on \''+
+ thingName+'\'' );
+ }
+ });
+//
+// Only the simulated device is interested in delta events.
+//
+if (args.testMode===2)
+{
+ thingShadows
+ .on('delta', function(thingName, stateObject) {
+ console.log(role+':delta on '+thingName+': '+
+ JSON.stringify(stateObject));
+ rgbValues=stateObject.state;
+ });
+}
+
+thingShadows
+ .on('timeout', function(thingName, clientToken) {
+ if (!isUndefined( operationCallbacks[clientToken] ))
+ {
+ operationCallbacks[clientToken].cb( thingName,
+ operationCallbacks[clientToken].operation,
+ 'timeout',
+ { } );
+ delete operationCallbacks[clientToken];
+ }
+ else
+ {
+ console.warn( 'timeout:unknown clientToken \''+clientToken+'\' on \''+
+ thingName+'\'' );
+ }
+ });
+}
+
+module.exports = cmdLineProcess;
+
+if (require.main === module) {
+ cmdLineProcess('connect to the AWS IoT service and demonstrate thing shadow APIs, test modes 1-2',
+ process.argv.slice(2), processTest );
+}
diff --git a/examples/thing-passthrough-example.js b/examples/thing-passthrough-example.js
new file mode 100644
index 0000000..250c83a
--- /dev/null
+++ b/examples/thing-passthrough-example.js
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+
+//npm deps
+
+//app deps
+const thingShadow = require('..').thingShadow;
+const cmdLineProcess = require('./lib/cmdline');
+
+//begin module
+
+function processTest( args, argsRemaining ) {
+//
+// Instantiate the thing shadow class.
+//
+const thingShadows = thingShadow({
+ keyPath: args.privateKey,
+ certPath: args.clientCert,
+ caPath: args.caCert,
+ clientId: args.clientId,
+ region: args.region,
+ reconnectPeriod: args.reconnectPeriod,
+});
+
+var count=1;
+var clientToken;
+var timer;
+
+//
+// This test demonstrates the use of thing shadows along with
+// non-thing topics. One process updates a thing shadow and
+// subscribes to a non-thing topic; the other receives delta
+// updates on the thing shadow on publishes to the non-thing
+// topic.
+//
+function updateThingShadow( )
+{
+ console.log('updating thing shadow...');
+ clientToken = thingShadows.update( 'thingShadow1',
+ { state: { desired: { value: count++ }}} );
+}
+
+thingShadows
+ .on('connect', function() {
+ console.log('connected to things instance, registering thing name');
+
+ if (args.testMode === 1)
+ {
+//
+// This process will update a thing shadow and subscribe to a non-
+// thing topic.
+//
+ thingShadows.register( 'thingShadow1', { ignoreDeltas: true } );
+ console.log('subscribing to non-thing topic');
+ thingShadows.subscribe( 'nonThingTopic1' );
+//
+// Begin by updating the state on the thing shadow; this will start the
+// exchange between the two processes.
+//
+ setTimeout( updateThingShadow(), 3000 );
+//
+// If no message has been received after 10 seconds, try again.
+//
+ timer = setInterval( function() {
+ updateThingShadow();
+ }, 10000 );
+ }
+ else
+ {
+//
+// This process will listen to deltas on a thing shadow and publish to a
+// non-thing topic.
+//
+ thingShadows.register( 'thingShadow1' );
+ }
+ });
+thingShadows
+ .on('close', function() {
+ console.log('close');
+ thingShadows.unregister( 'thingShadow1' );
+ if (args.testMode === 1)
+ {
+ clearInterval( timer );
+ timer=null;
+ }
+ });
+thingShadows
+ .on('reconnect', function() {
+ console.log('reconnect');
+ if (args.testMode === 1)
+ {
+ thingShadows.register( 'thingShadow1', { ignoreDeltas: true } );
+ thingShadows.subscribe( 'nonThingTopic1' );
+//
+// Begin by updating the state on the thing shadow; this will start the
+// exchange between the two processes.
+//
+ setTimeout( updateThingShadow(), 3000 );
+//
+// If no message has been received after 10 seconds, try again.
+//
+ timer = setInterval( function() {
+ updateThingShadow();
+ }, 10000 );
+ }
+ else
+ {
+ thingShadows.register( 'thingShadow1' );
+ }
+ });
+thingShadows
+ .on('offline', function() {
+ console.log('offline');
+ });
+thingShadows
+ .on('error', function(error) {
+ console.log('error', error);
+ });
+thingShadows
+ .on('message', function(topic, payload) {
+ console.log('received on \''+topic+'\': '+ payload.toString());
+ clearInterval( timer );
+//
+// After a few seconds, update the thing shadow and if no message has
+// been received after 10 seconds, try again.
+//
+ setTimeout( function() {
+ updateThingShadow();
+ timer = setInterval( function() {
+ updateThingShadow();
+ }, 10000 );
+ }, 3000 );
+
+ });
+//
+// Only the second process is interested in delta events.
+//
+if (args.testMode===2)
+{
+ thingShadows
+ .on('delta', function(thingName, stateObject) {
+ console.log('received delta on '+thingName+', publishing on non-thing topic...');
+ thingShadows.publish( 'nonThingTopic1',
+ JSON.stringify({ message: 'received '+
+ JSON.stringify(stateObject.state) } ));
+ });
+}
+
+thingShadows
+ .on('status', function(thingName, statusType, clientToken, stateObject) {
+ if (statusType !== 'accepted')
+ {
+//
+// This update wasn't accepted; do a get operation to re-sync. Wait
+// a few seconds, then get the thing shadow to re-sync; restart the
+// interval timer.
+//
+ clearInterval( timer );
+
+ console.log('status: '+statusType+', state: '+
+ JSON.stringify(stateObject));
+ setTimeout( function() {
+ clientToken = thingShadows.get( 'thingShadow1' );
+ timer = setInterval( function() {
+ updateThingShadow();
+ }, 10000 );
+ }, 3000 );
+ }
+ });
+}
+
+module.exports = cmdLineProcess;
+
+if (require.main === module) {
+ cmdLineProcess('connect to the AWS IoT service and demonstrate thing shadow APIs, test modes 1-2',
+ process.argv.slice(2), processTest );
+}
+
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..9366824
--- /dev/null
+++ b/index.js
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+/*
+* Expose AWS IoT Embedded Javascript SDK modules
+*/
+module.exports.device = require('./device');
+module.exports.thingShadow = require('./thing');
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6db6289
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "aws-iot-device-sdk",
+ "description": "AWS IoT JavaScript SDK for Embedded Devices",
+ "version": "1.0.6",
+ "author": {
+ "name":"Amazon Web Services",
+ "email":"",
+ "url":"http://aws.amazon.com"
+ },
+ "homepage": "https://github.com/aws/aws-iot-device-sdk-js",
+ "contributors": [
+ "Gary Wicker ",
+ "Frank Lovecchio "
+ ],
+ "main": "index.js",
+ "engines": {
+ "node": ">=0.8.6"
+ },
+ "repository": {
+ "type" : "git",
+ "url": "git://github.com/aws/aws-iot-device-sdk-js"
+ },
+ "bugs" : {
+ "url" : "http://github.com/aws/aws-iot-device-sdk-js/issues",
+ "mail" : "IotDeviceSDKSupport@amazon.com"
+ },
+ "license": "Apache-2.0",
+ "keywords" : ["api", "amazon", "aws", "iot", "mqtt"],
+ "dependencies": {
+ "mqtt": "1.4.3",
+ "minimist": "1.2.0",
+ "blessed": "0.1.81",
+ "blessed-contrib": "2.3.3"
+ }
+}
diff --git a/thing/index.js b/thing/index.js
new file mode 100644
index 0000000..0edc0f2
--- /dev/null
+++ b/thing/index.js
@@ -0,0 +1,698 @@
+/*
+ * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+//node.js deps
+const events = require('events');
+const inherits = require('util').inherits;
+
+//npm deps
+
+//app deps
+const deviceModule = require('../device');
+const isUndefined = require('../common/lib/is-undefined');
+
+//
+// private functions
+//
+function buildThingShadowTopic( thingName, operation, type )
+{
+ if (!isUndefined(type)) {
+ return '$aws/things/'+thingName+'/shadow/'+operation+'/'+type;
+ }
+ return '$aws/things/'+thingName+'/shadow/'+operation;
+}
+
+function isReservedTopic( topic )
+{
+ if (topic.substring( 0, 12 ) == '$aws/things/')
+ {
+ return true;
+ }
+ return false;
+}
+
+function isThingShadowTopic( topicTokens, direction )
+{
+ var rc = false;
+ if (topicTokens[0] = '$aws')
+ {
+//
+// Thing shadow topics have the form:
+//
+// $aws/things/{thingName}/shadow/{Operation}/{Status}
+//
+// Where {Operation} === update|get|delete
+// And {Status} === accepted|rejected|delta
+//
+ if ((topicTokens[1] === 'things') &&
+ (topicTokens[3] === 'shadow') &&
+ ((topicTokens[4] === 'update') ||
+ (topicTokens[4] === 'get') ||
+ (topicTokens[4] === 'delete')))
+ {
+//
+// Looks good so far; now check the direction and see if
+// still makes sense.
+//
+ if (direction === 'subscribe')
+ {
+ if (((topicTokens[5] === 'accepted')||
+ (topicTokens[5] === 'rejected')||
+ (topicTokens[5] === 'delta')) &&
+ (topicTokens.length === 6))
+ {
+ rc = true;
+ }
+ }
+ else // direction === 'publish'
+ {
+ if (topicTokens.length === 5)
+ {
+ rc = true;
+ }
+ }
+ }
+ }
+ return rc;
+}
+
+//begin module
+
+function thingShadowsClient( deviceOptions, thingShadowOptions ) {
+//
+// Force instantiation using the 'new' operator; this will cause inherited
+// constructors (e.g. the 'events' class) to be called.
+//
+ if (!(this instanceof thingShadowsClient))
+ {
+ return new thingShadowsClient( deviceOptions, thingShadowOptions );
+ }
+
+//
+// A copy of 'this' for use inside of closures
+//
+ var that = this;
+
+//
+// Track Thing Shadow registrations in here.
+//
+ var thingShadows = [{ }];
+
+//
+// Implements for every operation, used to construct clientToken.
+//
+ var operationCount = 0;
+
+//
+// Operation timeout (milliseconds). If no accepted or rejected response
+// to a thing operation is received within this time, subscriptions
+// to the accepted and rejected sub-topics for a thing are cancelled.
+//
+ var operationTimeout = 10000; /* milliseconds */
+//
+// Post-subscribe timeout used when not in persistent subscribe mode.
+//
+ var postSubscribeTimeout=2200; /* milliseconds */
+
+//
+// Variable used by the testing API setConnectionStatus() to simulate
+// network connectivity failures.
+//
+ var connected=true;
+
+//
+// Instantiate the device.
+//
+ const device = deviceModule( deviceOptions );
+
+ if (!isUndefined( thingShadowOptions ))
+ {
+ if (!isUndefined( thingShadowOptions.operationTimeout ))
+ {
+ operationTimeout = thingShadowOptions.operationTimeout;
+ }
+ if (!isUndefined( thingShadowOptions.postSubscribeTimeout ))
+ {
+ postSubscribeTimeout = thingShadowOptions.postSubscribeTimeout;
+ }
+ }
+
+//
+// Private function to subscribe and unsubscribe from topics.
+//
+ this._handleSubscriptions = function( thingName, operations,
+ statii, devFunction, callback )
+ {
+ var topics = [ ];
+
+//
+// Build an array of topic names.
+//
+ for (var i=0, k=0, opsLen=operations.length; i < opsLen; i++)
+ {
+ for (var j=0, statLen=statii.length; j < statLen; j++)
+ {
+ topics[k++] = buildThingShadowTopic( thingName,
+ operations[i],
+ statii[j] );
+ }
+ }
+
+ if (thingShadows[thingName].debug === true)
+ {
+ console.log( devFunction + ' on ' + topics );
+ }
+//
+// Subscribe/unsubscribe from the topics and perform callback when complete.
+//
+ if (!isUndefined( callback ))
+ {
+ device[devFunction]( topics, { qos: 0 }, callback );
+ }
+ else
+ {
+ device[devFunction]( topics );
+ }
+ }
+
+//
+// Private function to handle messages and dispatch them accordingly.
+//
+ this._handleMessages = function( thingName, operation, operationStatus, payload )
+ {
+ try
+ {
+ var stateObject = JSON.parse(payload.toString());
+ }
+ catch( err )
+ {
+ if (deviceOptions.debug === true)
+ {
+ console.error('failed parsing JSON \''+payload.toString()+'\', '+err);
+ }
+ return;
+ }
+ var clientToken = stateObject.clientToken;
+ var version = stateObject.version;
+//
+// Remove the properties 'clientToken' and 'version' from the stateObject;
+// these properties are internal to this class.
+//
+ delete stateObject.clientToken;
+ delete stateObject.version;
+//
+// Update the thing version on every accepted or delta message which
+// contains it.
+//
+ if ((!isUndefined(version)) && (operationStatus != 'rejected'))
+ {
+//
+// The thing shadow version is incremented by AWS IoT and should always
+// increase. Do not update our local version if the received version is
+// less than our version.
+//
+ if ((isUndefined(thingShadows[thingName].version)) ||
+ (version >= thingShadows[thingName].version))
+ {
+ thingShadows[thingName].version = version;
+ }
+ else
+ {
+//
+// We've received a message from AWS IoT with a version number lower than
+// we would expect; the message will be dropped because it is likely out-
+// of-date. Allow the application to override this behavior if it needs to.
+//
+ if (thingShadows[thingName].discardStale === true)
+ {
+ if (deviceOptions.debug === true)
+ {
+ console.warn('out-of-date version \''+version+'\' on \''+
+ thingName+'\' (local version \''+
+ thingShadows[thingName].version+'\')');
+ }
+ return;
+ }
+ }
+ }
+//
+// If this is a 'delta' message, emit an event for it and return.
+//
+ if (operationStatus === 'delta')
+ {
+ this.emit( 'delta', thingName, stateObject );
+ return;
+ }
+//
+// only accepted/rejected messages past this point
+// ===============================================
+// If this is an unkown clientToken (e.g., it doesn't have a corresponding
+// property in the timeouts list), do not unsubscribe from accepted/rejected.
+// Discard the message as it is not for us and continue.
+//
+ if (isUndefined(thingShadows[thingName].timeouts[clientToken]))
+ {
+ return;
+ }
+//
+// A response has been received, so cancel any outstanding timeout on this
+// thingName/clientToken, delete the timeout handle, and unsubscribe from
+// all sub-topics.
+//
+ clearTimeout(
+ thingShadows[thingName].timeouts[clientToken]);
+
+ delete thingShadows[thingName].timeouts[clientToken];
+
+//
+// Unsubscribe from the 'accepted' and 'rejected' sub-topics unless we are
+// persistently subscribed to this thing shadow.
+//
+ if (thingShadows[thingName].persistentSubscribe === false)
+ {
+ this._handleSubscriptions( thingName, [ operation ],
+ ['accepted', 'rejected'],
+ 'unsubscribe' );
+ }
+
+//
+// Emit an event detailing the operation status; the clientToken is included
+// as an argument so that the application can correlate status events to
+// the operations they are associated with.
+//
+ this.emit( 'status', thingName, operationStatus, clientToken, stateObject );
+ }
+
+ device
+ .on('connect', function() {
+ that.emit( 'connect' );
+ });
+ device.on('close', function() {
+ that.emit( 'close' );
+ });
+ device.on('reconnect', function() {
+ that.emit( 'reconnect' );
+ });
+ device.on('offline', function() {
+ that.emit( 'offline' );
+ });
+ device.on('error', function(error) {
+ that.emit( 'error', error);
+ });
+ device.on('message', function(topic, payload) {
+
+ if (connected===true)
+ {
+//
+// Parse the topic to determine what to do with it.
+//
+ var topicTokens = topic.split('/');
+//
+// First, do a rough check to see if we should continue or not.
+//
+ if (isThingShadowTopic( topicTokens, 'subscribe') )
+ {
+//
+// This looks like a valid Thing topic, so see if the Thing is in the
+// registered Thing table.
+//
+ if (thingShadows.hasOwnProperty( topicTokens[2] )) {
+//
+// This is a registered Thing, so perform message handling on it.
+//
+ that._handleMessages( topicTokens[2], // thingName
+ topicTokens[4], // operation
+ topicTokens[5], // status
+ payload );
+ }
+//
+// Any messages received for unregistered Things fall here and are ignored.
+//
+ }
+ else
+ {
+//
+// This isn't a Thing topic, so pass it along to the instance if they have
+// indicated they want to handle it.
+//
+ that.emit('message',topic,payload);
+ }
+ }
+ });
+ this._thingOperation = function( thingName, operation, stateObject )
+ {
+ var rc = null;
+
+ if (thingShadows.hasOwnProperty( thingName )) {
+//
+// If not provided, construct a clientToken from the clientId and a rolling
+// operation count. The clientToken is transmitted in any published stateObject
+// and is returned to the caller for each operation. Applications can use
+// clientToken values to correlate received responses or timeouts with
+// the original operations.
+//
+ var clientToken;
+
+ if (isUndefined(stateObject.clientToken))
+ {
+ clientToken = deviceOptions.clientId+'-'+operationCount++;
+ }
+ else
+ {
+ clientToken = stateObject.clientToken;
+ }
+
+ var publishTopic = buildThingShadowTopic( thingName,
+ operation );
+//
+// Subscribe to the 'accepted' and 'rejected' sub-topics for this get
+// operation and set a timeout beyond which they will be unsubscribed if
+// no messages have been received for either of them.
+//
+ thingShadows[thingName].timeouts[clientToken] = setTimeout(
+ function( thingName, clientToken ) {
+//
+// Timed-out. Unsubscribe from the 'accepted' and 'rejected' sub-topics unless
+// we are persistently subscribing to this thing shadow.
+//
+ if (thingShadows[thingName].persistentSubscribe === false)
+ {
+ that._handleSubscriptions( thingName,
+ [ operation ],
+ ['accepted','rejected'],
+ 'unsubscribe' );
+ }
+//
+// Emit an event for the timeout; the clientToken is included as an argument
+// so that the application can correlate timeout events to the operations
+// they are associated with.
+//
+ that.emit( 'timeout', thingName, clientToken );
+//
+// Delete the timeout handle for this thingName/clientToken combination.
+//
+ delete thingShadows[thingName].timeouts[clientToken];
+ }, operationTimeout,
+ thingName, clientToken );
+
+//
+// Subscribe to the 'accepted' and 'rejected' sub-topics unless we are
+// persistently subscribing, in which case we can publish to the topic immediately
+// since we are already subscribed to all applicable sub-topics.
+//
+ if (thingShadows[thingName].persistentSubscribe === false)
+ {
+ this._handleSubscriptions( thingName,
+ [ operation ],
+ [ 'accepted', 'rejected' ],
+ 'subscribe',
+ function(err, granted) {
+//
+// If 'stateObject' is defined, publish it to the publish topic for this
+// thingName+operation.
+//
+ if (err != null)
+ {
+ console.warn('failed subscription to accepted/rejected topics');
+ return;
+ }
+ if (!isUndefined(stateObject))
+ {
+//
+// Add the 'version' (if known) and 'clientToken' properties to the stateObject.
+//
+ if (!isUndefined(thingShadows[thingName].version))
+ {
+ stateObject.version=thingShadows[thingName].version;
+ }
+ stateObject.clientToken=clientToken;
+
+ setTimeout( function() {
+ device.publish( publishTopic, JSON.stringify(stateObject) );
+ if (!(isUndefined(thingShadows[thingName])) &&
+ thingShadows[thingName].debug === true)
+ {
+ console.log('publishing \''+JSON.stringify(stateObject)+
+ ' on \''+publishTopic+'\'');
+ }
+ }, postSubscribeTimeout );
+ }
+ });
+ }
+ else
+ {
+//
+// Add the 'version' (if known) and 'clientToken' properties to the stateObject.
+//
+ if (!isUndefined(thingShadows[thingName].version))
+ {
+ stateObject.version=thingShadows[thingName].version;
+ }
+ stateObject.clientToken=clientToken;
+
+ device.publish( publishTopic, JSON.stringify(stateObject) );
+ if (thingShadows[thingName].debug === true)
+ {
+ console.log('publishing \''+JSON.stringify(stateObject)+
+ ' on \''+publishTopic+'\'');
+ }
+ }
+ rc = clientToken; // return the clientToken to the caller
+ }
+ else
+ {
+ if (deviceOptions.debug === true)
+ {
+ console.error('attempting to '+operation+' unknown thing: ', thingName);
+ }
+ }
+ return rc;
+ }
+
+ this.register = function( thingName, options ) {
+ if (!thingShadows.hasOwnProperty( thingName )) {
+//
+// Initialize the registration entry for this thing; because the version # is
+// not yet known, do not add the property for it yet. The version number
+// property will be added after the first accepted update from AWS IoT.
+//
+ var ignoreDeltas = false;
+ thingShadows[thingName] = {timeouts: { },
+ persistentSubscribe: true,
+ debug: false,
+ discardStale: true };
+
+ if (!isUndefined( options ))
+ {
+ if (!isUndefined( options.ignoreDeltas ))
+ {
+ ignoreDeltas=options.ignoreDeltas;
+ }
+ if (!isUndefined( options.persistentSubscribe ))
+ {
+ thingShadows[thingName].persistentSubscribe=options.persistentSubscribe;
+ }
+ if (!isUndefined( options.debug ))
+ {
+ thingShadows[thingName].debug=options.debug;
+ }
+ if (!isUndefined( options.discardStale ))
+ {
+ thingShadows[thingName].discardStale=options.discardStale;
+ }
+ }
+//
+// Always listen for deltas unless requested otherwise.
+//
+ if (ignoreDeltas === false)
+ {
+ this._handleSubscriptions( thingName,
+ ['update'],
+ ['delta'],
+ 'subscribe');
+ }
+//
+// If we are persistently subscribing, we subscribe to everything we could ever
+// possibly be interested in. This will provide us the ability to publish
+// without waiting at the cost of potentially increased irrelevant traffic
+// which the application will need to filter out.
+//
+ if (thingShadows[thingName].persistentSubscribe === true)
+ {
+ this._handleSubscriptions( thingName,
+ ['update', 'get', 'delete'],
+ ['accepted', 'rejected'],
+ 'subscribe');
+ }
+ }
+ else
+ {
+ if (deviceOptions.debug === true)
+ {
+ console.error('thing already registered: ', thingName);
+ }
+ }
+ }
+
+ this.unregister = function( thingName ) {
+ if (thingShadows.hasOwnProperty( thingName )) {
+//
+// If an operation is outstanding, it will have a timeout set; when it
+// expires any accept/reject sub-topic subscriptions for the thing will be
+// deleted. If any messages arrive after the thing has been deleted, they
+// will simply be ignored as it no longer exists in the thing registrations.
+// The only sub-topic we need to unsubscribe from is the delta sub-topic,
+// which is always active.
+//
+ this._handleSubscriptions( thingName,
+ ['update'],
+ ['delta'],
+ 'unsubscribe');
+//
+// If we are persistently subscribing, we subscribe to everything we could ever
+// possibly be interested in; this means that when it's time to unregister
+// interest in a thing, we need to unsubscribe from all of these topics.
+//
+ if (thingShadows[thingName].persistentSubscribe === true)
+ {
+ this._handleSubscriptions( thingName,
+ ['update', 'get', 'delete'],
+ ['accepted', 'rejected'],
+ 'unsubscribe');
+ }
+//
+// Delete any pending timeouts
+//
+ for (var timeout in thingShadows[thingName].timeouts)
+ {
+ if (thingShadows[thingName].timeouts.hasOwnProperty(timeout))
+ {
+ clearTimeout( thingShadows[thingName].timeouts[timeout] );
+ }
+ }
+//
+// Delete the thing from the Thing registrations.
+//
+ delete thingShadows[thingName];
+ }
+ else
+ {
+ if (deviceOptions.debug === true)
+ {
+ console.error('attempting to unregister unknown thing: ', thingName);
+ }
+ }
+ }
+
+//
+// Perform an update operation on the given thing shadow.
+//
+ this.update = function( thingName, stateObject ) {
+ var rc = null;
+//
+// Verify that the message does not contain a property named 'version',
+// as these property is reserved for use within this class.
+//
+ if (isUndefined(stateObject.version))
+ {
+ rc = that._thingOperation( thingName, 'update', stateObject );
+ }
+ else
+ {
+ console.error('message can\'t contain \'version\' property');
+ }
+ return rc;
+ }
+
+//
+// Perform a get operation on the given thing shadow; allow the user
+// to specify their own client token if they don't want to use the
+// default.
+//
+ this.get = function( thingName, clientToken ) {
+ var stateObject = { };
+ if (!isUndefined( clientToken ))
+ {
+ stateObject.clientToken = clientToken;
+ }
+ return that._thingOperation( thingName, 'get', stateObject );
+ }
+
+//
+// Perform a delete operation on the given thing shadow.
+//
+ this.delete = function( thingName, clientToken ) {
+ var stateObject = { };
+ if (!isUndefined( clientToken ))
+ {
+ stateObject.clientToken = clientToken;
+ }
+ return that._thingOperation( thingName, 'delete', stateObject );
+ }
+//
+// Publish on non-thing topics.
+//
+ this.publish = function( topic, message, options, callback ) {
+ if (!isReservedTopic( topic ))
+ {
+ device.publish( topic, message, options, callback );
+ }
+ else
+ {
+ throw('cannot publish to reserved topic \''+topic+'\'');
+ }
+ }
+
+//
+// Subscribe to non-thing topics.
+//
+ this.subscribe = function( topic, options, callback ) {
+ if (!isReservedTopic( topic ))
+ {
+ device.subscribe( topic, options, callback );
+ }
+ else
+ {
+ throw('cannot subscribe to reserved topic \''+topic+'\'');
+ }
+ }
+//
+// Unsubscribe from non-thing topics.
+//
+ this.unsubscribe = function( topic, options, callback ) {
+ if (!isReservedTopic( topic ))
+ {
+ device.unsubscribe( topic, options, callback );
+ }
+ else
+ {
+ throw('cannot unsubscribe from reserved topic \''+topic+'\'');
+ }
+ }
+//
+// This is an unpublished API used for testing.
+//
+ this.setConnectionStatus = function( connectionStatus ) {
+ connected=connectionStatus;
+ }
+ events.EventEmitter.call(this);
+}
+
+//
+// Allow instances to listen in on events that we produce for them
+//
+inherits(thingShadowsClient, events.EventEmitter );
+
+module.exports = thingShadowsClient;