Industrial IoT (IIoT) Mobile App Development
Industrial IoT differs from consumer by three things: reliability more important than convenience, data flows 24/7, and cost of error is production stoppage. IIoT mobile app is not "turn on light," but operator tool reading Siemens S7-1500 PLC data via OPC UA, monitoring bearing vibration from IO-Link data, getting alerts when press mold temperature exceeds 195°C. Development approach accordingly.
Protocols and Communication Layer
First question at briefing — what protocols devices speak. Most common industrial variants:
| Protocol | Transport | Typical Use |
|---|---|---|
| OPC UA | TCP, WebSocket | PLC, SCADA, CNC machines |
| MQTT | TCP/TLS | Sensors, IoT gateways |
| Modbus TCP | TCP | Old PLC, frequency converters |
| PROFINET | Ethernet | Siemens industrial networks |
| IO-Link | RS-232/SIO | Field-level sensors |
Mobile app shouldn't speak Modbus TCP or OPC UA directly — too low-level. Right architecture: Edge Gateway collects device data, normalizes to single format (usually MQTT or REST), mobile app works with Gateway via secure channel.
PLC / sensors
↓ OPC UA / Modbus TCP
Edge Gateway (on-site)
↓ MQTT TLS / HTTPS
MQTT Broker / Backend (cloud or local)
↓ WebSocket / REST
Mobile app
MQTT Real-time: Android
For industrial apps on Android use Eclipse Paho MQTT or MQTT BLE variants. Key parameter — QoS. For telemetry (temperature once per second) QoS 0 sufficient. For control commands and critical alerts — QoS 2 with exactly-once delivery:
class MqttService : Service() {
private lateinit var client: MqttAndroidClient
fun connect(brokerUrl: String, credentials: MqttCredentials) {
client = MqttAndroidClient(applicationContext, brokerUrl, clientId)
client.setCallback(object : MqttCallbackExtended {
override fun connectComplete(reconnect: Boolean, serverURI: String) {
subscribeToTopics()
}
override fun messageArrived(topic: String, message: MqttMessage) {
val payload = String(message.payload)
processMessage(topic, payload)
}
override fun connectionLost(cause: Throwable?) {
// Log, trigger reconnect via ExponentialBackoff
scheduleReconnect(cause)
}
override fun deliveryComplete(token: IMqttDeliveryToken) {}
})
val options = MqttConnectOptions().apply {
userName = credentials.username
password = credentials.password.toCharArray()
isCleanSession = false // Keep subscriptions between reconnects
keepAliveInterval = 30
connectionTimeout = 10
isAutomaticReconnect = true
socketFactory = credentials.sslSocketFactory
}
client.connect(options)
}
}
isCleanSession = false critical for industrial apps: if phone loses connection, after reconnect broker delivers all missed QoS 1/2 messages.
Store Telemetry Locally
Store device data locally — production in basement without internet, operator rounds without signal. Room with Flow:
@Entity(tableName = "telemetry")
data class TelemetryRecord(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val deviceId: String,
val parameter: String,
val value: Double,
val unit: String,
val timestamp: Long,
val synced: Boolean = false
)
@Dao
interface TelemetryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(record: TelemetryRecord)
@Query("SELECT * FROM telemetry WHERE deviceId = :deviceId ORDER BY timestamp DESC LIMIT :limit")
fun observeLatest(deviceId: String, limit: Int): Flow<List<TelemetryRecord>>
@Query("SELECT * FROM telemetry WHERE synced = 0")
suspend fun getUnsynced(): List<TelemetryRecord>
}
WorkManager syncs unsynced records when network appears.
Alerts and Critical Notifications
Industrial FCM/APNs unsuitable for critical alerts — no delivery guarantee. For "stop-machine" alerts need direct WebSocket or MQTT push with local alarm as backup.
Implementation on Android: keep WebSocket in Foreground Service (type dataSync), on critical event call NotificationManager with IMPORTANCE_HIGH and Ringtone.play() at max volume:
fun showCriticalAlert(message: AlertMessage) {
val channel = NotificationChannel(
CRITICAL_CHANNEL_ID,
"Critical Alerts",
NotificationManager.IMPORTANCE_HIGH
).apply {
enableVibration(true)
vibrationPattern = longArrayOf(0, 500, 200, 500)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
}
notificationManager.createNotificationChannel(channel)
val notification = NotificationCompat.Builder(context, CRITICAL_CHANNEL_ID)
.setContentTitle("⚠ ${message.deviceName}")
.setContentText(message.description)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setAutoCancel(true)
.build()
notificationManager.notify(message.id.hashCode(), notification)
}
iOS — similarly via Critical Alerts (requires special entitlement: com.apple.developer.usernotifications.critical-alerts), play even in "Do Not Disturb" mode.
UX for Factory Operator
Industrial UX — not Material Design. Operator works in work gloves, poor light, phone in one hand. Requirements:
- Buttons minimum 48×48 dp, better 64×64 dp
- High-contrast theme with inversion option for direct sunlight
- Minimal navigation: needed info 1-2 taps away
- Offline mode with clear "no connection" indicator
Security and Access
Authentication in IIoT app — LDAP/Active Directory via SAML or OAuth 2.0 with corporate IdP. Biometric unlock acceptable for re-authentication, not initial login. All control commands logged with timestamp and user_id — audit mandatory.
IIoT app development from scratch (Android or iOS): 3-5 months depending on protocol count, device types and offline mode requirements. Cross-platform on Flutter — add 20-30% to timeline due to native platform channels. Cost calculated after detailed tech stack and reliability requirements analysis.







