Skip to main content

IoT Sensor Monitoring

Complete example of monitoring IoT sensors with dakkio, from hardware setup to alert notifications.

Overview

This example demonstrates:

  • ESP32 with DHT22 sensor (temperature & humidity)
  • Data ingestion to dakkio
  • Alert rules for anomalies
  • Slack notifications via webhooks

Architecture

Hardware Setup

Components

  • ESP32 Development Board
  • DHT22 Temperature & Humidity Sensor
  • 10kΩ resistor (pull-up)
  • Breadboard and jumper wires

Wiring

DHT22 Pin 1 (VCC)  → ESP32 3.3V
DHT22 Pin 2 (DATA) → ESP32 GPIO 4 (with 10kΩ pull-up to 3.3V)
DHT22 Pin 4 (GND) → ESP32 GND

Software Setup

1. Install Arduino Libraries

In Arduino IDE, install:

  • DHT sensor library by Adafruit
  • Adafruit Unified Sensor
  • ArduinoJson by Benoit Blanchon
  • HTTPClient (included with ESP32)

2. Create dakkio Resources

# Login and get JWT token
curl -X POST https://api.dakkio.io/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "your@email.com", "password": "password"}' \
| jq -r '.token' > token.txt

TOKEN=$(cat token.txt)

# Create bucket
BUCKET_RESPONSE=$(curl -X POST https://api.dakkio.io/api/buckets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Home Sensors",
"description": "Temperature and humidity monitoring",
"retentionDays": 90
}')

BUCKET_ID=$(echo $BUCKET_RESPONSE | jq -r '.bucket._id')
echo "Bucket ID: $BUCKET_ID"

# Create data source
DATASOURCE_RESPONSE=$(curl -X POST https://api.dakkio.io/api/datasources \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"bucketId\": \"$BUCKET_ID\",
\"name\": \"Living Room Sensor\",
\"type\": \"sensor\",
\"schema\": {
\"temperature\": \"number\",
\"humidity\": \"number\"
}
}")

DATASOURCE_ID=$(echo $DATASOURCE_RESPONSE | jq -r '.dataSource._id')
echo "Data Source ID: $DATASOURCE_ID"

# Generate API key
API_KEY_RESPONSE=$(curl -X POST https://api.dakkio.io/api/apikeys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "ESP32 Sensor",
"permissions": ["data:write", "data:read"]
}')

API_KEY=$(echo $API_KEY_RESPONSE | jq -r '.key')
echo "API Key: $API_KEY"
echo "⚠️ Save this key securely - it won't be shown again!"

3. ESP32 Code

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <DHT.h>

// WiFi credentials
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// dakkio configuration
const char* apiKey = "dakkio_your_api_key_here";
const char* bucketId = "your_bucket_id_here";
const char* dataSourceId = "your_datasource_id_here";
const char* apiUrl = "https://api.dakkio.io/api/data";

// DHT22 configuration
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// Reading interval (milliseconds)
const unsigned long readingInterval = 60000; // 1 minute
unsigned long lastReading = 0;

void setup() {
Serial.begin(115200);
Serial.println("\n=== dakkio IoT Sensor ===");

// Initialize DHT sensor
dht.begin();

// Connect to WiFi
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

Serial.println("\n✓ WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}

void loop() {
unsigned long currentMillis = millis();

// Check if it's time to read sensor
if (currentMillis - lastReading >= readingInterval) {
lastReading = currentMillis;

// Read sensor data
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();

// Check if readings are valid
if (isnan(temperature) || isnan(humidity)) {
Serial.println("✗ Failed to read from DHT sensor!");
return;
}

// Print to serial
Serial.println("\n--- Sensor Reading ---");
Serial.printf("Temperature: %.1f°C\n", temperature);
Serial.printf("Humidity: %.1f%%\n", humidity);

// Send to dakkio
bool success = sendToDakkio(temperature, humidity);

if (success) {
Serial.println("✓ Data sent to dakkio");
} else {
Serial.println("✗ Failed to send data");
}
}

delay(100);
}

bool sendToDakkio(float temperature, float humidity) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("✗ WiFi not connected");
return false;
}

HTTPClient http;

// Begin HTTP request
http.begin(apiUrl);
http.addHeader("Content-Type", "application/json");
http.addHeader("X-API-Key", apiKey);
http.setTimeout(10000); // 10 second timeout

// Create JSON payload
StaticJsonDocument<512> doc;
doc["bucketId"] = bucketId;
doc["dataSourceId"] = dataSourceId;

JsonObject values = doc.createNestedObject("values");
values["temperature"] = temperature;
values["humidity"] = humidity;

JsonObject metadata = doc.createNestedObject("metadata");
metadata["deviceId"] = "ESP32-001";
metadata["location"] = "Living Room";
metadata["firmwareVersion"] = "1.0.0";
metadata["rssi"] = WiFi.RSSI();

// Serialize JSON
String payload;
serializeJson(doc, payload);

// Send POST request
int httpCode = http.POST(payload);

// Check response
bool success = false;
if (httpCode == 201) {
success = true;
String response = http.getString();
Serial.println("Response: " + response);
} else {
Serial.printf("HTTP Error: %d\n", httpCode);
if (httpCode > 0) {
Serial.println(http.getString());
}
}

http.end();
return success;
}

