Skip to content

Commit

Permalink
feat: improve provisioning and error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
bbeesley committed Dec 13, 2021
1 parent 4634c9b commit e9c71f7
Show file tree
Hide file tree
Showing 29 changed files with 152 additions and 28 deletions.
Empty file modified .gitignore
100644 → 100755
Empty file.
Empty file modified README.md
100644 → 100755
Empty file.
Empty file modified client/.pylintrc
100644 → 100755
Empty file.
36 changes: 24 additions & 12 deletions client/code.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,51 +19,59 @@
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

battery_sensor = LC709203F(board.I2C())
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)
Expand All @@ -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)
3 changes: 3 additions & 0 deletions client/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
config = {
"name":"white"
}
Empty file modified enclosure/temperature-sensor-base.3mf
100644 → 100755
Empty file.
Empty file modified enclosure/temperature-sensor-lid.3mf
100644 → 100755
Empty file.
8 changes: 8 additions & 0 deletions provision-device.sh
Original file line number Diff line number Diff line change
@@ -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"
Empty file modified server/.eslintrc.js
100644 → 100755
Empty file.
Empty file modified server/.gitignore
100644 → 100755
Empty file.
Empty file modified server/.npmignore
100644 → 100755
Empty file.
Empty file modified server/.nvmrc
100644 → 100755
Empty file.
Empty file modified server/README.md
100644 → 100755
Empty file.
Empty file modified server/babel.config.js
100644 → 100755
Empty file.
Empty file modified server/bin/temperature-measurements.ts
100644 → 100755
Empty file.
Empty file modified server/cdk.json
100644 → 100755
Empty file.
Empty file modified server/functions/measurements/package-lock.json
100644 → 100755
Empty file.
Empty file modified server/functions/measurements/package.json
100644 → 100755
Empty file.
Empty file modified server/functions/measurements/src/@types/http-method.ts
100644 → 100755
Empty file.
Empty file modified server/functions/measurements/src/@types/index.ts
100644 → 100755
Empty file.
119 changes: 104 additions & 15 deletions server/functions/measurements/src/handler.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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<APIGatewayProxyResultV2> {
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 {
Expand All @@ -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<APIGatewayProxyResultV2> {
Expand All @@ -46,6 +45,7 @@ async function get(
ExpressionAttributeValues: {
':loggerId': loggerId,
},
ScanIndexForward: false,
})
);
} else {
Expand All @@ -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<APIGatewayProxyResultV2> {
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<APIGatewayProxyResultV2> {
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<APIGatewayProxyResultV2> {
Expand All @@ -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}"`);
Expand Down
Empty file modified server/jest.config.js
100644 → 100755
Empty file.
Empty file modified server/lerna.json
100644 → 100755
Empty file.
14 changes: 13 additions & 1 deletion server/lib/temperature-measurements-stack.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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'
Expand Down
Empty file modified server/package-lock.json
100644 → 100755
Empty file.
Empty file modified server/package.json
100644 → 100755
Empty file.
Empty file modified server/prettier.config.js
100644 → 100755
Empty file.
Empty file modified server/test/temperature-measurements.test.ts
100644 → 100755
Empty file.
Empty file modified server/tsconfig.json
100644 → 100755
Empty file.

0 comments on commit e9c71f7

Please sign in to comment.