This project implements a complete real-time IoT monitoring system on Azure that collects system metrics (CPU temperature, RAM usage, disk space) from a laptop, processes them through Azure services, and displays them on a live dashboard.
- High-Level Architecture
- System Components
- Data Flow
- Infrastructure Deployment
- Application Deployment
- Azure DevOps CI/CD Pipelines
- Monitoring and Operations
- Security Considerations
- Cost Optimization
- Testing
- Troubleshooting
- Technology Stack
- Project Structure
┌─────────────────┐
│ Windows Laptop │
│ IoT Emulator │
│ (Java 21) │
└────────┬────────┘
│ MQTT
│ Telemetry Data
↓
┌─────────────────┐
│ Azure IoT Hub │
│ (S1 Tier) │
└────────┬────────┘
│ Event Grid
│ Integration
↓
┌─────────────────┐
│ Event Grid │
│ System Topic │
└────────┬────────┘
│ Triggers
↓
┌─────────────────┐
│ Azure Function │
│ (Java 21) │
│ Consumption │
└────────┬────────┘
│ Stores Data
↓
┌─────────────────┐
│ Azure Redis │
│ Cache (Basic) │
└────────┬────────┘
│ Reads Data
↓
┌─────────────────┐
│ Spring Boot │
│ WebFlux API │
│ (Docker/ACR) │
└────────┬────────┘
│ Server-Sent
│ Events (SSE)
↓
┌─────────────────┐
│ HTML Dashboard │
│ (Real-time UI) │
└─────────────────┘
Technology: Java 21, Maven
Purpose: Simulates IoT device collecting system metrics
Functionality:
- Collects CPU temperature using OSHI library
- Monitors CPU usage percentage
- Tracks RAM usage (used/total MB)
- Monitors disk space (used/total GB)
- Sends telemetry every 5 seconds via MQTT protocol
Azure SDK: azure-iot-device-client for IoT Hub communication
Key Dependencies:
com.microsoft.azure.sdk.iot:iot-device-client:2.4.3com.github.oshi:oshi-core:6.4.10com.google.code.gson:gson:2.10.1
SKU: S1 (Standard tier)
Purpose: Central message hub for IoT device communication
Configuration:
- Device identity:
laptop-monitor - Protocol: MQTT
- Retention: 1 day
- Partition count: 2
Features:
- Device-to-cloud messaging
- Device authentication via connection strings
- Integration with Event Grid for event routing
Type: System Topic for IoT Hub
Purpose: Routes IoT Hub telemetry events to Azure Function
Event Types: Microsoft.Devices.DeviceTelemetry
Configuration:
- Event subscription with Azure Function as endpoint
- Retry policy: 30 attempts, 1440 minutes TTL
- Batch size: 1 event per batch
Runtime: Java 21 on Linux
Plan: Consumption (Y1 SKU)
Trigger: Event Grid trigger
Purpose: Process incoming telemetry and store in Redis
Functionality:
- Receives telemetry events from Event Grid
- Parses JSON payload (handles base64 encoding)
- Stores data in Redis with TTL (1 hour expiration)
- Maintains sorted set for time-based retrieval
- Limits storage to last 1000 entries
Dependencies:
azure-functions-java-library:3.1.0jedis:5.1.0for Redis connectiongson:2.10.1for JSON parsing
Function Code Structure:
@FunctionName("ProcessIoTEvent")
public void processEvent(
@EventGridTrigger(name = "event") String eventData,
final ExecutionContext context) {
// Parse event, decode payload, store in Redis
}SKU: Basic C0
Purpose: Temporary storage for telemetry data
Configuration:
- SSL enabled (port 6380)
- TLS 1.2 minimum
- Public network access enabled
- Eviction policy:
allkeys-lru
Data Structure:
- Keys:
telemetry:<timestamp> - Sorted set:
telemetry:timeline(for time-ordered retrieval) - TTL: 1 hour per entry
SKU: Standard
Purpose: Store Docker images for Spring Boot application
Configuration:
- Admin user enabled
- Repository:
iot-dashboard:latest - Public network access enabled
Framework: Spring Boot 3.4.1, Java 21
Architecture: Reactive WebFlux
Hosting: Azure App Service (B1 Linux)
Container: Docker image from ACR
Purpose: REST API for telemetry data with real-time streaming
REST Endpoints:
GET /api/telemetry/stream- Server-Sent Events (SSE) streamGET /api/telemetry/recent?count=N- Last N telemetry recordsGET /actuator/health- Health check endpoint
Key Components:
- RedisConfig: Reactive Redis connection with Lettuce client
- TelemetryService: Flux-based reactive data streaming
- TelemetryController: WebFlux REST endpoints with CORS enabled
- TelemetryModel: Data model for telemetry records
Configuration:
server:
port: 8080
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}Docker Configuration:
FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]Technology: Pure HTML5, CSS3, JavaScript (ES6)
Hosting: Served as static content from Spring Boot app
Location: src/main/resources/static/index.html
Features:
- Real-time updates via Server-Sent Events
- Responsive grid layout with glassmorphism design
- Visual progress bars with color coding (green/yellow/red)
- Alert system for threshold violations (>75°C, >90% usage)
- Auto-reconnection on connection loss
- No page reload required
UI Components:
- CPU Temperature card (threshold: >75°C warning)
- CPU Usage card (threshold: >90% warning)
- RAM Usage card with total/used display
- Disk Usage card with total/used display
- Connection status indicator
- Last update timestamp
IoT Emulator → Collects metrics every 5 seconds
↓
Creates JSON payload:
{
"deviceId": "laptop-monitor",
"timestamp": "2026-01-26T10:30:00Z",
"cpuTemperature": 55.3,
"cpuUsage": 23.5,
"ramUsedMB": 8192,
"ramTotalMB": 16384,
"ramUsagePercent": 50.0,
"diskUsedGB": 120,
"diskTotalGB": 256,
"diskUsagePercent": 46.9
}
MQTT Client → Sends to IoT Hub
↓
IoT Hub validates device identity
↓
Publishes to Event Grid topic
Event Grid → Triggers Azure Function
↓
Function parses event data
↓
Decodes base64 payload if needed
↓
Stores in Redis:
- Key: telemetry:<timestamp>
- Value: JSON string
- TTL: 3600 seconds
- Sorted set: telemetry:timeline
Dashboard → HTTP GET to /api/telemetry/stream
↓
Spring Boot WebFlux establishes SSE connection
↓
Flux.interval(1 second) polls Redis
↓
Queries sorted set for latest entry
↓
Returns Flux<TelemetryModel>
↓
Streams to client as Server-Sent Events
JavaScript EventSource → Receives SSE messages
↓
Parses JSON
↓
Updates DOM elements
↓
Updates progress bars
↓
Checks thresholds
↓
Displays alerts if needed
All infrastructure is deployed using pure Azure CLI commands.
# Azure CLI
az login
az account set --subscription "YOUR_SUBSCRIPTION_ID"
# Java 21
java -version
# Maven
mvn -version
# Docker
docker --versionexport SUBSCRIPTION_ID="your-subscription-id"
export RESOURCE_GROUP="rg-iot-monitoring"
export LOCATION="westeurope"
export UNIQUE_SUFFIX=$(date +%s | sha256sum | base64 | head -c 8 | tr '[:upper:]' '[:lower:]')
az account set --subscription $SUBSCRIPTION_IDaz group create \
--name $RESOURCE_GROUP \
--location $LOCATIONexport STORAGE_ACCOUNT="stfunc${UNIQUE_SUFFIX}"
az storage account create \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku Standard_LRS \
--kind StorageV2 \
--https-only true \
--min-tls-version TLS1_2export IOT_HUB_NAME="iothub-mon-${UNIQUE_SUFFIX}"
az iot hub create \
--name $IOT_HUB_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku S1 \
--partition-count 2 \
--retention-day 1
az iot hub device-identity create \
--hub-name $IOT_HUB_NAME \
--device-id laptop-monitorexport REDIS_NAME="redis-mon-${UNIQUE_SUFFIX}"
az redis create \
--name $REDIS_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku Basic \
--vm-size C0 \
--enable-non-ssl-port false \
--minimum-tls-version 1.2export ACR_NAME="acrmon${UNIQUE_SUFFIX}"
az acr create \
--name $ACR_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku Standard \
--admin-enabled trueexport FUNCTION_APP_NAME="func-proc-${UNIQUE_SUFFIX}"
az functionapp create \
--name $FUNCTION_APP_NAME \
--resource-group $RESOURCE_GROUP \
--storage-account $STORAGE_ACCOUNT \
--consumption-plan-location $LOCATION \
--runtime java \
--runtime-version 21 \
--functions-version 4 \
--os-type Linuxexport APP_SERVICE_PLAN="plan-webapp-${UNIQUE_SUFFIX}"
export WEB_APP_NAME="webapp-dash-${UNIQUE_SUFFIX}"
az appservice plan create \
--name $APP_SERVICE_PLAN \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--is-linux \
--sku B1
az webapp create \
--name $WEB_APP_NAME \
--resource-group $RESOURCE_GROUP \
--plan $APP_SERVICE_PLAN \
--deployment-container-image-name nginxexport EVENT_GRID_TOPIC="evt-mon-${UNIQUE_SUFFIX}"
az eventgrid system-topic create \
--name $EVENT_GRID_TOPIC \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--topic-type Microsoft.Devices.IoTHubs \
--source $(az iot hub show --name $IOT_HUB_NAME -g $RESOURCE_GROUP --query id -o tsv)cd iot-function
mvn clean package
cd target/azure-functions/iot-function-local
zip -r ../../../../function-app.zip .
cd ../../../..
az functionapp deployment source config-zip \
--resource-group $RESOURCE_GROUP \
--name $FUNCTION_APP_NAME \
--src function-app.zipcd iot-dashboard-service
mvn clean package -DskipTests
docker build -t iot-dashboard:latest .
az acr login --name $ACR_NAME
docker tag iot-dashboard:latest $ACR_NAME.azurecr.io/iot-dashboard:latest
docker push $ACR_NAME.azurecr.io/iot-dashboard:latest
az webapp config container set \
--name $WEB_APP_NAME \
--resource-group $RESOURCE_GROUP \
--docker-custom-image-name $ACR_NAME.azurecr.io/iot-dashboard:latest
az webapp restart --name $WEB_APP_NAME --resource-group $RESOURCE_GROUPaz iot hub device-identity connection-string show \
--hub-name $IOT_HUB_NAME \
--device-id laptop-monitor \
--query connectionString -o tsv
# Update CONNECTION_STRING in IoTEmulator.java
cd iot-emulator
mvn clean package
java -jar target/iot-emulator-1.0.0.jaraz ad sp create-for-rbac \
--name "sp-azdo-iot-monitoring" \
--role Contributor \
--scopes /subscriptions/$SUBSCRIPTION_IDConfigure in Azure DevOps Project Settings → Service connections → New → Azure Resource Manager → Service principal (manual)
trigger:
branches:
include:
- main
paths:
include:
- iot-function/**
pool:
vmImage: 'ubuntu-latest'
variables:
azureSubscription: 'azure-iot-monitoring-sc'
functionAppName: 'func-proc-<suffix>'
resourceGroup: 'rg-iot-monitoring'
stages:
- stage: Build
jobs:
- job: BuildFunction
steps:
- task: Maven@3
inputs:
mavenPomFile: 'iot-function/pom.xml'
goals: 'clean package'
- stage: Deploy
jobs:
- deployment: DeployFunction
environment: 'production-function'
strategy:
runOnce:
deploy:
steps:
- task: AzureFunctionApp@2
inputs:
azureSubscription: $(azureSubscription)
appName: $(functionAppName)
package: '$(System.ArtifactsDirectory)/function-app.zip'trigger:
branches:
include:
- main
paths:
include:
- iot-dashboard-service/**
pool:
vmImage: 'ubuntu-latest'
variables:
azureSubscription: 'azure-iot-monitoring-sc'
webAppName: 'webapp-dash-<suffix>'
containerRegistry: 'acrmon<suffix>'
stages:
- stage: Build
jobs:
- job: BuildApp
steps:
- task: Maven@3
inputs:
mavenPomFile: 'iot-dashboard-service/pom.xml'
goals: 'clean package'
- task: Docker@2
inputs:
command: 'buildAndPush'
repository: 'iot-dashboard'
- stage: Deploy
jobs:
- deployment: DeployWebApp
environment: 'production-webapp'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebAppContainer@1
inputs:
azureSubscription: $(azureSubscription)
appName: $(webAppName)# Function App
az functionapp function list --name $FUNCTION_APP_NAME -g $RESOURCE_GROUP
# Web App
curl https://$WEB_APP_URL/actuator/health
# Redis
az redis show --name $REDIS_NAME -g $RESOURCE_GROUP --query provisioningState# Function logs
az functionapp logs tail --name $FUNCTION_APP_NAME -g $RESOURCE_GROUP
# Web App logs
az webapp log tail --name $WEB_APP_NAME -g $RESOURCE_GROUP
# IoT Hub events
az iot hub monitor-events --hub-name $IOT_HUB_NAME- Authentication: IoT devices use SAS token connection strings
- Network: All traffic over HTTPS/TLS 1.2+
- Secrets: Stored in App Settings (encrypted at rest)
- Redis: SSL-only mode (port 6380)
- CORS: Configured for Web App API access
| Resource | SKU | Monthly Cost (USD) |
|---|---|---|
| IoT Hub | S1 | ~$25 |
| Redis Cache | Basic C0 | ~$16 |
| Function App | Consumption | ~$0.20/million executions |
| App Service | B1 | ~$13 |
| Container Registry | Standard | ~$20 |
| Total | ~$75-100/month |
Optimization Tips:
- Use F1 (Free) IoT Hub tier if only one hub needed
- Reduce Redis TTL to 30 minutes
- Scale down App Service during off-hours
- Clean up old Docker images regularly
# Terminal 1: Run emulator
cd iot-emulator
java -jar target/iot-emulator-1.0.0.jar
# Terminal 2: Monitor IoT Hub
az iot hub monitor-events --hub-name $IOT_HUB_NAME
# Terminal 3: Test API
curl https://$WEB_APP_URL/api/telemetry/recent?count=5
# Terminal 4: Open dashboard
open https://$WEB_APP_URLaz iot hub device-identity connection-string show \
--hub-name $IOT_HUB_NAME \
--device-id laptop-monitoraz eventgrid system-topic event-subscription show \
--name iot-telemetry-subscription \
-g $RESOURCE_GROUP \
--system-topic-name $EVENT_GRID_TOPICcurl -v https://$WEB_APP_URL/api/telemetry/recent?count=1
az webapp log tail --name $WEB_APP_NAME -g $RESOURCE_GROUP| Component | Technology | Version |
|---|---|---|
| Language | Java | 21 |
| Build Tool | Maven | 3.9+ |
| IoT SDK | Azure IoT Device Client | 2.4.3 |
| Backend Framework | Spring Boot | 3.4.1 |
| Reactive Framework | Spring WebFlux | 6.2.1 |
| Redis Client | Jedis / Lettuce | 5.1.0 |
| Container Runtime | Docker | Latest |
| Cloud Platform | Microsoft Azure | - |
iot-monitoring-system/
├── iot-emulator/
│ ├── pom.xml
│ └── src/main/java/com/iot/emulator/
│ └── IoTEmulator.java
├── iot-function/
│ ├── pom.xml
│ ├── host.json
│ └── src/main/java/com/iot/function/
│ └── IoTEventProcessor.java
├── iot-dashboard-service/
│ ├── pom.xml
│ ├── Dockerfile
│ ├── src/main/java/com/iot/dashboard/
│ │ ├── IoTDashboardApplication.java
│ │ ├── config/RedisConfig.java
│ │ ├── controller/TelemetryController.java
│ │ ├── service/TelemetryService.java
│ │ └── model/TelemetryModel.java
│ └── src/main/resources/
│ ├── application.yml
│ └── static/index.html
├── azure-pipelines-function.yml
├── azure-pipelines-webapp.yml
└── README.md
az group delete --name $RESOURCE_GROUP --yes --no-waitThis IoT monitoring system demonstrates a complete cloud-native architecture using Azure PaaS services with real-time data streaming, serverless computing, and modern CI/CD practices.
✅ Real-time data streaming with sub-second latency
✅ Serverless architecture for cost optimization
✅ Reactive programming for efficient resource usage
✅ Containerized deployments for portability
✅ Automated CI/CD pipelines for rapid iteration
✅ Infrastructure deployed entirely via Azure CLI
✅ Modern Java 21 features and Spring Boot 3.x
Last Updated: January 2026
Version: 1.0.0
Platform: Microsoft Azure
Runtime: Java 21