IoT and AI

Labeling Time Series Sensor Data for Machine Learning

How to capture machine events and operator labels over MQTT alongside telemetry, define a label schema, convert events into labeled intervals and join them with sensor windows for supervised learning.

Labeling time series sensor data timeline with vibration telemetry and colored operator labels for normal, setup, fault and maintenance periods
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

Labeling time series sensor data is usually the hardest part of supervised machine learning in IoT, not because the tooling is complicated but because the ground truth is scattered across operator memories, paper logs and maintenance systems. This guide shows how to capture events and operator labels over MQTT at the moment they happen, how to design a label schema that survives real use, and how to turn those events into labeled windows joined with your telemetry.

Why Labels Are the Bottleneck

Sensor data is cheap to collect. A gateway publishing every few seconds produces millions of points within weeks. But a model that should recognise a clogged filter, a tool breakage or a quality defect needs examples where someone said "this happened, here, at this time". Those examples are rare, and the usual sources are unreliable:

  • Maintenance work orders record when the repair was done, not when the fault started, and often contain free text such as "fixed pump".
  • Shift logs are written at the end of the shift, with approximate times.
  • Alarm histories record threshold crossings, which are not the same as real faults.
  • Retrospective labeling by engineers looking at charts is slow and inconsistent between people.

The most effective fix is to capture labels as structured events, at the time they happen, on the same messaging layer as the telemetry. If the operator presses "jam cleared" on a tablet and that becomes an MQTT message with a timestamp and an asset path, the label is already aligned with the sensor data.

Types of Labels for Sensor Data

Label type Shape Example Typical use
Point event Single timestamp "Tool changed", "Sample taken" Segmenting lives, marking interventions
Interval Start and end "Setup from 08:10 to 08:45" Excluding non-production periods, state classification
Window class Fixed window with a class "This 10-minute window: cavitation" Classifier training
Outcome Result linked to a period "Batch 4411 failed quality check" Quality prediction
Failure Timestamp plus failure mode "Bearing failure, outer race" Failure and RUL models

Most projects need several types at once. Intervals for machine state (running, setup, cleaning, fault) are especially valuable because they let you exclude periods that would otherwise confuse a model.

Designing a Label Schema

Before building any interface, agree on a schema. A practical one includes:

  1. A fixed vocabulary of label codes, for example STATE_SETUP, STATE_CLEANING, FAULT_JAM, FAULT_LEAK, MAINT_BEARING_REPLACED. Free text goes into a separate comment field.
  2. An event type: start, end or point. Intervals are built from start and end pairs.
  3. The asset path, using the same hierarchy as the telemetry, so joining is trivial.
  4. Source timestamp and entry timestamp. The operator may enter "jam started 5 minutes ago"; record both when it happened and when it was entered.
  5. Who labeled it (role or badge, not necessarily a name) and confidence where relevant.
  6. A unique event ID, so corrections can reference and supersede an earlier label.
  7. A schema version to handle changes without breaking older data.

Keep the vocabulary short. Ten well-defined codes used consistently are worth more than a hundred codes that each operator interprets differently. The IIoT data modeling guide covers the asset path side of this design.

Capturing Labels over MQTT

Publish labels on a dedicated subtopic of each asset, next to its telemetry:

connectmq/plant1/line2/filler1/telemetry/vibration
connectmq/plant1/line2/filler1/labels

Labels should use QoS 1 so they are not silently lost, and should not be retained, because each label is an event rather than a current state. If you also want the current machine state visible to dashboards, publish it separately as a retained state topic.

Here is a small Python labeling client that an operator station or a test script can use. It publishes structured label events to the public MQTT test broker; for real operations, use your own broker credentials.

# label_client.py
import json
import sys
import time
import uuid

import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
ASSET = "connectmq/demo/lbl-4c2e/line2/filler1"
CODES = {"STATE_SETUP", "STATE_CLEANING", "FAULT_JAM", "FAULT_LEAK", "MAINT_BEARING_REPLACED"}


def publish_label(client, code, event, minutes_ago=0, comment="", operator="op-station-2"):
    if code not in CODES:
        raise ValueError(f"unknown label code {code}")
    now = int(time.time() * 1000)
    label = {
        "schema": 1,
        "id": str(uuid.uuid4()),
        "code": code,
        "event": event,                      # start | end | point
        "ts": now - int(minutes_ago * 60000),
        "entered": now,
        "by": operator,
        "comment": comment,
    }
    info = client.publish(f"{ASSET}/labels", json.dumps(label), qos=1)
    info.wait_for_publish()
    print("sent", label["code"], label["event"])


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="labeler-" + uuid.uuid4().hex[:8])
client.connect(BROKER, 1883, keepalive=60)
client.loop_start()

# Usage: python label_client.py FAULT_JAM start 3 "bottle stuck at infeed"
code, event = sys.argv[1], sys.argv[2]
minutes = float(sys.argv[3]) if len(sys.argv) > 3 else 0
comment = sys.argv[4] if len(sys.argv) > 4 else ""
publish_label(client, code, event, minutes, comment)

client.loop_stop()
client.disconnect()

