Skip to content

Commit 5e87e97

Browse files
committed
gg samples update
1 parent 313825e commit 5e87e97

File tree

10 files changed

+349
-1
lines changed

10 files changed

+349
-1
lines changed

samples/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This directory contains sample applications for [aws-iot-device-sdk-js-v2](../RE
66
* [Node Samples](#node-samples)
77
* [MQTT5 Client Samples](#mqtt5-client-samples)
88
* [Service Client Samples](#service-client-samples)
9-
* [Greengrass IPC Sample](./node/gg_ipc/README.md)
9+
* [Greengrass Samples](#greengrass-samples)
1010
* [Instructions](#instructions)
1111
* [Enable Logging](#enable-logging-in-node-samples)
1212
* [Installing Via NPM](#installing-via-npm)
@@ -41,6 +41,12 @@ This directory contains sample applications for [aws-iot-device-sdk-js-v2](../RE
4141
| [Basic Fleet Provisioning](./node/service_clients/fleet_provisioning/basic/README.md) | Provision a device using the Fleet Provisioning template. |
4242
| [CSR Fleet Provisioning](./node/service_clients/fleet_provisioning/csr/README.md) | Demonstrates CSR-based device certificate provisioning. |
4343

44+
### Greengrass Samples
45+
##### Samples that interact with [AWS Greengrass](https://aws.amazon.com/greengrass/).
46+
| Greengrass Sample | Description |
47+
|--------|-------------|
48+
| [Greengrass Discovery](./node/greengrass/basic_discovery/README.md) | Discover and connect to a local Greengrass core. |
49+
| [Greengrass IPC](./node/greengrass/gg_ipc/README.md) | Demonstrates Inter-Process Communication (IPC) with Greengrass components. |
4450

4551
### Instructions
4652
Node samples can be installed from the sample directory using the command:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Node: Greengrass Discovery
2+
3+
[**Return to main sample list**](../../README.md)
4+
5+
This sample is intended for use with the following tutorials in the AWS IoT Greengrass documentation:
6+
7+
* [Connect and test client devices](https://docs.aws.amazon.com/greengrass/v2/developerguide/client-devices-tutorial.html) (Greengrass V2)
8+
* [Test client device communications](https://docs.aws.amazon.com/greengrass/v2/developerguide/test-client-device-communications.html) (Greengrass V2)
9+
* [Getting Started with AWS IoT Greengrass](https://docs.aws.amazon.com/greengrass/latest/developerguide/gg-gs.html) (Greengrass V1)
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/**
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
import { mqtt, io, iot, greengrass } from 'aws-iot-device-sdk-v2';
7+
import { TextDecoder } from 'util';
8+
9+
type Args = { [index: string]: any };
10+
11+
const common_args = require('aws-iot-samples-util/cli_args');
12+
13+
const yargs = require('yargs');
14+
yargs.command('*', false, (yargs: any) => {
15+
common_args.add_universal_arguments(yargs);
16+
common_args.add_topic_message_arguments(yargs);
17+
18+
yargs
19+
.option('ca_file', {
20+
alias: 'r',
21+
description: '<path>: path to a Root CA certificate file in PEM format (optional, system trust store used by default).',
22+
type: 'string',
23+
required: false
24+
})
25+
.option('cert', {
26+
alias: 'c',
27+
description: '<path>: path to a PEM encoded certificate to use with mTLS.',
28+
type: 'string',
29+
required: true
30+
})
31+
.option('key', {
32+
alias: 'k',
33+
description: '<path>: Path to a PEM encoded private key that matches cert.',
34+
type: 'string',
35+
required: true
36+
})
37+
.option('thing_name', {
38+
alias: 'T',
39+
description: 'Targeted Thing name.',
40+
type: 'string',
41+
required: true
42+
})
43+
.option('mode', {
44+
alias: 'm',
45+
description: 'Mode options: [publish, subscribe, both] (optional).',
46+
type: 'string',
47+
default: 'both',
48+
choices: ['publish', 'subscribe', 'both']
49+
})
50+
.option('region', {
51+
description: 'AWS Region.',
52+
type: 'string',
53+
required: true
54+
})
55+
.option('print_discover_resp_only', {
56+
description: 'Only print the response from Greengrass discovery (optional).',
57+
type: 'boolean',
58+
default: false
59+
})
60+
.option('is_ci', {
61+
description: 'Adjusts sample to output/run in CI mode (optional).',
62+
type: 'boolean',
63+
default: false
64+
})
65+
}, main).parse();
66+
67+
function firstResolved<T>(promises: Promise<T>[]) {
68+
let rejects: Error[] = [];
69+
return new Promise<T>((resolve, reject) => {
70+
promises.forEach((promise) => {
71+
promise
72+
.then((value) => {
73+
resolve(value)
74+
})
75+
.catch((error) => {
76+
rejects.push(error);
77+
if (rejects.length == promises.length) {
78+
reject(rejects);
79+
}
80+
});
81+
});
82+
});
83+
}
84+
85+
async function connect_to_iot(mqtt_client: mqtt.MqttClient, argv: Args, discovery_response: greengrass.model.DiscoverResponse) {
86+
return new Promise<mqtt.MqttClientConnection>((resolve, reject) => {
87+
const start_connections = () => {
88+
let attempted_cores: string[] = [];
89+
let connections: Promise<mqtt.MqttClientConnection>[] = [];
90+
for (const gg_group of discovery_response.gg_groups) {
91+
for (const core of gg_group.cores) {
92+
attempted_cores.push(core.thing_arn.toString());
93+
for (const endpoint of core.connectivity) {
94+
const mqtt_config = iot.AwsIotMqttConnectionConfigBuilder.new_mtls_builder_from_path(argv.cert, argv.key)
95+
.with_certificate_authority(gg_group.certificate_authorities[0])
96+
.with_client_id(argv.thing_name)
97+
.with_clean_session(false)
98+
.with_socket_options(new io.SocketOptions(io.SocketType.STREAM, io.SocketDomain.IPV4, 3000))
99+
.build();
100+
mqtt_config.host_name = endpoint.host_address;
101+
mqtt_config.port = endpoint.port;
102+
console.log(`Trying endpoint=${JSON.stringify(endpoint)}`);
103+
const mqtt_connection = mqtt_client.new_connection(mqtt_config);
104+
mqtt_connection.on('error', (error) => {
105+
console.warn(`Connection to endpoint=${JSON.stringify(endpoint)} failed: ${error}`);
106+
});
107+
connections.push(mqtt_connection.connect().then((session_present) => {
108+
console.log(`Connected to endpoint=${JSON.stringify(endpoint)}`);
109+
return mqtt_connection;
110+
}));
111+
}
112+
}
113+
}
114+
115+
return connections;
116+
}
117+
118+
const mqtt_connections = start_connections();
119+
firstResolved(mqtt_connections)
120+
.then((connection) => {
121+
resolve(connection);
122+
})
123+
.catch((error) => {
124+
reject(error);
125+
});
126+
});
127+
}
128+
129+
async function execute_session(connection: mqtt.MqttClientConnection, argv: Args) {
130+
console.log("execute_session: topic is " + argv.topic);
131+
return new Promise<void>(async (resolve, reject) => {
132+
try {
133+
let published = false;
134+
let subscribed = false;
135+
const decoder = new TextDecoder('utf8');
136+
if (argv.mode == 'both' || argv.mode == 'subscribe') {
137+
const on_publish = (topic: string, payload: ArrayBuffer, dup: boolean, qos: mqtt.QoS, retain: boolean) => {
138+
const json = decoder.decode(payload);
139+
console.log(`Publish received. topic:"${topic}" dup:${dup} qos:${qos} retain:${retain}`);
140+
console.log(json);
141+
const message = JSON.parse(json);
142+
if (message.sequence == argv.count) {
143+
subscribed = true;
144+
if (subscribed && published) {
145+
resolve();
146+
}
147+
}
148+
}
149+
await connection.subscribe(argv.topic, mqtt.QoS.AtLeastOnce, on_publish);
150+
}
151+
else {
152+
subscribed = true;
153+
}
154+
155+
if (argv.mode == 'both' || argv.mode == 'publish') {
156+
let published_counts = 0;
157+
for (let op_idx = 0; op_idx < argv.count; ++op_idx) {
158+
const publish = async () => {
159+
const msg = {
160+
message: argv.message,
161+
sequence: op_idx + 1,
162+
};
163+
const json = JSON.stringify(msg);
164+
console.log("execute_session: publishing...");
165+
connection.publish(argv.topic, json, mqtt.QoS.AtLeastOnce).then(() => {
166+
++published_counts;
167+
if (published_counts == argv.count) {
168+
published = true;
169+
if (subscribed && published) {
170+
resolve();
171+
}
172+
}
173+
});
174+
}
175+
setTimeout(publish, op_idx * 1000);
176+
}
177+
}
178+
else {
179+
published = true;
180+
}
181+
}
182+
catch (error) {
183+
reject(error);
184+
}
185+
});
186+
}
187+
188+
async function main(argv: Args) {
189+
if (argv.verbose && argv.verbose != 'none') {
190+
const level: io.LogLevel = parseInt(io.LogLevel[argv.verbose.toUpperCase()]);
191+
io.enable_logging(level);
192+
}
193+
194+
const client_bootstrap = new io.ClientBootstrap();
195+
const socket_options = new io.SocketOptions(io.SocketType.STREAM, io.SocketDomain.IPV4, 3000);
196+
const tls_options = new io.TlsContextOptions();
197+
if (argv.ca_file) {
198+
tls_options.override_default_trust_store_from_path(undefined, argv.ca_file);
199+
}
200+
tls_options.certificate_filepath = argv.cert;
201+
tls_options.private_key_filepath = argv.key;
202+
if (io.is_alpn_available()) {
203+
tls_options.alpn_list.push('x-amzn-http-ca');
204+
}
205+
const tls_ctx = new io.ClientTlsContext(tls_options);
206+
const discovery = new greengrass.DiscoveryClient(client_bootstrap, socket_options, tls_ctx, argv.region);
207+
208+
// force node to wait 60 seconds before killing itself, promises do not keep node alive
209+
const timer = setTimeout(() => { }, 60 * 1000);
210+
211+
console.log("Starting discovery for thing " + argv.thing_name);
212+
213+
await discovery.discover(argv.thing_name)
214+
.then(async (discovery_response: greengrass.model.DiscoverResponse) => {
215+
console.log("Discovery Response:");
216+
217+
if (argv.is_ci != true) {
218+
console.log(JSON.stringify(discovery_response));
219+
} else {
220+
console.log("Received a greengrass discovery result! Not showing result in CI for possible data sensitivity.");
221+
}
222+
223+
if (argv.print_discover_resp_only) {
224+
process.exit(0);
225+
}
226+
227+
const mqtt_client = new mqtt.MqttClient(client_bootstrap);
228+
return connect_to_iot(mqtt_client, argv, discovery_response);
229+
}).then(async (connection) => {
230+
await execute_session(connection, argv);
231+
console.log("Disconnecting...");
232+
return connection.disconnect();
233+
}).then(() => {
234+
console.log('Complete!');
235+
})
236+
.catch((reason) => {
237+
console.log(`DISCOVERY SAMPLE FAILED: ${JSON.stringify(reason)}`);
238+
process.exit(1);
239+
});
240+
241+
// Allow node to die if the promise above resolved
242+
clearTimeout(timer);
243+
process.exit(0);
244+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "basic-discovery",
3+
"version": "1.0.0",
4+
"description": "NodeJS IoT SDK v2 Greengrass Discovery Sample",
5+
"homepage": "https://github.com/awslabs/aws-iot-device-sdk-js-v2",
6+
"repository": {
7+
"type": "git",
8+
"url": "git+https://github.com/aws/aws-iot-device-sdk-js-v2.git"
9+
},
10+
"contributors": [
11+
"AWS SDK Common Runtime Team <[email protected]>"
12+
],
13+
"license": "Apache-2.0",
14+
"main": "./dist/index.js",
15+
"scripts": {
16+
"tsc": "tsc",
17+
"prepare": "npm run tsc"
18+
},
19+
"devDependencies": {
20+
"@types/node": "^10.17.50",
21+
"typescript": "^4.7.4"
22+
},
23+
"dependencies": {
24+
"aws-iot-device-sdk-v2": "file:../../..",
25+
"aws-iot-samples-util": "file:../../util",
26+
"yargs": "^16.2.0"
27+
}
28+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"compilerOptions": {
3+
/* Basic Options */
4+
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
5+
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
6+
// "lib": [], /* Specify library files to be included in the compilation. */
7+
// "allowJs": true, /* Allow javascript files to be compiled. */
8+
// "checkJs": true, /* Report errors in .js files. */
9+
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
10+
"declaration": true, /* Generates corresponding '.d.ts' file. */
11+
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
12+
"sourceMap": true, /* Generates corresponding '.map' file. */
13+
// "outFile": "./", /* Concatenate and emit output to single file. */
14+
"outDir": "./dist", /* Redirect output structure to the directory. */
15+
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
16+
// "composite": true, /* Enable project compilation */
17+
// "removeComments": false, /* Do not emit comments to output. */
18+
// "noEmit": true, /* Do not emit outputs. */
19+
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
20+
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
21+
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
22+
/* Strict Type-Checking Options */
23+
"strict": true, /* Enable all strict type-checking options. */
24+
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
25+
"strictNullChecks": true, /* Enable strict null checks. */
26+
"strictFunctionTypes": true, /* Enable strict checking of function types. */
27+
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
28+
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
29+
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
30+
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
31+
/* Additional Checks */
32+
"noUnusedLocals": true, /* Report errors on unused locals. */
33+
// "noUnusedParameters": true, /* Report errors on unused parameters. */
34+
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
35+
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
36+
/* Module Resolution Options */
37+
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
38+
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
39+
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
40+
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
41+
// "typeRoots": [], /* List of folders to include type definitions from. */
42+
// "types": [], /* Type declaration files to be included in compilation. */
43+
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
44+
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
45+
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
46+
/* Source Map Options */
47+
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
48+
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
49+
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
50+
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
51+
/* Experimental Options */
52+
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
53+
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
54+
},
55+
"include": [
56+
"*.ts"
57+
],
58+
"exclude": [
59+
"node_modules"
60+
]
61+
}
File renamed without changes.

0 commit comments

Comments
 (0)