A mini end-to-end IoT project: simulated machine sensors → Azure IoT Hub → Azure Functions → Azure SQL → Power BI dashboards, with automatic alerting when temperature or vibration exceed safe thresholds.
┌──────────────────┐ ┌────────────┐ ┌──────────────────┐ ┌────────────┐ ┌───────────┐
│ C# Sensor │ MQTT │ Azure │ Event │ Azure Function │ │ Azure SQL │ │ Power BI │
│ Simulator ├─────>│ IoT Hub ├──────>│ (ProcessTelemetry│─────>│ Database │─────>│ Dashboard│
│ (temp+vibration) │ │ │ Hub │ trigger) │ │ │ │ │
└──────────────────┘ └────────────┘ └─────────┬────────┘ └────────────┘ └───────────┘
│
│ threshold breached
v
┌───────────────────┐
│ Logic App / Power │
│ Automate -> Email, │
│ Teams, SMS alert │
└───────────────────┘
SmartFactoryMonitoring/
├── SensorSimulator/ C# console app - simulates machines, sends telemetry
│ ├── MachineSensor.cs Random-walk sensor model with fault injection
│ ├── MachineTelemetry.cs Telemetry data shape
│ └── Program.cs Connects to IoT Hub and streams readings
├── AzureFunctions/ProcessTelemetry/ Azure Function (isolated worker, .NET 8)
│ ├── TelemetryProcessorFunction.cs Event Hub-triggered processor + threshold checks
│ ├── MachineTelemetry.cs
│ ├── host.json / local.settings.json
├── Database/
│ ├── schema.sql Azure SQL tables + Power BI-ready views
│ └── CosmosAlternative.cs.txt Drop-in Cosmos DB storage option
└── docs/ (this README)
MachineSensordoes a random walk around baseline values (55°C, 2.5 mm/s) and injects a fault ~5% of the time so you'll actually see Warning/Critical readings and alerts without waiting for real hardware to break.- Run without any Azure setup first to sanity-check the data:
(No connection string → runs in offline demo mode, prints JSON to console.)
cd SensorSimulator dotnet run
az iot hub create --name <your-hub-name> --resource-group <rg> --sku S1
az iot hub device-identity create --hub-name <your-hub-name> --device-id machine-01
az iot hub device-identity connection-string show --hub-name <your-hub-name> --device-id machine-01
Set the connection string and run for real:
export IOTHUB_DEVICE_CONNECTION_STRING="HostName=...;DeviceId=machine-01;SharedAccessKey=..."
export MACHINE_ID="machine-01"
dotnet run
Repeat with different DeviceId/MACHINE_ID values (in separate terminals or
as separate deployed instances) to simulate a fleet of machines.
The function binds to IoT Hub's built-in Event Hub-compatible endpoint (IoT Hub → Built-in endpoints → Event Hub-compatible endpoint), so no separate Event Hub resource is needed.
cd AzureFunctions/ProcessTelemetry
func azure functionapp publish <your-function-app-name>
Configure these Application Settings on the Function App (Azure Portal →
Function App → Configuration), matching local.settings.json:
IoTHubEventHubConnectionString— the built-in endpoint connection stringIoTHubEventHubName— the Event Hub-compatible name shown alongside itSqlConnectionString— your Azure SQL connection stringAlertLogicAppUrl— HTTP trigger URL of a Logic App/Power Automate flow (optional but recommended)
The function:
- Parses each telemetry JSON message.
- Inserts a row into
dbo.Telemetry. - Checks it against thresholds (defaults: Warning at 75°C / 5 mm/s, Critical at 90°C / 7 mm/s — tune these constants for your equipment).
- On a breach, inserts a row into
dbo.Alertsand POSTs a JSON payload to your alert webhook.
az sql db create --resource-group <rg> --server <sql-server> --name FactoryMonitoring
Then run schema.sql against it (Azure Data Studio, SSMS, or sqlcmd). It
creates:
dbo.Telemetry,dbo.Alerts,dbo.Machinesvw_LatestReadingPerMachine,vw_HourlyAverages,vw_OpenAlerts— views designed to be queried directly by Power BI.
Prefer NoSQL? See Database/CosmosAlternative.cs.txt for a drop-in Cosmos DB
version of the storage step — Power BI's Cosmos DB connector works the same
way as its SQL connector.
Create a Logic App with an HTTP Request trigger, then add an Office
365 Outlook — Send an email (or Teams "Post message") action using fields
from the JSON body: machineId, severity, reason, temperatureC,
vibrationMmS. Paste the trigger's generated URL into AlertLogicAppUrl.
- Get Data → Azure SQL Database → point at your server/database, select
vw_LatestReadingPerMachine,vw_HourlyAverages,vw_OpenAlerts. - Suggested visuals:
- Card/gauge per machine using
vw_LatestReadingPerMachine(current temp & vibration vs. threshold lines). - Line chart of
AvgTemperatureC/AvgVibrationMmSoverHourBucketUtcfromvw_HourlyAverages, one line perMachineId. - Table of
vw_OpenAlertssorted byAlertTimeUtcdescending, conditional formatting onSeverity(red = Critical, amber = Warning). - Map or matrix by
Location(fromdbo.Machines) to see problem areas across the factory floor.
- Card/gauge per machine using
- Set the dataset refresh schedule (Settings → Scheduled refresh) — e.g. every 15–30 minutes — or use DirectQuery mode on the views for near-real-time updates without manual refresh.
Defaults live in TelemetryProcessorFunction.cs:
| Metric | Warning | Critical |
|---|---|---|
| Temperature | 75°C | 90°C |
| Vibration | 5 mm/s | 7 mm/s |
Adjust these constants to match your actual machine specs (ISO 10816 is a common reference standard for vibration severity by machine class).
- Run
SensorSimulatorwith no env vars → offline demo mode (console only). - Run the Function locally with
func startand Azurite for storage; pointIoTHubEventHubConnectionStringat a real IoT Hub (Functions Core Tools can consume from a live Event Hub-compatible endpoint even while the rest of your app runs locally).