In practice the same message can come from a Node-RED dashboard button, a tablet app using MQTT over WebSocket, a barcode scanner at a quality station, or a PLC that publishes its own state changes. Machine-generated labels, such as PLC state codes, are often the most reliable source for state intervals; operators then add what the machine cannot know, such as root causes.

Turning Events into Training Data

Once labels and telemetry are stored by a historian subscriber, the next step is to convert start and end events into intervals and assign them to telemetry windows. The following script does that with pandas.

# build_dataset.py
import pandas as pd

# labels.csv: id,code,event,ts   telemetry.csv: ts,vib_rms,current
labels = pd.read_csv("labels.csv", parse_dates=["ts"]).sort_values("ts")
tele = pd.read_csv("telemetry.csv", parse_dates=["ts"]).sort_values("ts")

# 1. Pair start/end events per code into intervals
intervals = []
open_events = {}
for row in labels.itertuples():
    if row.event == "start":
        open_events[row.code] = row.ts
    elif row.event == "end" and row.code in open_events:
        intervals.append((row.code, open_events.pop(row.code), row.ts))
intervals = pd.DataFrame(intervals, columns=["code", "start", "end"])

# 2. Build fixed windows of telemetry features
win = (tele.set_index("ts").resample("1min")
       .agg({"vib_rms": ["mean", "std", "max"], "current": ["mean"]}))
win.columns = ["_".join(c) for c in win.columns]
win = win.dropna().reset_index()
win["window_end"] = win["ts"] + pd.Timedelta(minutes=1)

# 3. Assign a label when a window overlaps an interval by more than half
def label_for(w):
    for iv in intervals.itertuples():
        overlap = (min(w.window_end, iv.end) - max(w.ts, iv.start)).total_seconds()
        if overlap > 30:
            return iv.code
    return "NORMAL"

win["label"] = [label_for(w) for w in win.itertuples()]

# 4. Drop ambiguous windows near interval boundaries
for iv in intervals.itertuples():
    near = ((win["ts"] - iv.start).abs() < pd.Timedelta(minutes=2)) | \
           ((win["ts"] - iv.end).abs() < pd.Timedelta(minutes=2))
    win.loc[near, "label"] = "EXCLUDE"

dataset = win[win["label"] != "EXCLUDE"]
print(dataset["label"].value_counts())
dataset.to_csv("training_windows.csv", index=False)

Two details matter here. Boundary windows are excluded because the exact start of a jam or leak is uncertain, and training on those windows teaches the model noise. And unpaired start events are not silently closed; review them, because a forgotten "end" press can turn hours of normal operation into a fault label.

Quality Checks for Labels

Use this checklist before training:

  1. Count labels per code and per asset; very uneven counts often reveal one shift or site not labeling at all.
  2. Find unpaired starts and ends, and overlapping intervals of incompatible codes.
  3. Compare entered time with event time; large gaps indicate retrospective guesses.
  4. Plot telemetry around a random sample of labels and check that the signal actually changes.
  5. Have a second person review a sample and measure agreement.
  6. Version the dataset with the label schema version and the query that produced it.

Keeping Labels Flowing

Labeling is not a one-time project. Make it part of normal work: the button that clears a jam alarm can also publish the label, and the maintenance closure form can publish the failure mode. Feed model alerts back as label candidates that operators confirm or reject; that confirmation is itself a valuable label. The IoT MLOps article describes how these feedback loops drive retraining, and predictive maintenance machine learning shows how failure labels become training targets. For degradation models, complete lifecycle events are essential, as explained in remaining useful life prediction.

Running Labels and Telemetry on ConnectMQ

Using one broker for telemetry and labels keeps everything aligned by asset path and time. ConnectMQ provides a fully managed MQTT service with MQTT over secure WebSocket on port 8084, so a browser-based labeling page can publish directly, and the live topic explorer lets you verify label messages as operators send them. Storage and dataset building run in your own subscribers, such as a historian writer and the script above. For the bigger picture of AI with connected devices, see the AI and IoT guide.

Get Started with ConnectMQ

Create a free Serverless broker at app.connectmq.com, no credit card required. Use the generated YOUR_USERNAME, YOUR_PASSWORD and YOUR_CLIENT_ID for your gateway and labeling clients, and start collecting labels next to your telemetry today; every week of consistent labels makes your next model better.

Frequently Asked Questions

What is the best way of labeling time series sensor data?

Capture labels as structured events at the moment they happen, with a fixed vocabulary, asset path and timestamps, on the same messaging layer as the telemetry. Retrospective labeling from charts is slower and less consistent.

Should label messages be retained in MQTT?

No. Labels are events, so publish them with QoS 1 and without the retain flag, and store them with a subscriber. Publish the current machine state separately as a retained topic if dashboards need it.

How do I handle uncertain label start times?

Record both the estimated event time and the entry time, and exclude windows close to interval boundaries from training. This removes the most ambiguous examples without discarding the label.

Can machines generate labels automatically?

Yes. PLC state codes, alarm events and quality station results are good automatic labels. Operators then add information the machine cannot know, such as root cause or confirmation of a model alert.

Related articles

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.