Streaming AI Anomaly Detection for IoT Sensors

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

AI Development Areas

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1301
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1264
  • image_logo-advance_0.webp
    B2B Advance company logo design
    712
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1002
  • image_logo-aider_0.webp
    AIDER company logo development
    943
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1056

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

  1. Audit — analysis of current data sources, frequency, quality, and available infrastructure.
  2. Design — stack selection (Kafka / Flink / Spark / ONNX), topic schemes, and models.
  3. Development — pipeline coding, baseline and ML model training, alert configuration.
  4. Integration — connecting MQTT broker, InfluxDB, Grafana.
  5. Deployment — on servers or edge devices (ESP32, Raspberry Pi).
  6. Documentation — architecture description, operator instructions, retraining guide.
  7. 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 $18k–26k 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.