On a production line, a temperature sensor suddenly shows 95°C instead of the normal 60°C. Is this a physical anomaly (bearing overheating) or a sensor malfunction? Each false alarm reduces operator trust, while a missed alarm can cost millions. We built a streaming system on Kafka, Flink, and ONNX that resolves this dilemma with 95% accuracy and reduces false alarms by 3x. Our experience includes over 50 deployments in industrial and energy sectors.
Traditional threshold methods yield up to 40% false alarms. Contextual anomalies, considering time of day and day of week, reduce this to 5%. We combine statistics (z-score, EWMA) and ML inference on edge devices to minimize latency and maintain 95% accuracy. Typical stack: MQTT broker Mosquitto, Kafka 3.5 for buffering, Flink for windowed processing, PyTorch for training, and ONNX Runtime for inference on edge. Integration with Grafana + InfluxDB for real-time monitoring. Per Apache Kafka documentation, this architecture guarantees fault tolerance and scalability.
How the Streaming Architecture Works
Pipeline from sensor to alert:
MQTT (sensor) → Kafka → Flink / Spark Streaming → ML inference → AlertManager
↓
InfluxDB / TimescaleDB
↓
Grafana Dashboard
Kafka processing scheme:
from kafka import KafkaConsumer, KafkaProducer
import json
import numpy as np
from collections import defaultdict, deque
class IoTAnomalyProcessor:
def __init__(self, bootstrap_servers='kafka:9092',
window_size=60): # last 60 values
self.consumer = KafkaConsumer(
'iot-sensor-raw',
bootstrap_servers=bootstrap_servers,
value_deserializer=lambda m: json.loads(m.decode()),
group_id='anomaly-detection'
)
self.producer = KafkaProducer(
bootstrap_servers=bootstrap_servers,
value_serializer=lambda v: json.dumps(v).encode()
)
self.sensor_windows = defaultdict(lambda: deque(maxlen=window_size))
self.sensor_stats = {} # EWMA mean/std per sensor
def process(self):
for message in self.consumer:
reading = message.value
sensor_id = reading['sensor_id']
value = reading['value']
# Update sliding window
self.sensor_windows[sensor_id].append(value)
window = list(self.sensor_windows[sensor_id])
# Anomaly detection
if len(window) >= 30:
result = self.detect_anomaly(sensor_id, value, window)
if result['anomaly']:
self.producer.send('iot-anomalies', result)
def detect_anomaly(self, sensor_id, current_value, window):
mean = np.mean(window)
std = np.std(window)
z_score = (current_value - mean) / (std + 1e-9)
is_anomaly = abs(z_score) > 3.5
return {
'sensor_id': sensor_id,
'value': current_value,
'z_score': round(z_score, 2),
'anomaly': bool(is_anomaly),
'window_mean': round(mean, 3),
'window_std': round(std, 3),
'severity': 'critical' if abs(z_score) > 5 else 'warning'
}
Why Contextual Anomaly Matters
85°C may be normal at 2:00 PM but critical at 3:00 AM. Contextual models consider hour, day of week, and month to build a baseline. This reduces false alarms by 3x compared to plain z-score.
def contextual_anomaly_detection(sensor_id: str,
current_value: float,
timestamp: pd.Timestamp,
historical_data: pd.DataFrame) -> dict:
"""
Normal range depends on:
- Time of day (hour)
- Day of week
- Season (month)
Baseline built separately for each context.
"""
# Current moment context
context = {
'hour': timestamp.hour,
'day_of_week': timestamp.dayofweek,
'month': timestamp.month
}
# Historical data in the same context
context_data = historical_data[
(historical_data['sensor_id'] == sensor_id) &
(historical_data['hour'] == context['hour']) &
(historical_data['day_of_week'] == context['day_of_week'])
]['value']
if len(context_data) < 10:
return {'status': 'insufficient_context_data'}
context_mean = context_data.mean()
context_std = context_data.std()
context_z = (current_value - context_mean) / (context_std + 1e-9)
return {
'sensor_id': sensor_id,
'value': current_value,
'context': context,
'context_mean': round(context_mean, 3),
'context_z_score': round(context_z, 2),
'contextual_anomaly': abs(context_z) > 3,
'context_samples': len(context_data)
}
How to Distinguish Sensor Malfunction from Process Anomaly
If only one sensor in a group of five shows anomaly, it's likely a sensor fault. If all five do, it's a process anomaly. We use the single anomaly rule: when the proportion of anomalous sensors is below 25%, we conclude a sensor malfunction; above 60%, a physical anomaly.
def distinguish_sensor_vs_process_anomaly(sensor_group: dict,
anomalous_sensor_id: str) -> dict:
"""
If only one sensor in the group is anomalous → likely sensor fault.
If all/most sensors are anomalous → process anomaly.
Applied when multiple sensors measure the same physical zone.
"""
anomaly_count = sum(1 for s_id, result in sensor_group.items()
if result.get('anomaly', False))
total = len(sensor_group)
group_anomaly_ratio = anomaly_count / total
if group_anomaly_ratio <= 0.25:
return {
'conclusion': 'sensor_fault',
'sensor_id': anomalous_sensor_id,
'anomaly_ratio': group_anomaly_ratio,
'action': 'replace_or_recalibrate_sensor',
'process_alert': False
}
elif group_anomaly_ratio >= 0.6:
return {
'conclusion': 'process_anomaly',
'anomaly_ratio': group_anomaly_ratio,
'action': 'investigate_physical_process',
'process_alert': True
}
else:
return {
'conclusion': 'uncertain',
'anomaly_ratio': group_anomaly_ratio,
'action': 'manual_investigation',
'process_alert': True # just in case
}
What Is Edge Inference and Why Do You Need It?
For industrial networks with limited connectivity, cloud latency is unacceptable. We deploy ONNX models on ESP32 or Raspberry Pi. Inference runs locally with no network delay, consuming less than 100 ms per prediction.
import onnxruntime as ort
import numpy as np
class EdgeAnomalyDetector:
"""
ONNX model deployed on edge device.
Inference without cloud: critical for industrial networks with limited connectivity.
"""
def __init__(self, model_path: str, window_size: int = 30):
self.session = ort.InferenceSession(model_path)
self.window_size = window_size
self.buffer = []
self.threshold = 0.5
def infer(self, new_value: float) -> dict:
self.buffer.append(new_value)
if len(self.buffer) > self.window_size:
self.buffer.pop(0)
if len(self.buffer) < self.window_size:
return {'status': 'warming_up'}
# Normalization
arr = np.array(self.buffer, dtype=np.float32)
arr = (arr - arr.mean()) / (arr.std() + 1e-9)
input_tensor = arr.reshape(1, self.window_size, 1)
result = self.session.run(None, {'input': input_tensor})[0]
anomaly_score = float(result[0][0])
return {
'anomaly': anomaly_score > self.threshold,
'score': round(anomaly_score, 3),
'latency_ms': 'local' # no network latency
}
Comparison of Detection Methods
| Method | Sensitivity | False Positives | Implementation Complexity |
|---|---|---|---|
| Z-score (sliding window) | Medium | High (outliers) | Low — 1 day |
| Contextual Z-score | High | Low — accounts for time/day | Medium — 2-3 days |
| ML model (LSTM/Transformer) | Very High | Very Low | High — 2-3 weeks |
ML detection gives 3x fewer false positives compared to threshold methods. We choose the approach based on your data.
Comparison of IoT Protocols for Data Transfer
| Protocol | Latency | Reliability | Application |
|---|---|---|---|
| MQTT | Low | High | IoT sensors |
| CoAP | Low | Medium | Constrained devices |
| HTTP | High | High | Monitoring |
For most industrial scenarios, we recommend MQTT due to low latency and built-in quality of service (QoS).
What's Included in the Work
- Audit — analysis of current data sources, frequency, quality, and available infrastructure.
- Design — stack selection (Kafka / Flink / Spark / ONNX), topic schemes, and models.
- Development — pipeline coding, baseline and ML model training, alert configuration.
- Integration — connecting MQTT broker, InfluxDB, Grafana.
- Deployment — on servers or edge devices (ESP32, Raspberry Pi).
- Documentation — architecture description, operator instructions, retraining guide.
- Support — staff training, 1-month warranty support.
Timing and Estimated Cost
Timeline depends on complexity and volume: basic pipeline with z-score starts from 2 weeks; comprehensive solution with ML and edge up to 8 weeks. Cost is calculated individually. Certified engineers with 5 years of ML and IoT experience guarantee results. Example savings: reducing false alarms by 80% can save up to 2 million rubles per year in maintenance. Contact us — we will analyze your data and propose the optimal architecture within 3 days.
Integration with platforms: AWS IoT Core, Azure IoT Hub, Yandex IoT Core, EdgeX Foundry. MQTT brokers: Eclipse Mosquitto, EMQ X. Storage and visualization: Grafana + InfluxDB.
Need a consultation? Describe your task — we'll find a solution within one day.







