What this tool does
The UDP Data Plotter draws numbers out of UDP datagrams sent to
iperfect.net:49152 onto a live time-series chart. It is the UDP
twin of the MQTT Live Data Plotter: same chart, same console, same
configuration — only the transport differs. It does not know anything
about your payload format; you tell it how to pull the numbers out, using one
regular expression and one short line of JavaScript per line on the chart.
Nothing about your data leaves this server: the page talks to
ajax/udp_monitor.php on iperfect.net, which reads the Node
listener over the loopback interface. That is the same path the
UDP Monitor tab uses.
Before you start
- Send to iperfect.net on UDP port 49152. Both fields are fixed — that is the only port the listener service binds.
- Your payload has to be text. Binary bytes are shown as dots in the
console and cannot be matched by a regular expression. Send
temp=25.5, not a packed struct. - UDP is fire-and-forget. A datagram can be lost, duplicated or arrive out of order, and nothing tells the sender. Gaps in a line are normal on a busy or distant link — the plotter draws what arrived.
- Keep a datagram under 8 KB; larger ones are dropped before they reach the buffer.
- Use a current Chrome, Edge or Firefox. The page only reads — it never sends anything back to your device.
Sending test data
If nothing is publishing yet, send a datagram by hand and watch it land in the console. Run one of these from any machine with internet access.
Shell — netcat
echo "node1,2340,17,1.42,-0.87,9.61" | nc -u -w1 iperfect.net 49152
Shell — a stream of readings
while true; do
echo "node1,2340,17,$RANDOM.0,$RANDOM.0,$RANDOM.0" | nc -u -w0 iperfect.net 49152
sleep 1
done
Python
import socket, time, random
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
msg = "node1,2340,17,%.2f,%.2f,%.2f" % (random.uniform(-2, 2),
random.uniform(-2, 2),
random.uniform(8, 11))
s.sendto(msg.encode(), ("iperfect.net", 49152))
time.sleep(1)
ESP32 / ESP8266 — Arduino
#include <WiFiUdp.h>
WiFiUDP udp;
void sendReading(float t) {
udp.beginPacket("iperfect.net", 49152);
udp.printf("node1,temp=%.2f", t);
udp.endPacket();
}
<source ip>:<port> → <your text> for
every datagram that arrives — that is your proof the path works,
before any parsing is involved.
Quick start
- Start monitoring. Leave Host and UDP Port as they are and press Start Monitoring. The status pill turns green and reads Monitoring :49152.
-
Read one datagram. Send something (see
section 3) and copy one line out of the
Incoming UDP Datagrams console. Everything after the arrow is
what your expressions will see as
data. - Name your lines. In Axis Configuration set X-Axis data types to plot to how many lines you want, press Apply, then type a name into each row.
- Write the expressions. In RegEx Configuration set Expressions to plot to one + one per line and press Apply. Row 1 splits the datagram (tick Is RegEx); the rest plot (leave it unticked).
- Save Configuration. The page reloads with your settings, so press Start Monitoring again. Points appear as datagrams arrive.
How parsing works
Every row in RegEx Configuration runs once per datagram, top to bottom. The Is RegEx tick is what decides how it runs:
| Is RegEx | What the row does |
|---|---|
| Ticked | The text is treated as a regular expression. The plotter
matches it against the datagram text and stores every match in
the regEx array. It draws nothing by itself. |
| Unticked | The text is treated as a line of JavaScript and executed
as written. This is where you call addPoint(). |
What you can use inside a row
| Name | Meaning |
|---|---|
data |
The text of the datagram that just arrived. |
pkt |
The whole packet record — pkt.from (sender IP),
pkt.sport (sender port), pkt.len (bytes),
pkt.hex, pkt.seq, pkt.time
(server clock). This one has no counterpart on the MQTT
plotter. |
regEx |
The array produced by your Is RegEx row. Counts from zero. |
dt |
The time the datagram was picked up, in milliseconds. Use it as the x-value of every point. |
highcharts |
The chart. highcharts.series[0] is your first named
line, [1] the second, and so on. |
dt is the moment the browser read the datagram, not
the moment it landed. With a 1000 ms poll, several datagrams from the
same poll share almost the same x-value. Lower the poll interval for
finer spacing, or plot
Date.parse(pkt.time.replace(" ","T")) to use the server's
arrival stamp instead.
Counting the index
/([^|,]+)/g means “every run of characters that is not a
comma or a pipe” — in other words, every field. Suppose a
datagram reads:
Zone,2340,e1:0b:6f:40:82:fb,17,1.42,-0.87,9.61|ok
Splitting it gives these positions:
So the three numbers you want are regEx[4],
regEx[5] and regEx[6]. Do this with a datagram
copied from your own console — the layout above is only an
illustration of the counting.
Four rules worth remembering
- Put the Is RegEx row first. Rows run in order, so the array has to be filled before anything reads from it.
- Use only one Is RegEx row. A second one replaces the first one's result.
- End each addPoint with
,false. That skips a redraw per point; the chart redraws once after all rows have run, which keeps it smooth when a poll brings in a burst of datagrams. - Multiply by
1. Matches come out as text.(regEx[4])*1turns"1.42"into the number1.42. Without it the point plots as zero or not at all.
Worked example — three accelerometer axes
This is a complete, working configuration that plots AccelX, AccelY and AccelZ from a node sending the datagram shown above. Copy it field for field.
| Field | Value |
|---|---|
| Host / UDP Port | iperfect.net / 49152 — fixed |
| Poll Interval | 1000 ms |
| Source Filter | left empty — plot every sender |
| X-Axis data types | 3 → named AccelX,
AccelY, AccelZ |
| Expressions to plot | 4 — one to split, three to plot |
| Expression 1 Is RegEx ticked | /([^|,]+)/g |
| Expression 2 | highcharts.series[0].addPoint([dt,(regEx[4])*1],false) |
| Expression 3 | highcharts.series[1].addPoint([dt,(regEx[5])*1],false) |
| Expression 4 | highcharts.series[2].addPoint([dt,(regEx[6])*1],false) |
Read it as: row 1 chops the datagram into fields; rows 2–4 take fields 5, 6 and 7 and push them onto the first, second and third line of the chart, stamped with the arrival time.
Field reference
Top bar
| Control | What it does |
|---|---|
| Status pill | Not Monitoring · Starting · Monitoring :49152 · Listener Offline · Error. The dot pulses while the poll loop is running. |
| Start Monitoring | Begins polling for new datagrams, from the next one onwards. |
| Stop | Stops polling. The chart keeps whatever it has drawn. |
| Help | Opens this guide. |
| UDP Monitor | Opens the raw monitor tab on iperfect.net in a new tab — see section 10. |
Connection strip
| Field | What it does |
|---|---|
| Host | Fixed at iperfect.net. The listener can only see
traffic that lands on this server. |
| UDP Port | Fixed at 49152, the open testing port the listener
service binds. |
| Poll Interval (ms) | How often the page asks for new datagrams: 250–10000 ms, default 1000. Changing it while monitoring takes effect at once, no restart needed. Lower means finer x-spacing and more requests. |
| Source Filter | Optional. Only datagrams whose sender IP starts with this
text are logged and plotted — 192.168. for a
subnet, a full address for one device. Everything else is
ignored, not queued. |
Chart and console
| Control | What it does |
|---|---|
| 1M / 5M / All | Range selector. 1M keeps the last minute in view, which is usually what you want while watching live data. |
| Legend | Click a name to hide or show that line. |
| ☰ menu | Export as PNG, SVG or PDF, and View data table / download CSV of everything plotted so far. |
| Datagram console | Shows sender:port → text for each datagram,
plus start/stop notes. Holds the last 500 lines. |
| pkt counter | Datagrams accepted since the page loaded. System notes are not counted; filtered-out datagrams are not counted either. |
| Autoscroll / Clear | Follow the newest line, or empty the console. Clearing does not touch the chart. |
Configuration
| Field | What it does |
|---|---|
| X-Axis data types to plot | How many lines the chart has. Apply redraws the name rows below. |
| Series name | The label in the legend. The row's grey tag tells you which
highcharts.series[i] it is. |
| Visible Series | Tick boxes that hide or show a line without deleting its data. |
| Expressions to plot | How many expression rows. Apply redraws them. |
| Is RegEx | Ticked = match and fill regEx; unticked = run as
JavaScript. |
| Save Configuration | Stores everything in the page URL and reloads. Nothing you type takes effect until you press it. |
| Dump Parsed Data to Console | A stub kept for parity with the MQTT plotter; it does nothing on
its own. To inspect a parse, open the browser console (F12) and
read regEx after a datagram has arrived. |
Expression cookbook
Copy a row, change the index, done.
Splitting rows — tick Is RegEx
| Expression | Splits on |
|---|---|
/([^|,]+)/g |
commas and pipes — a,b|c |
/([^,]+)/g |
commas only — a,b,c |
/([^\s]+)/g |
spaces — a b c |
/(-?\d+\.?\d*)/g |
every number, ignoring all labels and separators |
/"(?:temp|hum)":(-?\d+\.?\d*)/g |
two named values out of JSON |
Plotting rows — leave Is RegEx unticked
| Expression | What it plots |
|---|---|
highcharts.series[0].addPoint([dt,(regEx[4])*1],false) |
field 5 onto the first line |
highcharts.series[0].addPoint([dt,parseFloat(data.split("temp=")[1])],false) |
a value straight out of key=value text, no split
row needed |
highcharts.series[0].addPoint([dt,pkt.len],false) |
datagram size in bytes — a cheap traffic graph |
highcharts.series[0].addPoint([dt,JSON.parse(data).temp],false) |
a field of a JSON payload |
if(pkt.from=="192.168.1.50"){highcharts.series[0].addPoint([dt,(regEx[4])*1],false)} |
one device onto one line, when several send the same format |
highcharts.series[0].addPoint([Date.parse(pkt.time.replace(" ","T")),(regEx[4])*1],false) |
the same value, stamped with the server's arrival time instead of the poll time |
Troubleshooting
| What you see | Why | Fix |
|---|---|---|
| Status goes to Error with a red bar | The monitor endpoint answered with a problem, or could not be reached | The bar carries the server's own message. Reload; if it persists the listener service is down |
| Listener Offline | The service is running but is not bound to UDP 49152 | Nothing to do from here — the port has to be rebound on the server |
| Monitoring but the console stays empty | Nothing is arriving on port 49152 | Send a test datagram (section 3). If that shows up, your device is the problem — check its destination host, port and that outbound UDP is not blocked |
| Test datagram arrives, the device's does not | A firewall, NAT or mobile network is dropping outbound UDP | Try the device on another network; many corporate and some mobile networks allow only DNS over UDP |
| Only some datagrams appear | UDP loss, or a Source Filter that does not match | Clear the filter first. Remember the filter is a prefix match on the sender IP |
| older datagram(s) skipped in the console | More datagrams arrived between two polls than one poll returns (200) | Lower the poll interval, or send less often |
| Console fills but the chart stays flat | Your regEx[…] indices point at the wrong
fields |
Recount from a real datagram; open the browser console and read
regEx |
| Lines plot as 0 | The field is text, not a number | Add *1, and check you are not pointing at a label |
| Points arrive in steps, not smoothly | Everything in one poll shares nearly the same dt |
Lower the poll interval, or stamp with
pkt.time (see the cookbook) |
| Console shows dots where text should be | The payload is binary | Send text. pkt.hex is available if you must work
with raw bytes |
| Chart shows one point at 05:30 | The placeholder point every new line starts with | Normal. It disappears from view once real data arrives; use 1M or 5M |
| Edits appear to do nothing | Save Configuration was not pressed | Press it — the page reloads with the new settings, then start again |
| Chart area shows a red “library unavailable” box | Highcharts could not be downloaded | Check internet access to code.highcharts.com or
cdn.jsdelivr.net |
| It slows down after a long run | Every point is kept in memory | Reload the page to start a fresh capture |
Plotter vs. UDP Monitor
Both read the same listener through the same endpoint. They differ in what they do with what arrives.
| UDP Data Plotter | UDP Monitor tab | |
|---|---|---|
| Shows | A chart, plus a text console | A terminal: text, hex, or both |
| History | Skipped — starts from the next datagram | Replays the listener's buffer when you start |
| Good for | Watching a value move over time | Seeing exactly what a device sent, byte for byte |
| Extras | Series, expressions, CSV/PNG export | Hex dump, packet/byte/source counters, save log, clear buffer, send a test packet |
A useful pairing: open the monitor first to see the raw shape of a datagram, copy one line out of it, then come here and write the expressions against it.