4. Configure and Upload

  1. Update WiFi credentials in the code
  2. Add your API key, bucket ID, and data source ID
  3. Select "ESP32 Dev Module" in Arduino IDE
  4. Upload the sketch
  5. Open Serial Monitor (115200 baud)

You should see output like:

=== dakkio IoT Sensor ===
Connecting to WiFi...
✓ WiFi connected
IP address: 192.168.1.100

--- Sensor Reading ---
Temperature: 22.5°C
Humidity: 65.0%
Response: {"message":"Data point ingested successfully"}
✓ Data sent to dakkio

Set Up Alerts

High Temperature Alert

curl -X POST https://api.dakkio.io/api/alerts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"bucketId\": \"$BUCKET_ID\",
\"dataSourceId\": \"$DATASOURCE_ID\",
\"name\": \"High Temperature Alert\",
\"naturalLanguageQuery\": \"Temperature exceeds 28°C for 5 minutes\",
\"cooldownMinutes\": 15
}"

Low Humidity Alert

curl -X POST https://api.dakkio.io/api/alerts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"bucketId\": \"$BUCKET_ID\",
\"dataSourceId\": \"$DATASOURCE_ID\",
\"name\": \"Low Humidity Alert\",
\"naturalLanguageQuery\": \"Humidity is less than 30%\",
\"cooldownMinutes\": 30
}"

Sensor Offline Alert

curl -X POST https://api.dakkio.io/api/alerts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"bucketId\": \"$BUCKET_ID\",
\"dataSourceId\": \"$DATASOURCE_ID\",
\"name\": \"Sensor Offline\",
\"naturalLanguageQuery\": \"No data received for 10 minutes\",
\"cooldownMinutes\": 5
}"

Configure Slack Webhook

1. Create Slack Incoming Webhook

  1. Go to https://api.slack.com/messaging/webhooks
  2. Click "Create New App" → "From scratch"
  3. Name: "dakkio Alerts"
  4. Select your workspace
  5. Click "Incoming Webhooks" → Enable
  6. Click "Add New Webhook to Workspace"
  7. Select channel (e.g., #alerts)
  8. Copy webhook URL

2. Create dakkio Webhook

curl -X POST https://api.dakkio.io/api/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"bucketId\": \"$BUCKET_ID\",
\"url\": \"https://hooks.slack.com/services/YOUR/WEBHOOK/URL\",
\"events\": [\"alert.triggered\"],
\"secret\": \"your_secret_key\"
}"

3. Create Bridge Server (Optional)

For formatted Slack messages, create a bridge server:

// server.js
const express = require('express');
const axios = require('axios');
require('dotenv').config();

const app = express();
app.use(express.json());

const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL;

