Milestone 4
Learning Objectives
- Configure Azure IoT Hub and Web PubSub for real-time bidirectional communication
- Implement device simulators with interactive keyboard controls that send telemetry to IoT Hub
- Build Azure Functions REST API with WebSocket broadcasting via Azure Web PubSub
- Display real-time telemetry data in an Expo app using native WebSocket connections
- Execute cloud-to-device (C2D) commands with instant feedback via WebSocket updates
- Implement authentication with protected routes and role-based access control
🔍 Context
Section titled “🔍 Context”In this milestone, you’ll integrate your IoT devices from Connected Objects (6P3) with your Expo app. Since working directly with physical hardware can be challenging during development, you’ll create device simulators that mock your reterminal subsystems. This allows you to:
- Develop and test your Expo app independently of hardware availability
- Verify your Azure IoT Hub configuration works correctly
- Test bidirectional communication (Device-to-Cloud and Cloud-to-Device)
- Iterate quickly without hardware debugging
In Milestone 5, you’ll replace these simulators with your actual reterminal subsystems.
Architecture Overview
Section titled “Architecture Overview”Here’s how the components communicate:
- Device-to-Cloud (D2C): Simulator sends telemetry → IoT Hub → Event Hub Listener → Web PubSub broadcasts → Expo app receives via WebSocket (real-time)
- Cloud-to-Device (C2D): Expo app sends command → Azure Function → IoT Hub → Simulator executes → Sends immediate telemetry → Updates app via WebSocket
- Interactive Events: Keyboard press in simulator → Immediate telemetry → WebSocket push → Instant app update (< 100ms latency)
This architecture uses native WebSocket protocol along with Azure Web PubSub to provide a scalable WebSocket infrastructure. It also enables bidirectional real-time communication with instant feedback, with HTTP endpoints available as fallback via manual refresh.
📦 Understanding the Starter Template
Section titled “📦 Understanding the Starter Template”You’ll start with the iot-hub-starter template which provides the foundational structure while requiring you to customize for your specific devices.
What’s Provided
Section titled “What’s Provided”- Complete authentication system (login, protected routes, user roles)
- WebSocket real-time connection setup with Azure Web PubSub
- Tab-based navigation (one tab per team member’s device)
- Type-safe patterns with comprehensive examples
- Working device simulator example (temperature/humidity/motion/door/LED)
- Azure Function middleware with Event Hub listener and Web PubSub broadcasting
- CORS utilities and error handling
What You’ll Build
Section titled “What You’ll Build”Each team member is responsible for one device tab:
- Define YOUR device’s telemetry structure (based on 6P3 hardware)
- Customize the device simulator for YOUR sensors/actuators
- Build YOUR device’s UI in your assigned tab
- Implement commands for YOUR device’s actuators (if applicable)
For M4, each student works independently. You’ll have your own Azure IoT Hub, your own customized device simulator, your own Azure Function deployment, and your assigned tab in the shared Expo app repo. For M5, you’ll merge into one shared Azure infrastructure.
Expected Project Structure
Section titled “Expected Project Structure”Directoryiot-hub-starter/
Directoryapp/ Expo App (SHARED REPO)
Directoryapp/(tabs)/
- device1.tsx ← Student 1 edits THIS
- device2.tsx ← Student 2 edits THIS
- device3.tsx ← Student 3 edits THIS
- _layout.tsx Tab navigation
Directoryconfig/
- devices.ts ← ALL students configure their device IDs
Directorytypes/
- telemetry.ts ← ALL students add their device interface
- commands.ts ← ALL students add their command types
Directoryservices/
- api.ts
- signalr.ts
Directorycontexts/
- AuthContext.tsx
Directoryazure-function/ Individual for M4, merged for M5
Directorysrc/
Directoryfunctions/
- getTelemetry.ts
- sendCommand.ts
- negotiate.ts
- eventHubListener.ts
- types.ts ← Students coordinate types here too
Directorydevice-simulators/
Directorydevice-simulator-example/ Reference - each student copies
- simulator.ts Complete working example
- .env.example
- README.md Customization guide
Directorydevice-simulator-NAME1/ ← Student 1’s customized simulator
- …
Directorydevice-simulator-NAME2/ ← Student 2’s customized simulator
- …
Directorydevice-simulator-NAME3/ ← Student 3’s customized simulator
- …
🌿 Part 0: Branch
Section titled “🌿 Part 0: Branch”Before starting, one team member needs to set up the starter template in your shared repository.
-
Designate one team member to set up the branch (this person will be “Team Lead” for setup only).
-
Team Lead: Download the starter template from Moodle and extract it to a temporary location on your computer.
-
Team Lead: In your team’s project repository, create and switch to a new branch:
Terminal window git checkout -b milestone-4 -
Team Lead: Drag the following folders from the extracted starter template into your repository root:
app/(Expo app with tabs)azure-function/(REST API middleware)device-simulator-example/(reference implementation)
-
Team Lead: Commit and push the starter template:
Terminal window git add app/ azure-function/ device-simulator-example/git commit -m "Add Milestone 4 starter template"git push -u origin milestone-4 -
Other team members: Pull the milestone-4 branch:
Terminal window git fetchgit checkout milestone-4 -
All team members: Verify you have the three folders (
app/,azure-function/,device-simulator-example/) in your local repository before proceeding to Part 1.
☁️ Part 1: Azure Setup
Section titled “☁️ Part 1: Azure Setup”You should have already completed this in your Connected Objects course, but if not, follow the Getting Started with Azure instructions.
-
Log into portal.azure.com with your student account and confirm you have Azure for Students credits available.
-
If you haven’t already created an IoT Hub in 6P3:
- Search for “IoT Hub” in the Azure Portal
- Click Create
- Use the Free F1 tier (1 per subscription, 8,000 messages/day)
- Choose a region close to you (East US, *NOT Canada East!)
- Note your IoT Hub name (e.g.,
w26-telemetry-hub)
-
Create a device identity for YOUR device:
- Navigate to your IoT Hub → Devices → Add Device
- Use descriptive device IDs (see naming convention below)
- Device ID examples:
subsystem1,subsystem2,subsystem3 - Save your device’s Primary Connection String (you’ll need this for Part 2)
-
Navigate to Azure Portal and create a Web PubSub service:
- Search for “Web PubSub” and click Create
- Resource group: Same as your IoT Hub
- Name:
w26-telemetry-hub - Region: East US
- Pricing tier: Free (make sure you manually change to this!)
- After creation, go to Keys and copy the Connection string
-
You’ll need three types of connection strings:
Event Hub-compatible endpoint (for reading D2C telemetry):
- IoT Hub → Built-in endpoints → Event Hub-compatible endpoint
- Copy the full connection string
Service connection string (for sending C2D commands):
- IoT Hub → Shared access policies → service policy
- Copy the Primary connection string
Web PubSub connection string (for real-time broadcasting):
- Web PubSub resource → Keys → Connection string (primary)
-
In the
.envfiles from the starter code:device-simulator-YOURNAME/.env→ Your device connection stringazure-function/.env→ follow the instructions in the comments
Deeper Dive: Azure Cost Estimates
With the Free F1 tier IoT Hub (8,000 messages/day) and Flex Consumption Azure Functions plan:
- Device sending data every 30 seconds = 2,880 messages/day per device
- You can support ~2-3 simulators within the free tier
- Azure Functions has 1 million free executions/month
- Estimated cost for this milestone: $0 (within free tiers)
If you exceed limits, costs are minimal (~$0.25/day for F1 overflow). Monitor usage in Azure Portal.
🎮 Part 2: Device Simulator
Section titled “🎮 Part 2: Device Simulator”You’ll customize the provided example simulator to match YOUR 6P3 device.
Overview
Section titled “Overview”The device-simulator-example demonstrates a complete working simulator with:
- Temperature/humidity sensor (readings)
- Fan control (actuator)
- LED control (actuator)
- Motion sensor (event-based)
- Door sensor (event-based)
- Keyboard controls (‘m’ for motion, ‘d’ for door, ‘f’ for fan toggle, etc.)
Your task is to adapt this pattern to YOUR 6P3 device.
Requirements
Section titled “Requirements”Telemetry (Device-to-Cloud)
Section titled “Telemetry (Device-to-Cloud)”- Customize for YOUR device’s sensors (based on 6P3 hardware)
- Use MQTT protocol (already configured in example)
- Send telemetry at regular intervals (default: 30 seconds)
- Keyboard controls required: Adapt example triggers for YOUR device
- Include timestamp in each message
- State changes trigger immediate telemetry for instant app feedback
Commands (Cloud-to-Device)
Section titled “Commands (Cloud-to-Device)”- Implement at least one command handler for YOUR device’s actuators
- Commands should affect telemetry data (e.g., motor speed command → speed reading changes)
- Send immediate telemetry after executing command
- Return success/failure status to the caller
Keyboard Controls
Section titled “Keyboard Controls”- Implement at least one keyboard trigger for YOUR device
- Examples to adapt:
- Motor controller: Press
+/-to increase/decrease speed - LED: Press
r/g/bfor colors - Sensor: Press
sto force a reading
- Motor controller: Press
- Keyboard events send immediate telemetry for testing
- Display help menu with YOUR keyboard mappings
Implementation Steps
Section titled “Implementation Steps”-
Copy the example:
Terminal window cd iot-hub-startercp -r device-simulator-example device-simulator-YOURNAMEcd device-simulator-YOURNAMEReplace
YOURNAMEwith your name or device type (e.g.,device-simulator-alice). -
Before customizing, understand what’s provided:
- Connection management: MQTT connection to IoT Hub with automatic reconnection
- Telemetry generation:
generateTelemetry()function with mock sensor data - Command handlers:
onDeviceMethod()for C2D commands - Keyboard controls: Interactive testing with
readline - Graceful shutdown: Clean disconnect on SIGINT/SIGTERM
-
Copy
.env.exampleto.env:Terminal window cp .env.example .envEdit
.envwith YOUR device connection string from Part 1:device-simulator-YOURNAME/.env DEVICE_CONNECTION_STRING=HostName=YOUR-HUB.azure-devices.net;DeviceId=YOUR-DEVICE;SharedAccessKey=YOUR-KEYTELEMETRY_INTERVAL=30000 -
In
simulator.ts, find the state variables section (~line 45):device-simulator-YOURNAME/simulator.ts // TODO: CUSTOMIZE - Device State Variables// Example for motor controller:let motorSpeed: number = 0; // 0-100 RPMlet motorDirection: 'forward' | 'reverse' | 'stopped' = 'stopped';let temperature: number = 25.0; // Motor temperatureReplace with YOUR device’s sensors/actuators based on your 6P3 hardware.
-
Update
generateTelemetry()function (~line 80):device-simulator-YOURNAME/simulator.ts function generateTelemetry() {return {deviceId: process.env.DEVICE_ID || 'unknown',// YOUR device fields heremotorSpeed,motorDirection,temperature: parseFloat((temperature + (Math.random() - 0.5) * 2).toFixed(1)),timestamp: new Date().toISOString(),};}Match the structure to your physical device capabilities.
-
Replace fan/LED commands with YOUR actuators (~line 190):
device-simulator-YOURNAME/simulator.ts // Example: Motor speed controlclient.onDeviceMethod('SetMotorSpeed', async (request, response) => {const { speed } = request.payload || {};console.log(`🎛️ Command: SetMotorSpeed(${speed})`);if (!speed || speed < 0 || speed > 100) {response.send(400, { error: 'Speed must be 0-100' });return;}motorSpeed = speed;response.send(200, {message: `Motor speed set to ${speed}`,motorSpeed,});// Send immediate telemetry for instant app feedbackawait sendTelemetry(`command: SetMotorSpeed(${speed})`, false);});Implement handlers for all YOUR device’s actuators.
-
Update keyboard mappings (~line 355):
device-simulator-YOURNAME/simulator.ts switch (key.name) {case '+':motorSpeed = Math.min(100, motorSpeed + 10);console.log(`\n⬆️ Motor speed increased: ${motorSpeed}`);await sendTelemetry('keyboard: speed increase', false);break;case '-':motorSpeed = Math.max(0, motorSpeed - 10);console.log(`\n⬇️ Motor speed decreased: ${motorSpeed}`);await sendTelemetry('keyboard: speed decrease', false);break;case 'h':console.log('\n🎮 Keyboard Controls:');console.log(' [+] - Increase motor speed');console.log(' [-] - Decrease motor speed');console.log(' [h] - Show help');console.log(' [q] - Quit');break;}Make controls intuitive for YOUR device type.
-
Install Dependencies
Terminal window bun install -
Test Your Simulator
Terminal window bun startVerify it’s working:
- Simulator connects successfully (see ”✅ Connected” message)
- Telemetry sends every 30 seconds (see ”📤 Sent” messages)
- Keyboard controls respond (press ‘h’ for help)
Verify data reaches IoT Hub (optional but recommended):
- Azure Portal → Your IoT Hub → Metrics → Check “Telemetry messages sent” count increases
- OR run Azure CLI:
az iot hub monitor-events --hub-name YOUR-HUB-NAME(see Monitoring section)
-
Update
README.mdin your simulator folder:- What device you’re simulating
- What sensors/actuators you’ve implemented
- Keyboard control mapping
- Command list with examples
For complete implementation details, see the Device Simulator README in the appropriate subfolder.
Customization Checklist
Section titled “Customization Checklist”- Copied
device-simulator-exampletodevice-simulator-YOURNAME - Updated
.envwith YOUR device connection string - Customized state variables for YOUR sensors/actuators
- Updated
generateTelemetry()to match YOUR device structure - Implemented command handlers for YOUR actuators
- Customized keyboard controls for YOUR device
- Updated help menu with YOUR keyboard mappings
- Tested connection to IoT Hub
- Verified telemetry in Azure Portal metrics
- Documented customizations in README
⚡ Part 3: REST API with Azure Functions
Section titled “⚡ Part 3: REST API with Azure Functions”Build a middleware layer using Azure Functions that exposes a simple REST API for your Expo app. This approach is simpler and more secure than connecting Expo directly to IoT Hub.
Required Endpoints
Section titled “Required Endpoints”GET /api/telemetry (HTTP fallback)
Section titled “GET /api/telemetry (HTTP fallback)”- Returns current telemetry and recent history for all devices
- Reads from in-memory store (populated by Event Hub Listener)
- Used for initial data load and manual refresh
POST /api/negotiate (WebSocket connection)
Section titled “POST /api/negotiate (WebSocket connection)”- Returns Web PubSub connection URL with JWT token
- Used by Expo app to establish WebSocket connection
- Hub name must match Event Hub Listener configuration
POST /api/devices/{deviceId}/command
Section titled “POST /api/devices/{deviceId}/command”- Sends cloud-to-device command to specified device
- Uses IoT Hub Service Client to invoke direct methods
- Returns command result (success/failure)
Event Hub Listener (background process)
Section titled “Event Hub Listener (background process)”- Subscribes to IoT Hub Event Hub-compatible endpoint
- Stores telemetry in memory (last ~20 readings)
- Broadcasts to Web PubSub for real-time WebSocket push to all connected clients
Implementation Steps
Section titled “Implementation Steps”-
Navigate to the azure-function folder and install dependencies:
Terminal window cd azure-functionbun install -
Copy
local.settings.json.exampletolocal.settings.json:Terminal window cp local.settings.json.example local.settings.jsonEdit
local.settings.jsonwith YOUR connection strings from Part 1:{"IsEncrypted": false,"Values": {"AzureWebJobsStorage": "UseDevelopmentStorage=true","FUNCTIONS_WORKER_RUNTIME": "node","EVENT_HUB_CONNECTION_STRING": "Endpoint=sb://...","EVENT_HUB_CONSUMER_GROUP": "$Default","IOT_HUB_SERVICE_CONNECTION_STRING": "HostName=...","WEB_PUBSUB_CONNECTION_STRING": "Endpoint=https://..."}} -
Follow the instructions in
azure-function/src/types.tsto define your interface. Coordinate with teammates while editing to avoid conflicts. -
Deploy to Azure:
- Install the Azure Functions VS Code extension (if not already installed)
- Right-click on the
azure-functionfolder in VS Code - Select Deploy to Function App
- Choose Create new Function App (or select existing)
- Choose Flex Consumption plan (free tier)
- Wait for deployment to complete
If you’re still not able to get it to work, try using the Azure CLI:
Terminal window az loginaz storage account create \--name <pick-a-storage-account-name> \--resource-group <your-resource-group-name> \--location eastus \--sku Standard_LRSaz functionapp create \--name <pick-a-function-app-name> \--resource-group <your-resource-group-name> \--consumption-plan-location eastus \--runtime node \--runtime-version 22 \--functions-version 4 \--storage-account <same-storage-account-name-from-above>cd azure-function/bunx func azure functionapp publish <same-function-app-name-from-above> -
In Azure Portal → Your Function App → Environment variables:
- Add
EVENT_HUB_CONNECTION_STRING(from Part 1) - Add
EVENT_HUB_CONSUMER_GROUP(value:$Default) - Add
IOT_HUB_SERVICE_CONNECTION_STRING(from Part 1) - Add
WEB_PUBSUB_CONNECTION_STRING(from Part 1)
- Add
-
Copy your Function App URL (e.g.,
https://your-app.azurewebsites.net) - you’ll need this for Part 4.
For M4, you only need YOUR simulator running. But if you want to test multi-device:
# Terminal 1: Student 1's simulatorcd device-simulator-alice && bun start
# Terminal 2: Student 2's simulatorcd device-simulator-bob && bun start
# Terminal 3: Student 3's simulatorcd device-simulator-charlie && bun startThe Event Hub Listener automatically handles multiple devices. The WebSocket broadcasts all telemetry, and the Expo app filters by device ID.
Deeper Dive: Why In-Memory Storage?
For this milestone, storing telemetry in memory is sufficient:
- Pros: Simple, fast, no database costs
- Cons: Data lost on cold starts (Azure Functions recycle after inactivity)
For production apps, consider:
- Azure Cosmos DB (NoSQL)
- Azure Table Storage (cheap)
- Azure SQL Database
- Time Series Insights
📱 Part 4: Expo App - Your Device Tab
Section titled “📱 Part 4: Expo App - Your Device Tab”Overview
Section titled “Overview”The Expo app uses tabs, one per team member. You’ll build YOUR device’s UI in your assigned tab while your teammates build theirs independently.
The starter template provides:
- Complete authentication (login, protected routes, roles)
- Tab navigation structure (
app/(tabs)/) - WebSocket integration for real-time updates
- Example tab (
device1.tsx) with comprehensive instructions
Requirements
Section titled “Requirements”Your Responsibilities
Section titled “Your Responsibilities”- Build UI in YOUR assigned tab (
device1.tsx,device2.tsx, ordevice3.tsx) - Define YOUR device’s TypeScript interfaces
- Filter telemetry for YOUR device ID
- Display YOUR device’s sensor readings
- Implement command controls for YOUR actuators (if applicable)
- Test YOUR tab independently
Team Responsibilities
Section titled “Team Responsibilities”- Authentication system (already complete)
- WebSocket connection (already setup)
- API client (already configured)
- Type coordination in shared files
4.1 Setup Your Device Tab
Section titled “4.1 Setup Your Device Tab”-
Identify if you’re tab 1, 2, or 3.
-
Edit
config/devices.ts:config/devices.ts // Each student configures their device ID hereexport const MY_DEVICE_ID = 'temp-sensor-alice'; // YOUR device ID from Azure -
Update .env file:
app/.env EXPO_PUBLIC_API_URL=https://YOUR-FUNCTION-APP.azurewebsites.net/apiEXPO_PUBLIC_DEVICE_ID=temp-sensor-aliceRestart Expo after changing .env! (
bun run start --clear)
4.2 Test WebSocket Data FIRST
Section titled “4.2 Test WebSocket Data FIRST”-
Run your simulator:
Terminal window cd device-simulator-YOURNAMEbun start -
Run Expo app:
Terminal window cd appbun install # First time onlybun run startPress
ifor iOS orafor Android. -
Log in and check console:
- Username:
owner - Password:
owner123
Navigate to YOUR tab. Look at the console output in VS Code terminal:
📡 Raw telemetry received: {"deviceId": "temp-sensor-alice","timestamp": "2026-05-03T14:30:00.000Z","temperature": 22.4,"humidity": 65.2,"fanStatus": "off"}✅ Data for MY device: {...}Copy this JSON structure, you’ll use it in the next step!
- Username:
-
Press a key in your simulator (e.g., ‘f’ to toggle fan). You should see:
- Instant console output in the Expo terminal (< 1 second)
- WebSocket badge shows ”🔴 Live” in the app
If you don’t see updates, troubleshoot WebSocket connection before continuing.
4.3 Define Your Types
Section titled “4.3 Define Your Types”Based on the console output, define your interface in types/telemetry.ts:
// Base interface (already provided)export interface BaseTelemetry { deviceId: string; timestamp: string;}
// Student 1: Temperature Sensor - ADD THISexport interface TempSensorTelemetry extends BaseTelemetry { deviceId: 'temp-sensor-alice'; // Literal type for discrimination temperature: number; humidity: number; fanStatus?: 'on' | 'off';}
// Add to union type at the bottom:export type TelemetryReading = | TempSensorTelemetry | MotorTelemetry | LEDTelemetry;TypeScript can now discriminate: if (data.deviceId === 'temp-sensor-alice') narrows type to TempSensorTelemetry. Each student adds ONE interface. Announce in team chat before editing!
4.4 Build Your UI
Section titled “4.4 Build Your UI”In YOUR device tab file (device1.tsx, device2.tsx, or device3.tsx):
-
Look for the TODO around line 195:
app/(tabs)/device1.tsx (or device2.tsx or device3.tsx) /* TODO: Replace this JSON dump with actual UI components */<Text>{JSON.stringify(data, null, 2)}</Text>; -
Replace with sensor readings:
app/(tabs)/device1.tsx (or device2.tsx or device3.tsx) /* Current sensor readings */<View style={styles.section}><Text style={styles.sectionTitle}>🌡️ Current Readings</Text><View style={styles.card}><Text style={styles.label}>Temperature</Text><Text style={styles.value}>{data.temperature?.toFixed(1)}°C</Text></View><View style={styles.card}><Text style={styles.label}>Humidity</Text><Text style={styles.value}>{data.humidity?.toFixed(1)}%</Text></View></View>; -
Add actuator status (if applicable):
app/(tabs)/device1.tsx (or device2.tsx or device3.tsx) {data.fanStatus && (<View style={styles.section}><Text style={styles.sectionTitle}>🌀 Fan Status</Text><Textstyle={[styles.status,data.fanStatus === 'on' && styles.statusOn,]}>{data.fanStatus.toUpperCase()}</Text></View>);} -
Add command controls (for owners only):
app/(tabs)/device1.tsx (or device2.tsx or device3.tsx) {user?.role === 'owner' && (<View style={styles.section}><Text style={styles.sectionTitle}>🎮 Controls</Text><View style={styles.buttonRow}><Pressablestyle={[styles.button,isExecutingCommand && styles.buttonDisabled,]}onPress={() => handleCommand('TurnFanOn', {})}disabled={isExecutingCommand}><Text style={styles.buttonText}>Turn Fan On</Text></Pressable><Pressablestyle={[styles.button,isExecutingCommand && styles.buttonDisabled,]}onPress={() => handleCommand('TurnFanOff', {})}disabled={isExecutingCommand}><Text style={styles.buttonText}>Turn Fan Off</Text></Pressable></View></View>);}Define your commands in
types/commands.tsfollowing the examples. -
Implement
handleCommandfunction. It’s already provided in the template, but customize for YOUR commands:app/(tabs)/device1.tsx (or device2.tsx or device3.tsx) async function handleCommand(method: string, payload: any) {setIsExecutingCommand(true);try {await sendCommand(MY_DEVICE_ID, method, payload);// Success - NO alert popup! WebSocket will update UI} catch (error) {// Only show alert on ERRORAlert.alert('Command Failed', error.message);} finally {setIsExecutingCommand(false);}}
4.5 Optional: Add Charts
Section titled “4.5 Optional: Add Charts”Skip this if your device doesn’t need historical visualization (e.g., simple on/off switches). You can use charts for temperature over time, motor speed history, sensor readings, etc.
Install the library:
bun add react-native-chart-kit react-native-svgAdd to your tab:
import { LineChart } from 'react-native-chart-kit';import { Dimensions } from 'react-native';
const screenWidth = Dimensions.get('window').width;
// In your component:{ history.length > 0 && ( <View style={styles.section}> <Text style={styles.sectionTitle}>📈 Temperature History</Text> <LineChart data={{ labels: history.slice(-10).map((r) => new Date(r.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', }) ), datasets: [ { data: history .slice(-10) .map((r) => r.temperature || 0), color: (opacity = 1) => `rgba(59, 130, 246, ${opacity})`, // Blue for device 1 strokeWidth: 2, }, ], }} width={screenWidth - 32} height={220} chartConfig={{ backgroundColor: '#ffffff', backgroundGradientFrom: '#ffffff', backgroundGradientTo: '#ffffff', decimalPlaces: 1, color: (opacity = 1) => `rgba(59, 130, 246, ${opacity})`, labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`, style: { borderRadius: 16, }, }} bezier style={styles.chart} /> </View> );}🛠️ Common Issues & Solutions
Section titled “🛠️ Common Issues & Solutions”Deeper Dive: Troubleshooting Tips
“Module not found: azure-iot-device”
- Run
bun installin YOUR device-simulator directory - Verify
package.jsonincludes the dependency
“Connection refused” when testing Azure Function locally
- Ensure
bun startis running in azure-function directory - Check port 7071 is not in use
- Verify
local.settings.jsonhas correct connection strings
“Failed to fetch telemetry” in Expo app
- Check
EXPO_PUBLIC_API_URLin your.envfile matches YOUR Azure Function URL - Verify YOUR Azure Function is deployed and accessible (test with curl)
- Check CORS headers in Azure Function responses
- Restart Expo dev server after changing
.envfile:bun run start --clear
WebSocket shows “⚠️ Manual Refresh” instead of ”🔴 Live”
- Verify Web PubSub service is created in YOUR Azure Portal
- Check
WEB_PUBSUB_CONNECTION_STRINGis set in YOUR Azure Function app settings - Test negotiate endpoint:
curl https://YOUR-function.azurewebsites.net/api/negotiate - Check Expo console for WebSocket connection errors
- Verify hub name is “telemetry” in both
negotiate.tsandeventHubListener.ts
Telemetry not updating in real-time
- Verify WebSocket connection is established (check console logs)
- Check Event Hub Listener is running and broadcasting to Web PubSub
- Verify device simulator is running and sending data
- Check Azure Portal → IoT Hub → Metrics for message count
- Check Azure Function logs for broadcasting errors
- Test with keyboard press in simulator (should see instant update)
- Fall back to manual pull-to-refresh if needed
Commands not working
- Verify
IOT_HUB_SERVICE_CONNECTION_STRINGis set in Azure Function app settings - Check simulator has command handlers registered with
onDeviceMethod() - Check Azure Function logs for error details
- Verify device ID in API call matches simulator device ID
“WebSocket connection failed” or “Protocol error”
- Do NOT use @microsoft/signalr library - Azure Web PubSub uses native WebSocket protocol
- Use native WebSocket API:
new WebSocket(url) - Message format:
{type: 'message', dataType: 'json', data: {...}} - Check negotiate endpoint returns correct URL with JWT token
AsyncStorage errors in Expo
- Verify version is
2.2.0for SDK 54 compatibility - Run
bun installafter changing version - Clear Expo cache:
bunx expo start --clear
“Property ‘status’ does not exist” TypeScript error in sendCommand
- Azure SDK returns nested result: use
result.result?.status || result.status - See azure-function README for correct parsing
Keyboard controls not working in simulator
- Ensure
process.stdin.setRawMode(true)is called - Check
readline.emitKeypressEvents(process.stdin)is set up - Run simulator directly in terminal (not through VS Code debugger)
- Test with simple key like ‘h’ for help menu
“My tab doesn’t show any data”
- Check console logs - is telemetry being received?
- Verify YOUR device ID matches in:
- Simulator
.env(DEVICE_CONNECTION_STRING) config/devices.ts(MY_DEVICE_ID)- Azure IoT Hub device identity
- Simulator
- Check filtering logic in your tab:
if (data.deviceId === MY_DEVICE_ID) - Run YOUR simulator and verify it’s sending telemetry
“I see other students’ data in my tab”
- Check your filtering logic - should filter by YOUR device ID only
- Verify you’re using
MY_DEVICE_IDfromconfig/devices.ts - TypeScript discriminated unions help:
if (data.deviceId === 'temp-sensor-alice')
“Git conflict in types/telemetry.ts”
- Communicate with your teammate
- Combine both interfaces in the union type
- Test both tabs after resolving
- See “Merge Conflicts - How to Resolve” section above
“Simulator connects but commands don’t work”
- Verify command handler is registered:
client.onDeviceMethod('YourCommand', ...) - Check command method name matches exactly (case-sensitive)
- Check device ID in command API call matches YOUR simulator
- Look at simulator console for command reception logs
“Azure CLI monitor-events not working”
- Install Azure CLI:
brew install azure-cli(macOS) or download for Windows - Login:
az login(opens browser) - Install IoT extension:
az extension add --name azure-iot - Use correct hub name:
az iot hub monitor-events --hub-name YOUR-HUB-NAME - If you see “No messages received”, verify simulator is running and sending telemetry
- Check IoT Hub Metrics in Portal to confirm messages are arriving
📊 Grading Criteria
Section titled “📊 Grading Criteria”You have the option of demoing either in-person during our May 14 class, or via pre-recorded video. In either case, all members must be present for their demo. This means all members are present in class, or all members speak through their part in the shared recording.
If you decide to record a video, then you must adhere to this process:
- All team members hop on a Teams call together
- Select More actions > Record and transcribe > Start recording
- Take turns sharing your screens and going through the rubric below to show me how each criteria is met
- Keep it to 5 minutes MAX per person
- Select More actions > Record and transcribe > Stop recording
- After a few moments, the recording should show up in your group chat
- Hit the share icon and share it with Vik
Walkthrough
Section titled “Walkthrough”Sign into your Microsoft account to see me walk through, in real time, what I’m expecting from you. Ensure the quality is set to max if the video is blurry.
If the above video doesn’t work, try accessing it here.
Azure Setup (2 points)
Section titled “Azure Setup (2 points)”| Criteria | Points |
|---|---|
| IoT Hub device identity configured with descriptive name | 0.5 |
| Azure Function deployed and accessible | 0.5 |
| Azure Web PubSub connection string configured | 0.5 |
| Demo shows IoT Hub metrics or Azure CLI monitoring | 0.5 |
Method 1: Azure Portal Metrics (easiest, required)
Section titled “Method 1: Azure Portal Metrics (easiest, required)”- Go to Azure Portal → Your IoT Hub
- Click the Device to cloud messages chart that should be pinned under “Usage”
- Click Local Time: on the top right
- Set Time range to “Last 30 minutes”
- Set Time granularity to “1 minute”
- Click “Apply”
- You should see the graph updating in real time every minute
Method 2: Azure CLI (ideal, but not required)
Section titled “Method 2: Azure CLI (ideal, but not required)”Install Azure CLI with brew install azure-cli (macOS) or download for Windows.
# Loginaz login
# Install IoT extensionaz extension add --name azure-iot
# Show events from your IoT Hubaz iot hub monitor-events --hub-name YOUR-HUB-NAME --output tableDevice Simulator (4 points)
Section titled “Device Simulator (4 points)”| Criteria | Points |
|---|---|
| Simulator customized for your 6P3 device | 1.0 |
| Telemetry structure matches your defined TypeScript interface | 1.0 |
| Keyboard controls work and are relevant to your device type | 1.0 |
| Commands work correctly for your actuators (if applicable) | 1.0 |
TypeScript Types (1 points)
Section titled “TypeScript Types (1 points)”| Criteria | Points |
|---|---|
Interface defined in types/telemetry.ts with correct structure | 1.0 |
Expo App Tab (7 points)
Section titled “Expo App Tab (7 points)”| Criteria | Points |
|---|---|
| Tab displays your sensor data with clear labels | 1.0 |
| Real-time WebSocket updates work | 2.0 |
| UI is clean, intuitive, and appropriate for your device | 1.0 |
| Commands work with instant visual feedback (if applicable) | 1.0 |
| At least 2 types of users (ex. admin and non-admin) that have distinct authorization | 2.0 |