6 min read
Azure Digital Twins for Smart Environment Modeling
Azure Digital Twins enables you to create comprehensive digital models of physical environments. Whether modeling buildings, factories, energy networks, or cities, Digital Twins provides the foundation for understanding and optimizing your physical world through digital representation.
Understanding Digital Twins
Digital Twins creates a knowledge graph of your environment:
- Models: Define the types of entities (DTDL - Digital Twins Definition Language)
- Twins: Instances of models representing physical entities
- Relationships: Connections between twins
- Properties: Current state of each twin
Setting Up Azure Digital Twins
# Create Digital Twins instance
az dt create \
--dt-name mydigitaltwins \
--resource-group myResourceGroup \
--location eastus
# Assign role to yourself
az dt role-assignment create \
--dt-name mydigitaltwins \
--assignee "user@company.com" \
--role "Azure Digital Twins Data Owner"
# Get endpoint
az dt show \
--dt-name mydigitaltwins \
--query hostName \
--output tsv
Defining Models with DTDL
// Building model
{
"@id": "dtmi:example:Building;1",
"@type": "Interface",
"displayName": "Building",
"@context": "dtmi:dtdl:context;2",
"contents": [
{
"@type": "Property",
"name": "name",
"schema": "string"
},
{
"@type": "Property",
"name": "address",
"schema": "string"
},
{
"@type": "Property",
"name": "totalFloors",
"schema": "integer"
},
{
"@type": "Property",
"name": "yearBuilt",
"schema": "integer"
},
{
"@type": "Relationship",
"name": "hasFloor",
"target": "dtmi:example:Floor;1"
}
]
}
// Floor model
{
"@id": "dtmi:example:Floor;1",
"@type": "Interface",
"displayName": "Floor",
"@context": "dtmi:dtdl:context;2",
"contents": [
{
"@type": "Property",
"name": "floorNumber",
"schema": "integer"
},
{
"@type": "Property",
"name": "totalRooms",
"schema": "integer"
},
{
"@type": "Relationship",
"name": "hasRoom",
"target": "dtmi:example:Room;1"
}
]
}
// Room model with telemetry
{
"@id": "dtmi:example:Room;1",
"@type": "Interface",
"displayName": "Room",
"@context": "dtmi:dtdl:context;2",
"contents": [
{
"@type": "Property",
"name": "roomName",
"schema": "string"
},
{
"@type": "Property",
"name": "roomType",
"schema": {
"@type": "Enum",
"valueSchema": "string",
"enumValues": [
{ "name": "conference", "enumValue": "conference" },
{ "name": "office", "enumValue": "office" },
{ "name": "restroom", "enumValue": "restroom" },
{ "name": "lobby", "enumValue": "lobby" }
]
}
},
{
"@type": "Property",
"name": "capacity",
"schema": "integer"
},
{
"@type": ["Property", "Temperature"],
"name": "temperature",
"schema": "double",
"unit": "degreeCelsius"
},
{
"@type": "Property",
"name": "humidity",
"schema": "double"
},
{
"@type": "Property",
"name": "occupancy",
"schema": "integer"
},
{
"@type": "Relationship",
"name": "hasSensor",
"target": "dtmi:example:Sensor;1"
}
]
}
// Sensor model
{
"@id": "dtmi:example:Sensor;1",
"@type": "Interface",
"displayName": "Sensor",
"@context": "dtmi:dtdl:context;2",
"contents": [
{
"@type": "Property",
"name": "sensorId",
"schema": "string"
},
{
"@type": "Property",
"name": "sensorType",
"schema": "string"
},
{
"@type": "Property",
"name": "lastReading",
"schema": "double"
},
{
"@type": "Property",
"name": "lastReadingTime",
"schema": "dateTime"
},
{
"@type": "Property",
"name": "batteryLevel",
"schema": "double"
}
]
}
Working with Digital Twins SDK
from azure.digitaltwins.core import DigitalTwinsClient
from azure.identity import DefaultAzureCredential
import json
# Connect to Digital Twins
credential = DefaultAzureCredential()
client = DigitalTwinsClient(
"https://mydigitaltwins.api.eus.digitaltwins.azure.net",
credential
)
# Upload models
models = [building_model, floor_model, room_model, sensor_model]
created_models = client.create_models(models)
# Create Building twin
building_twin = {
"$metadata": {
"$model": "dtmi:example:Building;1"
},
"name": "Headquarters",
"address": "123 Main St, Seattle, WA",
"totalFloors": 5,
"yearBuilt": 2015
}
client.upsert_digital_twin("building-hq", building_twin)
# Create Floor twins
for floor_num in range(1, 6):
floor_twin = {
"$metadata": {
"$model": "dtmi:example:Floor;1"
},
"floorNumber": floor_num,
"totalRooms": 10
}
client.upsert_digital_twin(f"floor-{floor_num}", floor_twin)
# Create relationship to building
relationship = {
"$relationshipId": f"building-hq-floor-{floor_num}",
"$sourceId": "building-hq",
"$relationshipName": "hasFloor",
"$targetId": f"floor-{floor_num}"
}
client.upsert_relationship(
"building-hq",
f"building-hq-floor-{floor_num}",
relationship
)
# Create Room twins
room_configs = [
{"id": "room-101", "name": "Conference A", "type": "conference", "floor": 1, "capacity": 20},
{"id": "room-102", "name": "Executive Office", "type": "office", "floor": 1, "capacity": 1},
{"id": "room-201", "name": "Open Space", "type": "office", "floor": 2, "capacity": 50},
]
for room in room_configs:
room_twin = {
"$metadata": {
"$model": "dtmi:example:Room;1"
},
"roomName": room["name"],
"roomType": room["type"],
"capacity": room["capacity"],
"temperature": 22.0,
"humidity": 45.0,
"occupancy": 0
}
client.upsert_digital_twin(room["id"], room_twin)
# Relationship to floor
client.upsert_relationship(
f"floor-{room['floor']}",
f"floor-{room['floor']}-{room['id']}",
{
"$relationshipId": f"floor-{room['floor']}-{room['id']}",
"$sourceId": f"floor-{room['floor']}",
"$relationshipName": "hasRoom",
"$targetId": room["id"]
}
)
Querying Digital Twins
# Query all buildings
query = "SELECT * FROM digitaltwins WHERE IS_OF_MODEL('dtmi:example:Building;1')"
result = client.query_twins(query)
for twin in result:
print(f"Building: {twin['name']}")
# Query rooms with high temperature
query = """
SELECT room
FROM digitaltwins room
WHERE IS_OF_MODEL(room, 'dtmi:example:Room;1')
AND room.temperature > 25
"""
hot_rooms = list(client.query_twins(query))
# Query with relationships - find all rooms in a building
query = """
SELECT room
FROM digitaltwins building
JOIN floor RELATED building.hasFloor
JOIN room RELATED floor.hasRoom
WHERE building.$dtId = 'building-hq'
"""
rooms_in_building = list(client.query_twins(query))
# Aggregate queries
query = """
SELECT COUNT()
FROM digitaltwins room
WHERE IS_OF_MODEL(room, 'dtmi:example:Room;1')
AND room.occupancy > 0
"""
occupied_count = list(client.query_twins(query))
# Complex query with multiple conditions
query = """
SELECT room, floor
FROM digitaltwins floor
JOIN room RELATED floor.hasRoom
WHERE floor.floorNumber = 2
AND room.roomType = 'conference'
AND room.occupancy < room.capacity
"""
available_conference_rooms = list(client.query_twins(query))
Integrating with IoT Hub
# Azure Function to process IoT Hub telemetry and update Digital Twins
import azure.functions as func
from azure.digitaltwins.core import DigitalTwinsClient
from azure.identity import ManagedIdentityCredential
import json
def main(event: func.EventHubEvent):
# Parse IoT Hub message
body = json.loads(event.get_body().decode('utf-8'))
device_id = event.iothub_metadata['connection-device-id']
# Connect to Digital Twins
credential = ManagedIdentityCredential()
client = DigitalTwinsClient(
"https://mydigitaltwins.api.eus.digitaltwins.azure.net",
credential
)
# Find the room twin associated with this sensor
query = f"""
SELECT room
FROM digitaltwins sensor
JOIN room RELATED sensor.installedIn
WHERE sensor.sensorId = '{device_id}'
"""
results = list(client.query_twins(query))
if results:
room_id = results[0]['room']['$dtId']
# Update room properties based on sensor data
update_patch = []
if 'temperature' in body:
update_patch.append({
"op": "replace",
"path": "/temperature",
"value": body['temperature']
})
if 'humidity' in body:
update_patch.append({
"op": "replace",
"path": "/humidity",
"value": body['humidity']
})
if 'occupancy' in body:
update_patch.append({
"op": "replace",
"path": "/occupancy",
"value": body['occupancy']
})
client.update_digital_twin(room_id, update_patch)
# Also update sensor's last reading
sensor_patch = [
{"op": "replace", "path": "/lastReading", "value": body.get('temperature', 0)},
{"op": "replace", "path": "/lastReadingTime", "value": event.enqueued_time.isoformat()}
]
client.update_digital_twin(device_id, sensor_patch)
Event Routing
# Create Event Grid endpoint
az dt endpoint create eventgrid \
--dt-name mydigitaltwins \
--endpoint-name adt-eventgrid \
--eventgrid-resource-group myResourceGroup \
--eventgrid-topic myeventgridtopic
# Create event route
az dt route create \
--dt-name mydigitaltwins \
--endpoint-name adt-eventgrid \
--route-name adt-route \
--filter "type = 'Microsoft.DigitalTwins.Twin.Update'"
Visualization with 3D Scenes
// 3D Scene Studio configuration (simplified example)
const sceneConfig = {
scenes: [
{
id: "building-view",
displayName: "Building Overview",
twins: [
{
twinId: "building-hq",
primaryTwinIdExpression: "$dtId",
behaviors: [
{
id: "temperature-color",
type: "ColorChange",
valueExpression: "temperature",
colorRanges: [
{ min: 0, max: 18, color: "#0000FF" }, // Cold - Blue
{ min: 18, max: 24, color: "#00FF00" }, // Normal - Green
{ min: 24, max: 35, color: "#FF0000" } // Hot - Red
]
}
]
}
]
}
]
};
Best Practices
- Model Design: Start with clear entity relationships
- Naming Conventions: Use consistent twin and relationship IDs
- Query Optimization: Index frequently queried properties
- Event Handling: Use routes for downstream processing
- Security: Apply least privilege with RBAC
Conclusion
Azure Digital Twins enables sophisticated modeling of physical environments:
- Knowledge Graph: Rich representation of entities and relationships
- Real-Time Updates: Live state from IoT devices
- Powerful Queries: SQL-like querying across the twin graph
- Event Integration: React to changes in real-time
- Visualization: 3D scene rendering for intuitive understanding
It’s the foundation for smart building, manufacturing, and infrastructure solutions.