From f1166d033799c06ea60a3f5b53c7fc8d53b4e5f2 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Fri, 13 Oct 2023 12:10:31 +0200 Subject: [PATCH] fix: remove support for LEDs --- README.md | 10 -- cdk/BackendLambdas.d.ts | 1 - cdk/backend.ts | 1 - cdk/packLayer.ts | 25 ++-- cdk/resources/LambdaLogGroup.ts | 21 ---- cdk/resources/LightbulbThings.ts | 117 ------------------ cdk/resources/PublishSummaries.ts | 5 +- cdk/resources/ResolveCellLocation.ts | 12 +- .../ResolveNetworkSurveyGeoLocation.ts | 12 +- cdk/resources/WebsocketAPI.ts | 30 +---- cdk/stacks/BackendStack.ts | 7 -- lambda/event.json | 25 ---- lambda/lightbulbPing.ts | 53 -------- lambda/notifyClients.ts | 10 +- lambda/onMessage.ts | 83 +------------ 15 files changed, 30 insertions(+), 382 deletions(-) delete mode 100644 cdk/resources/LambdaLogGroup.ts delete mode 100644 cdk/resources/LightbulbThings.ts delete mode 100644 lambda/event.json delete mode 100644 lambda/lightbulbPing.ts diff --git a/README.md b/README.md index 829347c..c7e3b3f 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,6 @@ npm ci npx cdk deploy ``` -## Support for the MQTT Sample - -Because the sample is not using the shadow, some manual work is needed to make -it work: - -1. Create a thing type `rgb-light` (they cannot be created using - CloudFormation). -1. Assign the thing type `rgb-light` to the thing which should act as a light - bulb. - ## Authentication For changing the state of light bulbs, create a Thing attribute named `code` and diff --git a/cdk/BackendLambdas.d.ts b/cdk/BackendLambdas.d.ts index 9ecde84..de94554 100644 --- a/cdk/BackendLambdas.d.ts +++ b/cdk/BackendLambdas.d.ts @@ -10,5 +10,4 @@ type BackendLambdas = { publishSummaries: PackedLambda onNewNetworkSurvey: PackedLambda onNetworkSurveyLocated: PackedLambda - lightbulbPing: PackedLambda } diff --git a/cdk/backend.ts b/cdk/backend.ts index ed3b558..f52a3f0 100644 --- a/cdk/backend.ts +++ b/cdk/backend.ts @@ -45,7 +45,6 @@ new BackendApp({ publishSummaries: await pack('publishSummaries'), onNewNetworkSurvey: await pack('onNewNetworkSurvey'), onNetworkSurveyLocated: await pack('onNetworkSurveyLocated'), - lightbulbPing: await pack('lightbulbPing'), }, layer: await packLayer({ id: 'baseLayer', diff --git a/cdk/packLayer.ts b/cdk/packLayer.ts index 812e820..09478fd 100644 --- a/cdk/packLayer.ts +++ b/cdk/packLayer.ts @@ -31,17 +31,20 @@ export const packLayer = async ({ await mkdir(nodejsDir, { recursive: true }) - const depsToBeInstalled = dependencies.reduce((resolved, dep) => { - const resolvedDependency = deps[dep] ?? devDeps[dep] - if (resolvedDependency === undefined) - throw new Error( - `Could not resolve dependency "${dep}" in ${packageJsonFile}!`, - ) - return { - ...resolved, - [dep]: resolvedDependency, - } - }, {} as Record) + const depsToBeInstalled = dependencies.reduce( + (resolved, dep) => { + const resolvedDependency = deps[dep] ?? devDeps[dep] + if (resolvedDependency === undefined) + throw new Error( + `Could not resolve dependency "${dep}" in ${packageJsonFile}!`, + ) + return { + ...resolved, + [dep]: resolvedDependency, + } + }, + {} as Record, + ) await writeFile( path.join(nodejsDir, 'package.json'), diff --git a/cdk/resources/LambdaLogGroup.ts b/cdk/resources/LambdaLogGroup.ts deleted file mode 100644 index 7f6d5d3..0000000 --- a/cdk/resources/LambdaLogGroup.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - aws_lambda as Lambda, - aws_logs as CloudWatchLogs, - RemovalPolicy, - Resource, -} from 'aws-cdk-lib' -import type { Construct } from 'constructs' - -export class LambdaLogGroup extends Resource { - public constructor(parent: Construct, id: string, lambda: Lambda.IFunction) { - super(parent, id) - const isTest = this.node.tryGetContext('isTest') === true - new CloudWatchLogs.LogGroup(this, 'LogGroup', { - removalPolicy: isTest ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN, - logGroupName: `/aws/lambda/${lambda.functionName}`, - retention: isTest - ? CloudWatchLogs.RetentionDays.ONE_DAY - : CloudWatchLogs.RetentionDays.ONE_WEEK, - }) - } -} diff --git a/cdk/resources/LightbulbThings.ts b/cdk/resources/LightbulbThings.ts deleted file mode 100644 index a62d28a..0000000 --- a/cdk/resources/LightbulbThings.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { - aws_iam as IAM, - aws_iot as IoT, - aws_lambda as Lambda, - Duration, - Stack, -} from 'aws-cdk-lib' -import { Construct } from 'constructs' -import type { PackedLambda } from '../backend' -import { LambdaLogGroup } from './LambdaLogGroup' -import type { WebsocketAPI } from './WebsocketAPI' - -/** - * Manage resources needed for the MQTT sample simulating a smart lightbulb - */ -export class LightbulbThings extends Construct { - constructor( - parent: Stack, - { - lambdaSources, - baseLayer, - websocketAPI, - }: { - lambdaSources: { - lightbulbPing: PackedLambda - } - baseLayer: Lambda.ILayerVersion - websocketAPI: WebsocketAPI - }, - ) { - super(parent, 'LightbulbThings') - - // lightbulbPing - - const lightbulbPing = new Lambda.Function(this, 'lightbulbPing', { - handler: lambdaSources.lightbulbPing.handler, - architecture: Lambda.Architecture.ARM_64, - runtime: Lambda.Runtime.NODEJS_18_X, - timeout: Duration.seconds(60), - memorySize: 1792, - code: Lambda.Code.fromAsset(lambdaSources.lightbulbPing.lambdaZipFile), - description: 'Invoked when the MQTT sample sends a ping', - layers: [baseLayer], - environment: { - VERSION: this.node.tryGetContext('version'), - CONNECTIONS_TABLE_NAME: websocketAPI.connectionsTable.tableName, - WEBSOCKET_MANAGEMENT_API_URL: websocketAPI.websocketManagementAPIURL, - }, - initialPolicy: [ - new IAM.PolicyStatement({ - actions: ['execute-api:ManageConnections'], - resources: [websocketAPI.websocketAPIArn], - }), - new IAM.PolicyStatement({ - actions: ['iot:DescribeThing'], - resources: ['*'], - }), - ], - }) - - new LambdaLogGroup(this, 'lightbulbPingLogs', lightbulbPing) - - websocketAPI.connectionsTable.grantFullAccess(lightbulbPing) - - const lightbulbPingRuleRole = new IAM.Role(this, 'lightbulbPingRuleRole', { - assumedBy: new IAM.ServicePrincipal( - 'iot.amazonaws.com', - ) as IAM.IPrincipal, - inlinePolicies: { - rootPermissions: new IAM.PolicyDocument({ - statements: [ - new IAM.PolicyStatement({ - actions: ['iot:Publish'], - resources: [ - `arn:aws:iot:${parent.region}:${parent.account}:topic/errors`, - ], - }), - ], - }), - }, - }) - - const lightbulbPingRule = new IoT.CfnTopicRule(this, 'lightbulbPingRule', { - topicRulePayload: { - description: `Send pings to lambda to forward them`, - ruleDisabled: false, - awsIotSqlVersion: '2016-03-23', - sql: [ - // Lambda does not support binary data, must be encoded - `SELECT encode(*, 'base64') AS message,`, - `clientid() as deviceId`, - `FROM '+/light-bulb/telemetry'`, - ].join(' '), - actions: [ - { - lambda: { - functionArn: lightbulbPing.functionArn, - }, - }, - ], - errorAction: { - republish: { - roleArn: lightbulbPingRuleRole.roleArn, - topic: 'errors', - }, - }, - }, - }) - - lightbulbPing.addPermission('invokeBylightbulbPingRulePermission', { - principal: new IAM.ServicePrincipal( - 'iot.amazonaws.com', - ) as IAM.IPrincipal, - sourceArn: lightbulbPingRule.attrArn, - }) - } -} diff --git a/cdk/resources/PublishSummaries.ts b/cdk/resources/PublishSummaries.ts index 11acc18..650fed7 100644 --- a/cdk/resources/PublishSummaries.ts +++ b/cdk/resources/PublishSummaries.ts @@ -5,11 +5,11 @@ import { aws_lambda as Lambda, Duration, Stack, + aws_logs as Logs, } from 'aws-cdk-lib' import type { IPrincipal } from 'aws-cdk-lib/aws-iam/index.js' import { Construct } from 'constructs' import type { PackedLambda } from '../backend.js' -import { LambdaLogGroup } from '../resources/LambdaLogGroup.js' import type { WebsocketAPI } from './WebsocketAPI.js' /** @@ -78,10 +78,9 @@ export class PublishSummaries extends Construct { resources: ['*'], }), ], + logRetention: Logs.RetentionDays.ONE_WEEK, }) - new LambdaLogGroup(this, 'Logs', lambda) - websocketAPI.connectionsTable.grantFullAccess(lambda) const rule = new Events.Rule(this, 'Rule', { diff --git a/cdk/resources/ResolveCellLocation.ts b/cdk/resources/ResolveCellLocation.ts index e7cd971..695f885 100644 --- a/cdk/resources/ResolveCellLocation.ts +++ b/cdk/resources/ResolveCellLocation.ts @@ -6,10 +6,10 @@ import { aws_iot as IoT, aws_lambda as Lambda, Stack, + aws_logs as Logs, } from 'aws-cdk-lib' import { Construct } from 'constructs' import type { PackedLambda } from '../backend.js' -import { LambdaLogGroup } from '../resources/LambdaLogGroup.js' import type { WebsocketAPI } from './WebsocketAPI.js' export class ResolveCellLocation extends Construct { @@ -66,11 +66,10 @@ export class ResolveCellLocation extends Construct { resources: ['*'], }), ], + logRetention: Logs.RetentionDays.ONE_WEEK, }, ) - new LambdaLogGroup(this, 'resolveCellLocationLogs', resolveCellLocation) - websocketAPI.connectionsTable.grantFullAccess(resolveCellLocation) const resolveCellLocationRuleRole = new IAM.Role( @@ -167,16 +166,11 @@ export class ResolveCellLocation extends Construct { }), ], layers: [baseLayer], + logRetention: Logs.RetentionDays.ONE_WEEK, }, ) websocketAPI.connectionsTable.grantFullAccess(onCellGeoLocationResolved) - new LambdaLogGroup( - this, - 'onCellGeoLocationResolvedLogGroup', - onCellGeoLocationResolved, - ) - const publishCellGeolocationSuccessEventsRule = new Events.Rule( this, 'publishCellGeolocationSuccessEventsRule', diff --git a/cdk/resources/ResolveNetworkSurveyGeoLocation.ts b/cdk/resources/ResolveNetworkSurveyGeoLocation.ts index e36eb3a..d917808 100644 --- a/cdk/resources/ResolveNetworkSurveyGeoLocation.ts +++ b/cdk/resources/ResolveNetworkSurveyGeoLocation.ts @@ -7,10 +7,10 @@ import { aws_lambda as Lambda, aws_lambda_event_sources as LambdaEvents, Stack, + aws_logs as Logs, } from 'aws-cdk-lib' import { Construct } from 'constructs' import type { PackedLambda } from '../backend' -import { LambdaLogGroup } from './LambdaLogGroup.js' import type { WebsocketAPI } from './WebsocketAPI' /** @@ -71,10 +71,9 @@ export class ResolveNetworkSurveyGeoLocation extends Construct { resources: ['*'], }), ], + logRetention: Logs.RetentionDays.ONE_WEEK, }) - new LambdaLogGroup(this, 'onNewNetworkSurveyLogs', onNewNetworkSurvey) - websocketAPI.connectionsTable.grantFullAccess(onNewNetworkSurvey) onNewNetworkSurvey.addEventSource( @@ -118,17 +117,12 @@ export class ResolveNetworkSurveyGeoLocation extends Construct { }), ], layers: [baseLayer], + logRetention: Logs.RetentionDays.ONE_WEEK, }, ) websocketAPI.connectionsTable.grantFullAccess(onNetworkSurveyLocated) surveysTable.grantReadData(onNetworkSurveyLocated) - new LambdaLogGroup( - this, - 'onNetworkSurveyLocatedLogGroup', - onNetworkSurveyLocated, - ) - const publishNetworkSurveyGeolocationSuccessEventsRule = new Events.Rule( this, 'publishNetworkSurveyGeolocationSuccessEventsRule', diff --git a/cdk/resources/WebsocketAPI.ts b/cdk/resources/WebsocketAPI.ts index b38d0df..9aee710 100644 --- a/cdk/resources/WebsocketAPI.ts +++ b/cdk/resources/WebsocketAPI.ts @@ -7,10 +7,10 @@ import { aws_lambda as Lambda, RemovalPolicy, Stack, + aws_logs as Logs, } from 'aws-cdk-lib' import { Construct } from 'constructs' import type { PackedLambda } from '../backend.js' -import { LambdaLogGroup } from '../resources/LambdaLogGroup.js' export class WebsocketAPI extends Construct { public readonly websocketURI: string @@ -82,10 +82,10 @@ export class WebsocketAPI extends Construct { }, initialPolicy: [], layers: [baseLayer], + logRetention: Logs.RetentionDays.ONE_WEEK, }) this.connectionsTable.grantWriteData(onConnect) - new LambdaLogGroup(this, 'onConnectLogs', onConnect) const connectIntegration = new ApiGateway.CfnIntegration( this, 'connectIntegration', @@ -117,27 +117,12 @@ export class WebsocketAPI extends Construct { environment: { VERSION: this.node.tryGetContext('version'), CONNECTIONS_TABLE_NAME: this.connectionsTable.tableName, - WEBSOCKET_MANAGEMENT_API_URL: this.websocketManagementAPIURL, }, - initialPolicy: [ - new IAM.PolicyStatement({ - actions: ['iot:DescribeThing'], - resources: ['*'], - }), - new IAM.PolicyStatement({ - actions: ['iot:Publish'], - resources: ['arn:aws:iot:*:*:topic/*/light-bulb/*'], - }), - new IAM.PolicyStatement({ - actions: ['execute-api:ManageConnections'], - resources: [this.websocketAPIArn], - }), - ], layers: [baseLayer], + logRetention: Logs.RetentionDays.ONE_WEEK, }) this.connectionsTable.grantReadWriteData(onMessage) - new LambdaLogGroup(this, 'onMessageLogs', onMessage) const sendMessageIntegration = new ApiGateway.CfnIntegration( this, 'sendMessageIntegration', @@ -172,10 +157,10 @@ export class WebsocketAPI extends Construct { }, initialPolicy: [], layers: [baseLayer], + logRetention: Logs.RetentionDays.ONE_WEEK, }) this.connectionsTable.grantWriteData(onDisconnect) - new LambdaLogGroup(this, 'onDisconnectLogs', onDisconnect) const disconnectIntegration = new ApiGateway.CfnIntegration( this, 'disconnectIntegration', @@ -244,17 +229,12 @@ export class WebsocketAPI extends Construct { }), ], layers: [baseLayer], + logRetention: Logs.RetentionDays.ONE_WEEK, }, ) this.connectionsTable.grantReadWriteData(publishToWebsocketClients) - new LambdaLogGroup( - this, - 'publishToWebsocketClientsLogs', - publishToWebsocketClients, - ) - const publishToWebsocketClientsRuleRole = new IAM.Role( this, 'publishToWebsocketClientsRuleRole', diff --git a/cdk/stacks/BackendStack.ts b/cdk/stacks/BackendStack.ts index 95fe1e9..1ff0d0b 100644 --- a/cdk/stacks/BackendStack.ts +++ b/cdk/stacks/BackendStack.ts @@ -8,7 +8,6 @@ import { } from 'aws-cdk-lib' import type { BackendLambdas } from '../BackendLambdas.js' import type { PackedLayer } from '../packLayer.js' -import { LightbulbThings } from '../resources/LightbulbThings.js' import { Map } from '../resources/Map.js' import { PublishSummaries } from '../resources/PublishSummaries.js' import { ResolveCellLocation } from '../resources/ResolveCellLocation.js' @@ -92,12 +91,6 @@ export class BackendStack extends Stack { ), }) - new LightbulbThings(this, { - lambdaSources, - baseLayer, - websocketAPI: api, - }) - // Outputs new CfnOutput(this, 'WebSocketURI', { exportName: `${this.stackName}:WebSocketURI`, diff --git a/lambda/event.json b/lambda/event.json deleted file mode 100644 index d94d59b..0000000 --- a/lambda/event.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "event": { - "requestContext": { - "routeKey": "sendmessage", - "messageId": "AVKFSed-PHcCGVQ=", - "eventType": "MESSAGE", - "extendedRequestId": "AVKFSHS7vHcFWPw=", - "requestTime": "14/Feb/2023:13:18:25 +0000", - "messageDirection": "IN", - "stage": "2022-11-22", - "connectedAt": 1676378263853, - "requestTimeEpoch": 1676380705521, - "identity": { - "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36", - "sourceIp": "88.88.113.98" - }, - "requestId": "AVKFSHS7vHcFWPw=", - "domainName": "gqhubutcb1.execute-api.us-west-2.amazonaws.com", - "connectionId": "AVEHxernvHcCGVQ=", - "apiId": "gqhubutcb1" - }, - "body": "{\"message\":\"sendmessage\",\"data\":{\"desired\":{\"led\":{\"v\":{\"color\":[0,255,0]}}},\"deviceId\":\"1078800338:demo5Gmesh_gw01\",\"code\":\"8je7dlvr\"}}", - "isBase64Encoded": false - } -} diff --git a/lambda/lightbulbPing.ts b/lambda/lightbulbPing.ts deleted file mode 100644 index fbcf635..0000000 --- a/lambda/lightbulbPing.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ApiGatewayManagementApi } from '@aws-sdk/client-apigatewaymanagementapi' -import { DynamoDBClient } from '@aws-sdk/client-dynamodb' -import { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot' -import { fromEnv } from '@nordicsemiconductor/from-env' -import { notifyClients } from './notifyClients.js' - -const { connectionsTableName, websocketManagementAPIURL } = fromEnv({ - connectionsTableName: 'CONNECTIONS_TABLE_NAME', - websocketManagementAPIURL: 'WEBSOCKET_MANAGEMENT_API_URL', -})(process.env) - -const iot = new IoTClient({}) -const db = new DynamoDBClient({}) -const apiGwManagementClient = new ApiGatewayManagementApi({ - endpoint: websocketManagementAPIURL, -}) -const notifier = notifyClients({ - db, - connectionsTableName, - apiGwManagementClient, -}) - -export const handler = async (event: { - deviceId: string - message: string -}): Promise => { - console.log(JSON.stringify({ event })) - console.log( - JSON.stringify({ - message: Buffer.from(event.message, 'base64').toString(), - }), - ) - try { - const res = await iot.send( - new DescribeThingCommand({ - thingName: event.deviceId, - }), - ) - - if (res.attributes?.name !== undefined) { - // Notify about names - await notifier({ - deviceId: event.deviceId, - deviceAlias: res.attributes.name, - lightbulb: { - type: 'rgb', - }, - }) - } - } catch (err) { - console.error(err) - } -} diff --git a/lambda/notifyClients.ts b/lambda/notifyClients.ts index eb08acd..5f5bda0 100644 --- a/lambda/notifyClients.ts +++ b/lambda/notifyClients.ts @@ -58,14 +58,7 @@ export type CellGeoLocationEvent = { } } -export type LightbulbEvent = { - lightbulb: { - type: 'rgb' - color?: [number, number, number] - } -} - -export type Event = DeviceEvent | CellGeoLocationEvent | LightbulbEvent +export type Event = DeviceEvent | CellGeoLocationEvent export const notifyClients = ( @@ -140,7 +133,6 @@ const getEventContext = (event: Event): string | null => { if ('history' in event) return 'https://thingy.rocks/device-history' if ('cellGeoLocation' in event) return 'https://thingy.rocks/cell-geo-location' - if ('lightbulb' in event) return 'https://thingy.rocks/lightbulb' return null } diff --git a/lambda/onMessage.ts b/lambda/onMessage.ts index 6cf14c5..4d7b362 100644 --- a/lambda/onMessage.ts +++ b/lambda/onMessage.ts @@ -1,38 +1,17 @@ -import { ApiGatewayManagementApi } from '@aws-sdk/client-apigatewaymanagementapi' import { DynamoDBClient, UpdateItemCommand } from '@aws-sdk/client-dynamodb' -import { DescribeThingCommand, IoTClient } from '@aws-sdk/client-iot' -import { - IoTDataPlaneClient, - PublishCommand, -} from '@aws-sdk/client-iot-data-plane' import { fromEnv } from '@nordicsemiconductor/from-env' import type { APIGatewayProxyStructuredResultV2, APIGatewayProxyWebsocketEventV2, } from 'aws-lambda' -import { notifyClients } from './notifyClients.js' -const { TableName } = fromEnv({ TableName: 'CONNECTIONS_TABLE_NAME' })( - process.env, -) const db = new DynamoDBClient({}) -const iot = new IoTClient({}) -const iotData = new IoTDataPlaneClient({}) -const { connectionsTableName, websocketManagementAPIURL } = fromEnv({ - connectionsTableName: 'CONNECTIONS_TABLE_NAME', +const { TableName } = fromEnv({ + TableName: 'CONNECTIONS_TABLE_NAME', websocketManagementAPIURL: 'WEBSOCKET_MANAGEMENT_API_URL', })(process.env) -export const apiGwManagementClient = new ApiGatewayManagementApi({ - endpoint: websocketManagementAPIURL, -}) -const notifier = notifyClients({ - db, - connectionsTableName, - apiGwManagementClient, -}) - export const handler = async ( event: APIGatewayProxyWebsocketEventV2, ): Promise => { @@ -62,64 +41,6 @@ export const handler = async ( }), ) - if (event.body !== undefined) { - try { - const message = JSON.parse(event.body) as { - data?: { - desired?: { - led?: { - v: { - color: [number, number, number] - } - } - } - deviceId?: string - code?: string - } - } - - const ledColor = message?.data?.desired?.led?.v.color - const deviceId = message?.data?.deviceId - const code = message?.data?.code - if ( - ledColor !== undefined && - deviceId !== undefined && - code !== undefined - ) { - const res = await iot.send( - new DescribeThingCommand({ - thingName: deviceId, - }), - ) - if (res.attributes?.code === code) { - const deviceAlias = res.attributes.name - switch (res.thingTypeName) { - case 'rgb-light': - await iotData.send( - new PublishCommand({ - topic: `${deviceId}/light-bulb/led-ctrl`, - payload: new TextEncoder().encode(ledColor.join(',')), - }), - ) - await notifier({ - deviceId, - deviceAlias, - lightbulb: { - type: 'rgb', - color: ledColor, - }, - }) - break - default: - console.error(`Thing has unsupported type`, res.thingTypeName) - } - } - } - } catch (err) { - console.error(err) - } - } - return { statusCode: 200, body: `Got your message, ${event.requestContext.connectionId}!`,