How Serial Studio Turns Bytes Into Datasets
How Serial Studio turns raw bytes into datasets: Quick Plot, built-in templates, custom parsers, and why the frame parser is sometimes deliberately empty.
Serial Studio is built on one idea: the device sends data, and the host decides what that data means. "Deciding what it means" can happen in a few different places, and knowing which one to use is the difference between a dashboard that behaves and one that mysteriously records every value twice. This post walks through the options from simplest to most involved, and explains a case that surprises people: when the frame parser is supposed to be empty on purpose.
The simplest path: Quick Plot
If your device prints comma-separated numbers, one line per sample, you do not need a parser at all. Quick Plot reads each line, splits it on commas, and draws every value as its own trace. There is no code and no configuration. You pick the port, match the baud rate, and select Quick Plot (Comma Separated Values).
This is the right tool for a quick look at an Arduino sketch or any device with a plain CSV output. It is deliberately dumb, which is exactly why it is fast.
Built-in templates: still no code
When you outgrow Quick Plot and build a project with a real dashboard, you still often do not need to write anything. Serial Studio ships built-in parser templates. The Delimited text template exposes a separator, an optional quote character, and a couple of toggles. The Modbus frames template exposes a channel count and register offset. You fill in a small form, and the template does the parsing.
Reach for a template first. A surprising number of projects never need custom code.
Custom parsers: when the format is richer
You write a parse() function (in JavaScript or Lua) when the data is more than delimited text: a binary packet, a JSON payload, a frame with a checksum. The contract is simple. Serial Studio hands you one frame, and you return an array of values.
For comma-separated text the whole function is one line:
function parse(frame) {
return frame.split(",");
}
For a JSON message you pull out the fields you want in a fixed order:
function parse(frame) {
const msg = JSON.parse(frame);
return [msg.temp, msg.hum, msg.vbat];
}
Whatever you return becomes the row of values that feeds your widgets. Return an empty array and nothing renders for that frame.
The case that surprises people: the empty parser
Serial Studio has a Control Loop: a host-side script with setup() and loop() functions that behaves like an Arduino sketch, but runs on your computer instead of the device. Its normal job is to drive a connection you have already opened, sending handshakes or polling a device on a timer.
It can also do something less obvious. The Control Loop can write the dashboard's data tables itself and call dashboardTick() to render and record a frame. When the script generates the data this way, the device might be silent or just a dummy source. In that situation the frame parser must return empty:
function parse(frame) {
return []; // the Control Loop owns the data, so the parser stays quiet
}
The reason is worth understanding, because it reveals how the pipeline records data. If the parser also returns a non-empty frame, every value gets exported twice: once by the frame the parser produced, and once by the frame dashboardTick() synthesized. The two export paths then race in the recorded output. Leaving the parser empty keeps the script as the single owner of the data.
The opposite arrangement is the parser-driven one, and it has a mirror-image rule. When real device frames arrive and parse() writes the tables itself, have it return dummy data so each incoming frame still flows through the export pipeline once, and do not also call dashboardTick(). Either path on its own exports each value exactly once.
The mental model
All of this reduces to a single rule: exactly one thing should own each value. Decide who the owner is, and keep the other side quiet.
- Quick Plot owns the data. You write nothing.
- A template owns the data. You fill in a form.
- Your
parse()owns the data. Return the values. - The Control Loop owns the data. The parser returns empty.
Once you know which owner you have picked, the behavior stops being mysterious, and double-recorded values never happen. The Control Loop reference and the frame parser reference go deeper on each path.
Comments