From e9c71f7f6ea9bf87aebbf1b89031f27b5b34394a Mon Sep 17 00:00:00 2001 From: Bill Beesley Date: Mon, 13 Dec 2021 17:30:38 +0000 Subject: [PATCH] feat: improve provisioning and error handling --- .gitignore | 0 README.md | 0 client/.pylintrc | 0 client/code.py | 36 ++++-- client/config.py | 3 + enclosure/temperature-sensor-base.3mf | Bin enclosure/temperature-sensor-lid.3mf | Bin provision-device.sh | 8 ++ server/.eslintrc.js | 0 server/.gitignore | 0 server/.npmignore | 0 server/.nvmrc | 0 server/README.md | 0 server/babel.config.js | 0 server/bin/temperature-measurements.ts | 0 server/cdk.json | 0 .../functions/measurements/package-lock.json | 0 server/functions/measurements/package.json | 0 .../measurements/src/@types/http-method.ts | 0 .../measurements/src/@types/index.ts | 0 server/functions/measurements/src/handler.ts | 119 +++++++++++++++--- server/jest.config.js | 0 server/lerna.json | 0 server/lib/temperature-measurements-stack.ts | 14 ++- server/package-lock.json | 0 server/package.json | 0 server/prettier.config.js | 0 server/test/temperature-measurements.test.ts | 0 server/tsconfig.json | 0 29 files changed, 152 insertions(+), 28 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 README.md mode change 100644 => 100755 client/.pylintrc create mode 100755 client/config.py mode change 100644 => 100755 enclosure/temperature-sensor-base.3mf mode change 100644 => 100755 enclosure/temperature-sensor-lid.3mf mode change 100644 => 100755 server/.eslintrc.js mode change 100644 => 100755 server/.gitignore mode change 100644 => 100755 server/.npmignore mode change 100644 => 100755 server/.nvmrc mode change 100644 => 100755 server/README.md mode change 100644 => 100755 server/babel.config.js mode change 100644 => 100755 server/bin/temperature-measurements.ts mode change 100644 => 100755 server/cdk.json mode change 100644 => 100755 server/functions/measurements/package-lock.json mode change 100644 => 100755 server/functions/measurements/package.json mode change 100644 => 100755 server/functions/measurements/src/@types/http-method.ts mode change 100644 => 100755 server/functions/measurements/src/@types/index.ts mode change 100644 => 100755 server/functions/measurements/src/handler.ts mode change 100644 => 100755 server/jest.config.js mode change 100644 => 100755 server/lerna.json mode change 100644 => 100755 server/lib/temperature-measurements-stack.ts mode change 100644 => 100755 server/package-lock.json mode change 100644 => 100755 server/package.json mode change 100644 => 100755 server/prettier.config.js mode change 100644 => 100755 server/test/temperature-measurements.test.ts mode change 100644 => 100755 server/tsconfig.json diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/client/.pylintrc b/client/.pylintrc old mode 100644 new mode 100755 diff --git a/client/code.py b/client/code.py index c241625..5012c51 100755 --- a/client/code.py +++ b/client/code.py @@ -1,8 +1,11 @@ import adafruit_requests +import digitalio import board import json +import microcontroller import socketpool import secrets +import config import ssl import time import wifi @@ -16,26 +19,32 @@ except ImportError: print("WiFi secrets are kept in secrets.py, please add them there!") raise +try: + from config import config +except ImportError: + print("Device name should be in config.py, please add it there!") + raise -print("Available WiFi networks:") -for network in wifi.radio.start_scanning_networks(): - print( - "\t%s\t\tRSSI: %d\tChannel: %d" - % (str(network.ssid, "utf-8"), network.rssi, network.channel) - ) -wifi.radio.stop_scanning_networks() +print("-" * 40) print("Connecting to %s" % secrets["ssid"]) wifi.radio.connect(secrets["ssid"], secrets["password"]) print("Connected to %s!" % secrets["ssid"]) print("My IP address is", wifi.radio.ipv4_address) +print("-" * 40) + pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context()) # setup temperature sensor libs +# Pull the I2C power pin low +i2c_power = digitalio.DigitalInOut(board.I2C_POWER_INVERTED) +i2c_power.switch_to_output() +i2c_power.value = False + i2c = board.I2C() bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c) -device_id = "compact-logger-01" +device_id = config["name"] headers = {"x-api-key": secrets["api_key"]} pending_measurement = None @@ -43,24 +52,26 @@ battery_sensor.pack_size = PackSize.MAH3000 def check_charge_status(): - print("Battery percentage: %0.1f %%" % (battery_sensor.cell_percent)) - + print("-" * 40) + print("Battery percentage: ", battery_sensor.cell_percent) + print("-" * 40) while True: - check_charge_status() # create payload try: + check_charge_status() measurement = { "temperature": bme280.temperature, "humidity": bme280.humidity, "pressure": bme280.pressure, "logger": device_id, + "charge": battery_sensor.cell_percent } pending_measurements = measurement except RuntimeError as e: print("could not get temperature measurement: ", e) time.sleep(60) - continue + raise try: json_data = json.dumps(pending_measurements) @@ -80,5 +91,6 @@ def check_charge_status(): pending_measurements.clear() except RuntimeError as e: print("could not send measurements: ", e) + microcontroller.reset() time.sleep(60) diff --git a/client/config.py b/client/config.py new file mode 100755 index 0000000..af0ed43 --- /dev/null +++ b/client/config.py @@ -0,0 +1,3 @@ +config = { + "name":"white" +} diff --git a/enclosure/temperature-sensor-base.3mf b/enclosure/temperature-sensor-base.3mf old mode 100644 new mode 100755 diff --git a/enclosure/temperature-sensor-lid.3mf b/enclosure/temperature-sensor-lid.3mf old mode 100644 new mode 100755 diff --git a/provision-device.sh b/provision-device.sh index c3999eb..32133a0 100755 --- a/provision-device.sh +++ b/provision-device.sh @@ -1,3 +1,11 @@ #!/bin/sh +SENSOR_NAME="$1" + +cat << EOF > client/config.py +config = { + "name":"${SENSOR_NAME:-sensor01}" +} +EOF + rsync --delete -rhv "client/" "/Volumes/CIRCUITPY" diff --git a/server/.eslintrc.js b/server/.eslintrc.js old mode 100644 new mode 100755 diff --git a/server/.gitignore b/server/.gitignore old mode 100644 new mode 100755 diff --git a/server/.npmignore b/server/.npmignore old mode 100644 new mode 100755 diff --git a/server/.nvmrc b/server/.nvmrc old mode 100644 new mode 100755 diff --git a/server/README.md b/server/README.md old mode 100644 new mode 100755 diff --git a/server/babel.config.js b/server/babel.config.js old mode 100644 new mode 100755 diff --git a/server/bin/temperature-measurements.ts b/server/bin/temperature-measurements.ts old mode 100644 new mode 100755 diff --git a/server/cdk.json b/server/cdk.json old mode 100644 new mode 100755 diff --git a/server/functions/measurements/package-lock.json b/server/functions/measurements/package-lock.json old mode 100644 new mode 100755 diff --git a/server/functions/measurements/package.json b/server/functions/measurements/package.json old mode 100644 new mode 100755 diff --git a/server/functions/measurements/src/@types/http-method.ts b/server/functions/measurements/src/@types/http-method.ts old mode 100644 new mode 100755 diff --git a/server/functions/measurements/src/@types/index.ts b/server/functions/measurements/src/@types/index.ts old mode 100644 new mode 100755 diff --git a/server/functions/measurements/src/handler.ts b/server/functions/measurements/src/handler.ts old mode 100644 new mode 100755 index 5862e8d..34c91e3 --- a/server/functions/measurements/src/handler.ts +++ b/server/functions/measurements/src/handler.ts @@ -12,15 +12,14 @@ import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda'; const client = new DynamoDBClient({}); const dynamo = DynamoDBDocumentClient.from(client); -async function post( +async function postMeasurements( event: APIGatewayProxyEventV2 ): Promise { const measurement = JSON.parse(event.body ?? '{}'); - const { temperature, humidity, pressure, logger } = measurement; await dynamo.send( new PutCommand({ TableName: process.env.TABLE_NAME, - Item: { timestamp: Date.now(), temperature, humidity, pressure, logger }, + Item: { ...measurement, timestamp: Date.now() }, }) ); return { @@ -29,7 +28,7 @@ async function post( }; } -async function get( +async function getMeasurements( // eslint-disable-next-line @typescript-eslint/no-unused-vars event: APIGatewayProxyEventV2 ): Promise { @@ -46,6 +45,7 @@ async function get( ExpressionAttributeValues: { ':loggerId': loggerId, }, + ScanIndexForward: false, }) ); } else { @@ -58,19 +58,98 @@ async function get( return { statusCode: 200, body: JSON.stringify( - { - items: res.Items?.map((i) => ({ - ...i, - dateTime: new Date(i.timestamp).toISOString(), - })), - endAt: res.LastEvaluatedKey, - }, + res.Items?.map((i) => ({ + ...i, + dateTime: new Date(i.timestamp).toISOString(), + })), null, 2 ), }; } +async function getBatteryLevel( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + event: APIGatewayProxyEventV2 +): Promise { + const { loggerId = 'logger01' } = event.pathParameters ?? {}; + const res = await dynamo.send( + new QueryCommand({ + TableName: process.env.TABLE_NAME, + KeyConditionExpression: '#logger = :loggerId', + ExpressionAttributeNames: { + '#logger': 'logger', + }, + ExpressionAttributeValues: { + ':loggerId': loggerId, + }, + Limit: 1, + ScanIndexForward: false, + }) + ); + if ((res.Items ?? []).length > 0) { + const latest = res.Items?.pop() ?? {}; + return { + statusCode: 200, + body: JSON.stringify( + { + batteryLevel: latest.charge, + dateTime: new Date(latest.timestamp).toISOString(), + }, + null, + 2 + ), + }; + } + return { + statusCode: 404, + body: JSON.stringify({ + message: 'unknown device', + }), + }; +} + +async function getTemperature( + event: APIGatewayProxyEventV2 +): Promise { + const { loggerId = 'logger01' } = event.pathParameters ?? {}; + const res = await dynamo.send( + new QueryCommand({ + TableName: process.env.TABLE_NAME, + KeyConditionExpression: '#logger = :loggerId', + ExpressionAttributeNames: { + '#logger': 'logger', + }, + ExpressionAttributeValues: { + ':loggerId': loggerId, + }, + Limit: 1, + ScanIndexForward: false, + }) + ); + if ((res.Items ?? []).length > 0) { + const latest = res.Items?.pop() ?? {}; + return { + statusCode: 200, + body: JSON.stringify( + { + temperature: latest.temperature, + humidity: latest.humidity, + dateTime: new Date(latest.timestamp).toISOString(), + }, + null, + 2 + ), + }; + } + return { + statusCode: 404, + body: JSON.stringify({ + message: 'unknown device', + }), + }; +} + export async function main( event: APIGatewayProxyEventV2 ): Promise { @@ -82,12 +161,22 @@ export async function main( }; } try { - if (event.requestContext.http.method === HttpMethod.GET) { - const res = await get(event); + if (event.rawPath.startsWith('/measurements')) { + if (event.requestContext.http.method === HttpMethod.GET) { + const res = await getMeasurements(event); + return res; + } + if (event.requestContext.http.method === HttpMethod.POST) { + const res = await postMeasurements(event); + return res; + } + } + if (event.rawPath.startsWith('/battery')) { + const res = await getBatteryLevel(event); return res; } - if (event.requestContext.http.method === HttpMethod.POST) { - const res = await post(event); + if (event.rawPath.startsWith('/temperature')) { + const res = await getTemperature(event); return res; } throw new Error(`unsupported method "${event.requestContext.http.method}"`); diff --git a/server/jest.config.js b/server/jest.config.js old mode 100644 new mode 100755 diff --git a/server/lerna.json b/server/lerna.json old mode 100644 new mode 100755 diff --git a/server/lib/temperature-measurements-stack.ts b/server/lib/temperature-measurements-stack.ts old mode 100644 new mode 100755 index e555c60..e47e1cf --- a/server/lib/temperature-measurements-stack.ts +++ b/server/lib/temperature-measurements-stack.ts @@ -28,7 +28,7 @@ export class TemperatureMeasurementsStack extends Stack { }, }); - const measurementsHandler = new LambdaFunction(this, 'API', { + const measurementsHandler = new LambdaFunction(this, 'measurements', { runtime: Runtime.NODEJS_14_X, code: Code.fromAsset('resources'), handler: 'handler.main', @@ -61,6 +61,18 @@ export class TemperatureMeasurementsStack extends Stack { integration: measurementsIntegration, }); + api.addRoutes({ + path: '/battery/{loggerId}', + methods: [HttpMethod.GET], + integration: measurementsIntegration, + }); + + api.addRoutes({ + path: '/temperature/{loggerId}', + methods: [HttpMethod.GET], + integration: measurementsIntegration, + }); + new CfnOutput(this, 'MeasurementsEndpoint', { value: `${api.defaultStage?.url}${ measurementsRoute.path?.replace(/^\//, '') ?? 'measurements' diff --git a/server/package-lock.json b/server/package-lock.json old mode 100644 new mode 100755 diff --git a/server/package.json b/server/package.json old mode 100644 new mode 100755 diff --git a/server/prettier.config.js b/server/prettier.config.js old mode 100644 new mode 100755 diff --git a/server/test/temperature-measurements.test.ts b/server/test/temperature-measurements.test.ts old mode 100644 new mode 100755 diff --git a/server/tsconfig.json b/server/tsconfig.json old mode 100644 new mode 100755