IoT (Internet of Things) apps connect smartphones to smart devices. This guide covers protocols, implementation patterns, and best practices for building IoT companion apps.
IoT App Architecture
Architecture Overview:
┌─────────────────────────────────────────────────────────────┐
│ Mobile App │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ UI │ │ Business │ │ Device │ │ Cloud │ │
│ │ Layer │ │ Logic │ │ Manager │ │ Sync │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Bluetooth │ │ WiFi │ │ Cloud │
│ BLE / Classic │ │ Local Network │ │ Platform │
└─────────────────┘ └─────────────────┘ └──────────────┘
│ │ │
▼ ▼ │
┌─────────────────────────────────────────┐ │
│ IoT Device │◄──────────┘
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Sensors │ │ Actuators│ │ MCU │ │
│ └──────────┘ └──────────┘ └────────┘ │
└─────────────────────────────────────────┘
Communication Protocols
1. Bluetooth Low Energy (BLE)
Most common for wearables and small devices.
// iOS Core Bluetooth Implementation
import CoreBluetooth
class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
private var centralManager: CBCentralManager!
private var connectedPeripheral: CBPeripheral?
// UUIDs for your device
let serviceUUID = CBUUID(string: "FFE0")
let characteristicUUID = CBUUID(string: "FFE1")
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
// Central Manager State
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
startScanning()
case .poweredOff:
print("Bluetooth is off")
case .unauthorized:
print("Bluetooth not authorized")
default:
break
}
}
// Start scanning for devices
func startScanning() {
centralManager.scanForPeripherals(
withServices: [serviceUUID],
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
)
}
// Device discovered
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],
rssi RSSI: NSNumber) {
print("Found device: \(peripheral.name ?? "Unknown")")
connectedPeripheral = peripheral
centralManager.connect(peripheral, options: nil)
}
// Connected to device
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
}
// Services discovered
func peripheral(_ peripheral: CBPeripheral,
didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
peripheral.discoverCharacteristics([characteristicUUID], for: service)
}
}
// Characteristics discovered
func peripheral(_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
// Send data to device
func sendData(_ data: Data) {
guard let peripheral = connectedPeripheral,
let service = peripheral.services?.first,
let characteristic = service.characteristics?.first else { return }
peripheral.writeValue(data, for: characteristic, type: .withResponse)
}
// Receive data from device
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
guard let data = characteristic.value else { return }
// Process received data
print("Received: \(data.hexEncodedString())")
}
}
// Android BLE Implementation
import android.bluetooth.*
import android.bluetooth.le.*
class BLEManager(private val context: Context) {
private val bluetoothAdapter: BluetoothAdapter? =
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
private var bluetoothGatt: BluetoothGatt? = null
companion object {
val SERVICE_UUID: UUID = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb")
val CHARACTERISTIC_UUID: UUID = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")
}
// Scan for devices
fun startScan() {
val scanner = bluetoothAdapter?.bluetoothLeScanner
val filters = listOf(
ScanFilter.Builder()
.setServiceUuid(ParcelUuid(SERVICE_UUID))
.build()
)
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
scanner?.startScan(filters, settings, scanCallback)
}
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device
Log.d("BLE", "Found: ${device.name ?: "Unknown"}")
connectToDevice(device)
}
}
// Connect to device
fun connectToDevice(device: BluetoothDevice) {
bluetoothGatt = device.connectGatt(context, false, gattCallback)
}
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
val service = gatt.getService(SERVICE_UUID)
val characteristic = service?.getCharacteristic(CHARACTERISTIC_UUID)
// Enable notifications
characteristic?.let {
gatt.setCharacteristicNotification(it, true)
val descriptor = it.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
val data = characteristic.value
// Process received data
Log.d("BLE", "Received: ${data.toHexString()}")
}
}
// Send data
fun sendData(data: ByteArray) {
val service = bluetoothGatt?.getService(SERVICE_UUID)
val characteristic = service?.getCharacteristic(CHARACTERISTIC_UUID)
characteristic?.value = data
bluetoothGatt?.writeCharacteristic(characteristic)
}
}
2. WiFi Direct Communication
For high-bandwidth local communication.
// React Native with local network discovery
import { NetworkInfo } from 'react-native-network-info';
import dgram from 'react-native-udp';
class LocalDeviceDiscovery {
constructor() {
this.socket = dgram.createSocket('udp4');
this.discoveredDevices = [];
}
// mDNS-style discovery
async discoverDevices() {
const localIP = await NetworkInfo.getIPV4Address();
const subnet = localIP.substring(0, localIP.lastIndexOf('.'));
// Broadcast discovery message
this.socket.bind(5353);
this.socket.on('message', (msg, rinfo) => {
const device = JSON.parse(msg.toString());
this.discoveredDevices.push({
...device,
ip: rinfo.address
});
});
// Send discovery broadcast
const discoveryMessage = Buffer.from(JSON.stringify({
type: 'DISCOVER',
appId: 'com.myapp.iot'
}));
this.socket.send(
discoveryMessage,
0,
discoveryMessage.length,
5353,
`${subnet}.255`
);
}
// Connect to discovered device
async connectToDevice(deviceIP, port = 8080) {
return new Promise((resolve, reject) => {
const client = net.createConnection({ host: deviceIP, port }, () => {
resolve(client);
});
client.on('error', reject);
});
}
}
3. MQTT Protocol
Standard for IoT cloud communication.
// MQTT Client Implementation
import mqtt from 'mqtt';
class MQTTClient {
constructor(config) {
this.config = config;
this.client = null;
this.subscriptions = new Map();
}
connect() {
return new Promise((resolve, reject) => {
this.client = mqtt.connect(this.config.brokerUrl, {
clientId: `mobile_${Date.now()}`,
username: this.config.username,
password: this.config.password,
clean: true,
reconnectPeriod: 5000
});
this.client.on('connect', () => {
console.log('MQTT Connected');
resolve();
});
this.client.on('error', reject);
this.client.on('message', (topic, message) => {
const handler = this.subscriptions.get(topic);
if (handler) {
handler(JSON.parse(message.toString()));
}
});
});
}
subscribe(topic, handler) {
this.client.subscribe(topic, { qos: 1 });
this.subscriptions.set(topic, handler);
}
publish(topic, data, options = {}) {
this.client.publish(
topic,
JSON.stringify(data),
{ qos: options.qos || 1, retain: options.retain || false }
);
}
disconnect() {
this.client?.end();
}
}
// Usage
const mqttClient = new MQTTClient({
brokerUrl: 'mqtt://broker.example.com',
username: 'user',
password: 'password'
});
await mqttClient.connect();
// Subscribe to device data
mqttClient.subscribe('devices/thermostat/temperature', (data) => {
console.log(`Temperature: ${data.value}°C`);
});
// Send command to device
mqttClient.publish('devices/thermostat/commands', {
action: 'setTemperature',
value: 22
});
4. HTTP REST API
For cloud-connected devices.
// Device Cloud API Client
class DeviceAPIClient {
constructor(baseUrl, apiKey) {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
}
async request(method, endpoint, data = null) {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: data ? JSON.stringify(data) : null
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
// Get all devices
async getDevices() {
return this.request('GET', '/devices');
}
// Get device status
async getDeviceStatus(deviceId) {
return this.request('GET', `/devices/${deviceId}/status`);
}
// Send command
async sendCommand(deviceId, command) {
return this.request('POST', `/devices/${deviceId}/commands`, command);
}
// Get historical data
async getDeviceHistory(deviceId, startTime, endTime) {
const params = new URLSearchParams({
start: startTime.toISOString(),
end: endTime.toISOString()
});
return this.request('GET', `/devices/${deviceId}/history?${params}`);
}
}
Smart Home Integration
HomeKit (iOS)
import HomeKit
class HomeKitManager: NSObject, HMHomeManagerDelegate {
private let homeManager = HMHomeManager()
private var home: HMHome?
override init() {
super.init()
homeManager.delegate = self
}
func homeManagerDidUpdateHomes(_ manager: HMHomeManager) {
home = manager.primaryHome
}
// Get all accessories
func getAccessories() -> [HMAccessory] {
return home?.accessories ?? []
}
// Control a light
func setLightState(accessory: HMAccessory, on: Bool) {
let lightService = accessory.services.first { $0.serviceType == HMServiceTypeLightbulb }
let powerChar = lightService?.characteristics.first {
$0.characteristicType == HMCharacteristicTypePowerState
}
powerChar?.writeValue(on) { error in
if let error = error {
print("Error: \(error)")
}
}
}
// Control thermostat
func setThermostat(accessory: HMAccessory, temperature: Double) {
let thermostatService = accessory.services.first {
$0.serviceType == HMServiceTypeThermostat
}
let targetTemp = thermostatService?.characteristics.first {
$0.characteristicType == HMCharacteristicTypeTargetTemperature
}
targetTemp?.writeValue(temperature) { error in
if let error = error {
print("Error: \(error)")
}
}
}
// Create automation
func createAutomation(name: String, trigger: HMTrigger, action: HMAction) {
home?.addTrigger(trigger) { trigger, error in
if let error = error {
print("Error creating trigger: \(error)")
return
}
// Add action to trigger
trigger?.addActionSet(HMActionSet()) { error in
// Handle completion
}
}
}
}
Google Home / Matter
// Matter-compatible device control
import { DeviceController } from '@matter/node';
class MatterDeviceManager {
constructor() {
this.controller = new DeviceController();
this.devices = new Map();
}
async discoverDevices() {
const discoveredDevices = await this.controller.discoverDevices();
for (const device of discoveredDevices) {
await this.pairDevice(device);
}
}
async pairDevice(device) {
const pairedDevice = await this.controller.pairDevice(device.id, {
passcode: device.setupCode
});
this.devices.set(device.id, pairedDevice);
}
async controlLight(deviceId, state) {
const device = this.devices.get(deviceId);
if (!device) throw new Error('Device not found');
await device.clusters.onOff.toggle(state);
}
async setLightLevel(deviceId, level) {
const device = this.devices.get(deviceId);
await device.clusters.levelControl.moveToLevel({
level: Math.round(level * 254),
transitionTime: 10
});
}
async getLightState(deviceId) {
const device = this.devices.get(deviceId);
const onOffState = await device.clusters.onOff.getOnOffState();
const level = await device.clusters.levelControl.getCurrentLevel();
return {
on: onOffState,
brightness: level / 254
};
}
}
Device Provisioning (Setup Flow)
// Device Setup/Provisioning Flow
class DeviceProvisioner {
constructor() {
this.currentStep = 0;
this.steps = [
'detect',
'connect',
'configure_wifi',
'register',
'complete'
];
}
// Step 1: Detect device in setup mode
async detectDevice() {
// Device typically broadcasts on BLE or creates WiFi AP
const devices = await BLEManager.scanForSetupDevices();
return devices.filter(d => d.isInSetupMode);
}
// Step 2: Connect to device
async connectToDevice(device) {
if (device.setupType === 'ble') {
await BLEManager.connect(device);
} else if (device.setupType === 'wifi-ap') {
// Device creates its own WiFi network
await WiFiManager.connectToNetwork(device.apSSID, device.apPassword);
}
}
// Step 3: Configure device WiFi
async configureDeviceWiFi(ssid, password) {
const config = {
ssid,
password,
security: 'WPA2'
};
// Send WiFi credentials to device
await this.sendToDevice({
command: 'configure_wifi',
data: config
});
// Wait for device to connect
await this.waitForDeviceOnline();
}
// Step 4: Register device with cloud
async registerDevice(deviceInfo) {
const response = await fetch(`${API_URL}/devices/register`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
deviceId: deviceInfo.id,
deviceType: deviceInfo.type,
name: deviceInfo.name,
location: deviceInfo.room
})
});
return response.json();
}
// Step 5: Complete setup
async completeSetup(device) {
// Send completion command to device
await this.sendToDevice({
command: 'setup_complete',
data: {
cloudEndpoint: API_URL,
authToken: device.cloudToken
}
});
// Disconnect from setup mode
await this.disconnect();
}
async sendToDevice(message) {
// Implementation depends on connection type
const data = JSON.stringify(message);
await BLEManager.sendData(data);
}
async waitForDeviceOnline(timeout = 60000) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
try {
const status = await this.checkDeviceStatus();
if (status.online) return true;
} catch (e) {
// Device not ready yet
}
await sleep(2000);
}
throw new Error('Device setup timeout');
}
}
Real-Time Data Visualization
// Real-time sensor data display
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, Tooltip } from 'recharts';
function SensorDashboard({ deviceId }) {
const [data, setData] = useState([]);
const [currentValue, setCurrentValue] = useState(null);
useEffect(() => {
// Subscribe to real-time updates
const subscription = mqttClient.subscribe(
`devices/${deviceId}/telemetry`,
(message) => {
const reading = {
time: new Date().toLocaleTimeString(),
value: message.temperature
};
setCurrentValue(message.temperature);
setData(prev => [...prev.slice(-29), reading]);
}
);
return () => subscription.unsubscribe();
}, [deviceId]);
return (
<View style={styles.dashboard}>
<Text style={styles.currentValue}>
{currentValue?.toFixed(1)}°C
</Text>
<LineChart width={350} height={200} data={data}>
<XAxis dataKey="time" />
<YAxis domain={['auto', 'auto']} />
<Tooltip />
<Line
type="monotone"
dataKey="value"
stroke="#007AFF"
strokeWidth={2}
dot={false}
/>
</LineChart>
<View style={styles.controls}>
<Button
title="Set Target: 20°C"
onPress={() => sendCommand(deviceId, { target: 20 })}
/>
<Button
title="Set Target: 22°C"
onPress={() => sendCommand(deviceId, { target: 22 })}
/>
</View>
</View>
);
}
Security Best Practices
IoT Security Checklist:
├── Device Authentication
│ ├── Unique device certificates
│ ├── Secure key storage (TEE/HSM)
│ └── Token-based authentication
├── Communication Security
│ ├── TLS 1.3 for all connections
│ ├── Certificate pinning
│ └── End-to-end encryption for sensitive data
├── Firmware Security
│ ├── Signed firmware updates
│ ├── Secure boot chain
│ └── Rollback protection
├── App Security
│ ├── Secure credential storage
│ ├── No hardcoded secrets
│ └── Proper session management
└── Cloud Security
├── Per-device access control
├── Rate limiting
└── Audit logging
Testing IoT Apps
Testing Strategy:
├── Unit Tests
│ ├── Protocol parsers
│ ├── Data transformations
│ └── Business logic
├── Integration Tests
│ ├── Device simulators
│ ├── Cloud API mocking
│ └── BLE mock services
├── End-to-End Tests
│ ├── Real device testing
│ ├── Network condition testing
│ └── Setup flow testing
└── Performance Tests
├── Connection latency
├── Data throughput
└── Battery impact
Conclusion
IoT app development requires understanding:
- Protocols: BLE for nearby devices, MQTT for cloud, HTTP for APIs
- Provisioning: Smooth device setup is critical for UX
- Real-time: Handle live data updates efficiently
- Security: IoT devices are high-value targets
- Reliability: Handle disconnections gracefully
The IoT ecosystem is complex but offers tremendous opportunities.
Need help building an IoT companion app? Contact Hevcode for expert guidance on connected device applications.