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
- Update WiFi credentials in the code
- Add your API key, bucket ID, and data source ID
- Select "ESP32 Dev Module" in Arduino IDE
- Upload the sketch
- 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
- Go to https://api.slack.com/messaging/webhooks
- Click "Create New App" → "From scratch"
- Name: "dakkio Alerts"
- Select your workspace
- Click "Incoming Webhooks" → Enable
- Click "Add New Webhook to Workspace"
- Select channel (e.g., #alerts)
- 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-Keyheader format - Ensure API key has
data:writepermission - 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?
- 📖 API Reference
- 💬 Discord Community
- 📧 Email: support@dakkio.io