Industrial Equipment Monitoring via Mobile App
Industrial equipment monitoring — task not so much of development as of choosing right data collection architecture. Vibration sensor on bearing generates 25,600 samples per second. Temperature sensor — once per 5 seconds. Motor current — 1,000 measurements per second. Mobile app cannot and should not work with this stream directly — only aggregated metrics, trends and alerts.
Data Collection Architecture
Equipment-level data collected by Edge component: industrial gateway (Moxa, Advantech, Siemens IPC) or custom Linux server. Normalizes data from different protocols and publishes aggregates to MQTT or serves via REST/WebSocket.
For mobile app two streams important:
- Real-time — current key parameter values, update every 1-5 seconds. WebSocket.
- History — trends over shift, day, week. REST API with pagination and aggregation.
Sensors → PLC / Edge Gateway → Time-Series DB (InfluxDB / TimescaleDB)
↓
Backend API (REST + WebSocket)
↓
Mobile app
Real-time via WebSocket: Flutter
class EquipmentMonitorRepository {
WebSocketChannel? _channel;
final StreamController<EquipmentState> _stateController =
StreamController.broadcast();
Stream<EquipmentState> get stateStream => _stateController.stream;
void connect(String equipmentId, String token) {
_channel = WebSocketChannel.connect(
Uri.parse('wss://iiot.factory.com/ws/equipment/$equipmentId'),
);
_channel!.stream
.map((event) => json.decode(event as String))
.map(EquipmentState.fromJson)
.listen(
_stateController.add,
onError: _handleError,
onDone: _scheduleReconnect,
);
_channel!.sink.add(json.encode({'auth': token}));
}
void _scheduleReconnect() {
Future.delayed(const Duration(seconds: 5), () => connect(_lastId, _lastToken));
}
}
BLoC for monitoring screen state management:
class EquipmentMonitorBloc extends Bloc<EquipmentEvent, EquipmentMonitorState> {
StreamSubscription<EquipmentState>? _subscription;
EquipmentMonitorBloc(this._repository) : super(EquipmentMonitorInitial()) {
on<StartMonitoring>((event, emit) async {
_subscription = _repository.stateStream.listen(
(state) => add(StateUpdated(state)),
);
_repository.connect(event.equipmentId, event.token);
});
on<StateUpdated>((event, emit) {
final current = event.state;
final isAlert = current.temperature > 85.0 || current.vibrationRms > 12.5;
emit(EquipmentMonitorRunning(state: current, hasAlert: isAlert));
});
}
}
Trend Visualization
For historical data use fl_chart (Flutter) or MPAndroidChart (Android native). Key optimization — don't pass all 86,400 points per day to chart, aggregate on API side. Query to InfluxDB-based API:
GET /api/v1/equipment/{id}/trend?
parameter=temperature&
from=2024-01-15T06:00:00Z&
to=2024-01-15T18:00:00Z&
resolution=300 # aggregate per 5 minutes
Response — 144 points instead of 43,200. Chart renders without freezes.
Baseline and Deviations
Useful feature — display baseline (normal range) on graph. If motor current normal 12-15A, highlight zone, operator immediately sees deviation:
LineChartData buildTrendChart(List<TrendPoint> data, Range baseline) {
return LineChartData(
extraLinesData: ExtraLinesData(
horizontalLines: [
HorizontalLine(y: baseline.min, color: Colors.green.withOpacity(0.3)),
HorizontalLine(y: baseline.max, color: Colors.green.withOpacity(0.3)),
],
),
betweenBarsData: [
BetweenBarsData(
fromIndex: 0,
toIndex: 0,
color: Colors.green.withOpacity(0.1),
),
],
lineBarsData: [
LineChartBarData(
spots: data.map((p) => FlSpot(p.timestamp.toDouble(), p.value)).toList(),
color: data.any((p) => p.value > baseline.max || p.value < baseline.min)
? Colors.red
: Colors.blue,
),
],
);
}
Alerts and Escalation
Simple alert — push via FCM/APNs. But for critical situations need escalation: if operator doesn't acknowledge alert within 5 minutes — goes to shift master, then shop chief.
Keep escalation logic on backend, mobile app only displays and acknowledges. Alert acknowledgement:
Future<void> acknowledgeAlert(String alertId) async {
await _dio.post('/api/v1/alerts/$alertId/acknowledge', data: {
'acknowledgedBy': currentUser.id,
'acknowledgedAt': DateTime.now().toIso8601String(),
'note': _noteController.text,
});
}
Alert screen shows escalation history — who received, who saw, who acknowledged. Important for post-shift analysis.
Offline and Shop Work
Factory shops — unstable Wi-Fi zone. Cache critical data (current metrics, active alerts) in SQLite via Room or Drift. On connection loss show last known state with "data from HH:MM, no connection" label.
Monitoring app development for one equipment type with WebSocket telemetry and historical trends — 4-6 weeks. Adding support for multiple equipment types, complex alerts with escalation, offline mode — 2-3 months. Cost calculated individually after analyzing data sources and reliability requirements.