app.post('/webhook', (req, res) => {
const { event, data } = req.body;

if (event === 'alert.triggered') {
const emoji = data.alertName.includes('High') ? '🔥' : '❄️';

const message = {
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `${emoji} ${data.alertName}`
}
},
{
type: 'section',
fields: [
{
type: 'mrkdwn',
text: `*Condition:*\n${data.condition}`
},
{
type: 'mrkdwn',
text: `*Current Value:*\n${data.currentValue}°C`
},
{
type: 'mrkdwn',
text: `*Location:*\n${data.dataPoint.metadata.location}`
},
{
type: 'mrkdwn',
text: `*Device:*\n${data.dataPoint.metadata.deviceId}`
}
]
},
{
type: 'context',
elements: [
{
type: 'mrkdwn',
text: `Triggered at ${new Date(data.triggeredAt).toLocaleString()}`
}
]
}
]
};

axios.post(SLACK_WEBHOOK_URL, message)
.then(() => console.log('Slack notification sent'))
.catch(err => console.error('Slack error:', err));
}

res.status(200).send('OK');
});

app.listen(3000, () => {
console.log('Webhook bridge running on port 3000');
});

Query Historical Data

Get Last 24 Hours

curl -X POST https://api.dakkio.io/api/data/query \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"bucketId\": \"$BUCKET_ID\",
\"filters\": {
\"dataSourceIds\": [\"$DATASOURCE_ID\"],
\"startTime\": \"$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)\",
\"endTime\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
},
\"aggregation\": \"avg\",
\"groupBy\": \"hour\"
}" | jq

Get Min/Max Temperatures

curl -X POST https://api.dakkio.io/api/data/query \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"bucketId\": \"$BUCKET_ID\",
\"filters\": {
\"dataSourceIds\": [\"$DATASOURCE_ID\"],
\"startTime\": \"$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)\",
\"endTime\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"fields\": [\"temperature\"]
},
\"aggregation\": \"min\"
}" | jq

curl -X POST https://api.dakkio.io/api/data/query \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"bucketId\": \"$BUCKET_ID\",
\"filters\": {
\"dataSourceIds\": [\"$DATASOURCE_ID\"],
\"startTime\": \"$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)\",
\"endTime\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"fields\": [\"temperature\"]
},
\"aggregation\": \"max\"
}" | jq

Production Enhancements

1. Error Handling & Retry

bool sendDataWithRetry(float temperature, float humidity, int maxRetries = 3) {
for (int attempt = 0; attempt < maxRetries; attempt++) {
if (sendToDakkio(temperature, humidity)) {
return true;
}

if (attempt < maxRetries - 1) {
Serial.printf("Retry %d/%d in 5 seconds...\n", attempt + 1, maxRetries);
delay(5000);
}
}

return false;
}

2. Local Data Buffering

#include <SPIFFS.h>

void saveDataLocally(float temperature, float humidity) {
File file = SPIFFS.open("/buffer.json", FILE_APPEND);
if (file) {
StaticJsonDocument<128> doc;
doc["temp"] = temperature;
doc["humidity"] = humidity;
doc["timestamp"] = millis();

serializeJson(doc, file);
file.println();
file.close();
}
}

void sendBufferedData() {
// Read and send buffered data
// Delete buffer file after successful send
}

3. Battery Optimization

#include <esp_sleep.h>

const int DEEP_SLEEP_SECONDS = 300; // 5 minutes

void enterDeepSleep() {
Serial.println("Entering deep sleep...");
esp_sleep_enable_timer_wakeup(DEEP_SLEEP_SECONDS * 1000000);
esp_deep_sleep_start();
}

void loop() {
// Read sensor
// Send data
// Enter deep sleep
enterDeepSleep();
}

Troubleshooting

Sensor Reads NaN

  • Check wiring connections
  • Verify 10kΩ pull-up resistor
  • Try different GPIO pin
  • Test with example sketch

WiFi Won't Connect

  • Verify SSID and password
  • Check 2.4GHz network (ESP32 doesn't support 5GHz)
  • Move closer to router
  • Check router's MAC filtering

HTTP 401 Unauthorized

  • Verify API key is correct
  • Check X-API-Key header format
  • Ensure API key has data:write permission
  • Generate new API key if needed

Data Not Appearing

  • Check bucket ID and data source ID
  • Verify WiFi is connected
  • Check serial monitor for errors
  • Test API key with curl

Next Steps

Full Project Code

Complete working code is available at: https://github.com/dakkio/examples/tree/main/iot-sensors

Need Help?