-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: publish LwM2M shadows for hello.nrfcloud.com/map
- Loading branch information
1 parent
9100231
commit d4fbe5c
Showing
10 changed files
with
709 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Construct } from 'constructs' | ||
import { aws_logs as Logs, Names, Stack } from 'aws-cdk-lib' | ||
|
||
export class LambdaLogGroup extends Construct { | ||
public readonly logGroup: Logs.LogGroup | ||
constructor( | ||
parent: Construct, | ||
id: string, | ||
retention = Logs.RetentionDays.ONE_DAY, | ||
) { | ||
super(parent, id) | ||
this.logGroup = new Logs.LogGroup(this, 'logGroup', { | ||
retention, | ||
logGroupName: `/${Stack.of(this).stackName}/fn/${id}-${Names.uniqueId(this)}`, | ||
logGroupClass: Logs.LogGroupClass.STANDARD, // INFREQUENT_ACCESS does not support custom metrics | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { | ||
Duration, | ||
aws_iam as IAM, | ||
aws_lambda as Lambda, | ||
RemovalPolicy, | ||
aws_s3 as S3, | ||
aws_events_targets as EventTargets, | ||
aws_events as Events, | ||
} from 'aws-cdk-lib' | ||
import { Construct } from 'constructs' | ||
import type { PackedLambda } from '../../backend.js' | ||
import { LambdaLogGroup } from '../LambdaLogGroup.js' | ||
|
||
/** | ||
* Publish a JSON of all LwM2M shadows so https://hello.nrfcloud.com/map can show them. | ||
*/ | ||
export class PublicLwM2MShadows extends Construct { | ||
public readonly bucket: S3.Bucket | ||
constructor( | ||
parent: Construct, | ||
{ | ||
baseLayer, | ||
lambdaSources, | ||
}: { | ||
baseLayer: Lambda.ILayerVersion | ||
lambdaSources: { | ||
publishLwM2MShadowsToJSON: PackedLambda | ||
} | ||
}, | ||
) { | ||
super(parent, 'PublicLwM2MShadows') | ||
|
||
this.bucket = new S3.Bucket(this, 'bucket', { | ||
autoDeleteObjects: true, | ||
removalPolicy: RemovalPolicy.DESTROY, | ||
publicReadAccess: true, | ||
websiteIndexDocument: 'index.html', | ||
blockPublicAccess: { | ||
blockPublicAcls: false, | ||
ignorePublicAcls: false, | ||
restrictPublicBuckets: false, | ||
blockPublicPolicy: false, | ||
}, | ||
objectOwnership: S3.ObjectOwnership.OBJECT_WRITER, | ||
cors: [ | ||
{ | ||
allowedOrigins: ['https://hello.nrfcloud.com', 'http://localhost:*'], | ||
allowedMethods: [S3.HttpMethods.GET], | ||
}, | ||
], | ||
}) | ||
|
||
const fn = new Lambda.Function(this, 'fn', { | ||
handler: lambdaSources.publishLwM2MShadowsToJSON.handler, | ||
architecture: Lambda.Architecture.ARM_64, | ||
runtime: Lambda.Runtime.NODEJS_20_X, | ||
timeout: Duration.minutes(1), | ||
memorySize: 1792, | ||
code: Lambda.Code.fromAsset( | ||
lambdaSources.publishLwM2MShadowsToJSON.lambdaZipFile, | ||
), | ||
description: | ||
'Provides the LwM2M shadow of the devices to https://hello.nrfcloud.com/map', | ||
layers: [baseLayer], | ||
environment: { | ||
VERSION: this.node.tryGetContext('version'), | ||
NODE_NO_WARNINGS: '1', | ||
BUCKET: this.bucket.bucketName, | ||
}, | ||
...new LambdaLogGroup(this, 'devicesFnLogs'), | ||
initialPolicy: [ | ||
new IAM.PolicyStatement({ | ||
actions: ['iot:SearchIndex', 'iot:DescribeThing'], | ||
resources: ['*'], | ||
}), | ||
], | ||
}) | ||
|
||
this.bucket.grantWrite(fn) | ||
|
||
const rule = new Events.Rule(this, 'rule', { | ||
description: `Rule to schedule publishLwM2MShadowsToJSON lambda invocations`, | ||
schedule: Events.Schedule.rate(Duration.minutes(1)), | ||
}) | ||
rule.addTarget(new EventTargets.LambdaFunction(fn)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { IoTClient } from '@aws-sdk/client-iot' | ||
import { fetchLwM2MShadows } from '../lwm2m/fetchLwM2MShadows.js' | ||
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3' | ||
import { fromEnv } from '@nordicsemiconductor/from-env' | ||
|
||
const iot = new IoTClient({}) | ||
const fetchShadows = fetchLwM2MShadows(iot) | ||
const s3 = new S3Client({}) | ||
const { bucket } = fromEnv({ bucket: 'BUCKET' })(process.env) | ||
|
||
export const handler = async (): Promise<void> => { | ||
await s3.send( | ||
new PutObjectCommand({ | ||
Bucket: bucket, | ||
Key: 'lwm2m-shadows.json', | ||
ContentType: 'application/json', | ||
CacheControl: 'public, max-age=60', | ||
Body: JSON.stringify({ | ||
'@context': 'https://github.com/hello-nrfcloud/proto/map/devices', | ||
devices: (await fetchShadows()).map(({ deviceId, objects }) => ({ | ||
'@context': 'https://github.com/hello-nrfcloud/proto/map/device', | ||
id: deviceId, | ||
model: 'world.thingy.rocks', | ||
state: objects, | ||
})), | ||
}), | ||
}), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { IoTClient, SearchIndexCommand } from '@aws-sdk/client-iot' | ||
import type { LwM2MObjectInstance } from '@hello.nrfcloud.com/proto-lwm2m' | ||
import { shadowToObjects } from './shadowToObjects.js' | ||
import { getDeviceInfo } from '../lambda/withDeviceAlias.js' | ||
|
||
type LwM2MShadow = { | ||
deviceId: string | ||
alias?: string | ||
objects: LwM2MObjectInstance[] | ||
} | ||
|
||
export const fetchLwM2MShadows = ( | ||
iot: IoTClient, | ||
): (() => Promise<LwM2MShadow[]>) => { | ||
const deviceInfo = getDeviceInfo(iot) | ||
return async () => { | ||
const { things } = await iot.send( | ||
new SearchIndexCommand({ | ||
// Find all things which have an LwM2M shadow | ||
queryString: 'shadow.name.lwm2m.hasDelta:*', | ||
}), | ||
) | ||
return ( | ||
await Promise.all<LwM2MShadow>( | ||
(things ?? []).map(async ({ thingName, shadow }) => { | ||
const alias = (await deviceInfo(thingName as string)).alias | ||
const reported = JSON.parse(shadow ?? '{}').name.lwm2m.reported | ||
if (reported === undefined) | ||
return { | ||
deviceId: thingName as string, | ||
alias, | ||
objects: [], | ||
} | ||
|
||
try { | ||
return { | ||
deviceId: thingName as string, | ||
alias, | ||
objects: shadowToObjects(reported), | ||
} | ||
} catch (err) { | ||
console.error(`Failed to convert shadow for thing ${thingName}`) | ||
console.log( | ||
JSON.stringify({ | ||
thingName, | ||
shadow: { | ||
reported, | ||
}, | ||
}), | ||
) | ||
console.error(err) | ||
return { | ||
deviceId: thingName as string, | ||
alias, | ||
objects: [], | ||
} | ||
} | ||
}), | ||
) | ||
).filter(({ objects }) => objects.length > 0) | ||
} | ||
} |
Oops, something went wrong.