PCE-T 394 Thermometer Control (USB)
Control a PCE-T 394 four-channel K-type thermometer and data logger from the browser over USB: read all four probes live, download and chart the logger memory, export CSV, and configure or erase the recorder.
1. Connect
Close the vendor software first — a serial port is exclusive and the second program to ask is refused. The browser will show a picker listing devices matching 0x04D9/0x8008.
2. Live readings
The device cannot stream, so this polls its memory and shows the newest record. The polling period adapts to how long a read actually takes. A reading that goes stale is blanked rather than held at its last value — a frozen number that looks live is worse than no number.
3. Device status
The status reply carries the settings the last setup left behind, so it doubles as a read-back of the recorder configuration.
Raw 35-byte status payload
–
4. Download the logger memory
The device sends its entire memory every time — there is no range parameter — at roughly 20 kB/s. The decoded total is checked against the status count so a truncated transfer is reported rather than charted.
Last 200 records
| Time | Ch 1 | Ch 2 | Ch 3 | Ch 4 |
|---|---|---|---|---|
| Read the memory to see records here. | ||||
5. Configure the recorder
6. Frame log
Every frame in both directions, as it goes over the wire. Useful for checking a port of this protocol against a known-good exchange.
What this tool is
The PCE-T 394 is a four-channel K-type thermocouple thermometer with a built-in data logger. It ships with a Windows application called Smart Logger 2, which is the only software that knows how to talk to it. This page is a second option: it speaks the same protocol from a browser tab, with no installation, and it works on any desktop operating system a Chromium browser runs on.
The protocol below was recovered by capturing the USB traffic between Smart Logger 2 and a real instrument, and then verified against the hardware — serial 240302110375, through 2026. Nothing here is inferred from documentation, because there is no published documentation. Where a field is still unidentified, it says so rather than guessing.
How the device is reached from a browser
The meter is a USB CDC device — a Holtek USB-to-UART bridge presenting a virtual COM port at 0x04D9/0x8008. That makes it reachable through the Web Serial API, which lets a page open a serial port the user has explicitly picked from a browser dialog. The permission is granted per site and per device; the page cannot enumerate your ports, cannot open one you did not choose, and cannot reach anything else on the bus.
Because the bridge is a CDC device, the line settings barely matter — the tool opens at 115200 8N1 because that is what the vendor software uses, but the baud rate is not carried to the instrument in any meaningful way. What does matter is exclusivity: only one program can hold the port, so Smart Logger 2 must be closed before connecting here.
Framing
Every message in both directions has the same shape, delimited by ASCII braces and quotes:
7B 22 | cmd | len | data[len] | checksum | 22 7D
The start delimiter 7B 22 is the characters {"and the end delimiter 22 7D is "}. The checksum is the low byte of the arithmetic sum of the command byte, the length byte and every data byte.
That last point is the first trap in this protocol. The checksum is a sum, not an XOR — and the two coincide exactly when the payload is empty, which is precisely the case you will test first. Three of the four commands take no payload, so an XOR implementation appears to work perfectly right up to the moment you send the one command that erases the memory.
Because the length is a single byte, one frame carries at most 255 data bytes, so a memory transfer is split across many frames.
The four commands
The device never sends anything on its own. It does not stream readings, and it does not answer the 0x41 command used by the related CENTER 309 and Voltcraft K204 family. It responds to these four requests and nothing else.
| Request | Reply | Purpose |
|---|---|---|
0x0A | 0x0C, 14 bytes | Model identifier and serial number |
0x1A | 0x1C, 35 bytes | Record count and current recorder settings |
0x2A | 0x2C × n, then 0x3C | The entire memory image, 128 bytes per frame |
0x4A, 29 bytes | 0x1C, 35 bytes | Arm the recorder — and erase the memory |
Note that 0x4A breaks the otherwise consistent "reply = command + 2" pattern: it is answered by an ordinary status frame, not by a 0x4C. The reply's record count reads zero, which is how you confirm the erase actually happened.
The identification payload is eight ASCII characters of model identifier followed by six BCD bytes of serial number — LD812518followed by 24 03 02 11 03 75 for the unit this was developed against, whose serial matches the number printed on the case. It is worth asking for: the USB vendor and product ids belong to a generic bridge chip and say nothing about which instrument is on the other end.
Reading temperatures
There is no "read the current value" command. The device answers a request for its memory, and it answers it while a recording is in progress— which is what makes near-live monitoring possible at all. Ask for the memory once a second and the newest record is the current temperature.
The cost is that the entire memory comes back every time. There is no offset or range parameter, so the transfer grows with everything stored: about 0.3 seconds for 63 records, around a second once roughly two thousand have accumulated, at a fairly steady 20 kB/s. The device never clears itself, so old sessions keep piling up and every read gets slower. This is the real reason to erase before a long measurement, and it is why the live panel above adapts its polling period to about twice the measured read time rather than using a fixed rate.
One consequence is worth designing around. Between two polls you only know the last value you fetched, so anything logged in real time is a staircase with steps as long as the polling period. But each download contains the complete history with the device's own timestamps — so if you are logging alongside another instrument, take one final read when you finish and re-align your records against those timestamps. You then get the logger's full resolution instead of your polling rate.
How a measurement is encoded
The memory image is a flat array of eight-byte groups. A measurement record is four channels, each a big-endian signed 16-bit integer:
temperature (°C) = (raw − 5000) / 10
So the group 14 ED 14 86 14 82 00 00 decodes as 35.7 °C, 25.4 °C, 25.0 °C, and then a channel reading exactly −500.0 °C. That last value is not a temperature: a raw zero is the sentinel for a probe that is not plugged in, and anything at or below −500 °C should be dropped rather than charted.
Blocks, timestamps and padding
Records carry no timestamp of their own. Time comes from block markers — groups whose first byte is 0xFE, 0xFD or0xFC, carrying a 32-bit big-endian Unix timestamp in bytes 1 to 4. The time of record n inside a block is:
timestamp = block start + n × logging interval
Markers usually arrive in pairs carrying the same timestamp, so a new block begins only when that timestamp actually changes — treat a pair as one marker or you will split every block in two. None of0xFC–0xFE can be the high byte of a real reading (they would mean about −580 °C), so testing the first byte is a safe way to spot a marker.
Blocks appear repeatedly through the memory: the device closes one and opens another periodically, not only when the user presses REC. Consecutive blocks are contiguous in time, so for a continuous history you can simply concatenate them.
The group FF FF FF FF FF FF FF FF is unused memory. Here is the second serious trap: padding appears between blocks, not only after the last record. Stopping at the first one decodes only the first block, and the symptom is insidious — you get plausible data, but always the oldest records, so the reading looks frozen while the instrument is working perfectly. Skip padding and keep going.
Page headers, the nastiest trap
The memory is organised in 256-byte pages, and the first group of a page is sometimes a header rather than a record. Headers look like14 00 10 80 00 00 00 00 — where channel 1 decodes as 12.0 °C, which is an entirely believable room temperature. Range checks will not catch that.
They are told apart by continuity. Measured over a three-and-a-half-hour recording, genuine records at a page start differed from the previous sample by at most 0.2 K across 353 cases, while the 26 page headers differed by 102 to 124 K. A 20 K threshold separates the two populations with a factor of 500 to spare.
The tempting shortcut — skip every group at a page boundary — is wrong: in that same recording 353 of 379 page starts were real records, so a blanket rule throws away most of your data. If you skip the continuity test instead, the symptom is isolated readings of −77 °C or 1138 °C scattered through a long log. The groups rejected figure in the download panel above is this test doing its work.
Configuring the recorder, and the alarm encoding
The setup command is a 29-byte payload which is, in essence, the status payload with two timestamps written into it. The safe way to build it is to read the status immediately beforehand and echo everything you are not deliberately changing — that way the device keeps its logging interval and the handful of fields nobody has identified yet.
| Offset | Size | Meaning |
|---|---|---|
| 0 | uint32 BE | Current time (Unix) — the host's clock, shown as the meter time |
| 6 | byte | Start mode: 0x00 manual, 0x20 immediate, 0x40 scheduled |
| 11 | uint16 BE | Point limit; 65000 is the wizard's "until full" |
| 13 | uint16 BE | Logging interval in seconds |
| 15 | uint16 BE | Alarm high — see the encoding below |
| 17 | uint16 BE | Alarm low |
| 23 | uint32 BE | When recording should start (Unix), used in scheduled mode |
| 31 | uint16 BE | In the status reply: records currently in memory |
The status payload carries the same fields at the same offsets, so reading the status tells you the point limit, interval, start mode and alarm thresholds the device is currently holding.
And now the third trap. Alarm thresholds do not use the record encoding. Records put their zero at −500 °C; alarms put theirs at −50 °C:
alarm raw = °C × 10 + 500 °C = (raw − 500) / 10
This was verified against four values across two captures: 10 °C encodes as 600, 100 °C as 1500, 23 °C as 730, and 234 °C as 2840. Reusing the record offset would put every threshold 450 °C off — in a field the device never reads back to you at runtime, so nothing would ever tell you.
One field is conspicuously absent: the instrument name. Renaming the device in the vendor wizard changes nothing in the frame but the two timestamps, and the string appears nowhere in the USB traffic in ASCII or UTF-16. The name lives in Smart Logger 2, labelling its files. The device does not know it.
Start modes, and the fifteen-second lag
The start mode byte decides whether the device records after the erase, and getting it wrong is the difference between an instrument that keeps measuring and one sitting idle until somebody walks over to it. The device stores the mode and echoes it back in the status payload, so a program that never sets one inherits whatever the last setup left behind.
When you do start it immediately, expect the counter to lag. Measured after an immediate-start setup, the record count stayed at zero for 15.6 seconds and then advanced one per second exactly as configured. Do not read that first quiet stretch as a failure to start — wait at least thirty seconds before concluding anything is wrong.
Failure modes worth handling
Two of them will bite anyone who runs this for hours rather than minutes.
The device re-enumerates. Over a long session it drops off the USB bus and comes back, often on a different port — observed after about eighty minutes of once-a-second polling. Every write to the old handle then fails and the handle never recovers, so the only correct response is to close it and find the device again by its USB identity. This page reports it and asks you to reconnect.
Never repeat the last value when a read fails. It writes a plausible-looking constant temperature into your log, and any alarm you have set silently compares against a frozen number. Report the value as unknown instead, after a short grace period so a single hiccup does not blank the display. The live panel above blanks a stale channel for exactly this reason.
Two more are easy to mistake for faults. A transfer that is short by tens of percent was truncated, and because memory is sent oldest-first, truncation costs you exactly the newest records — compare the decoded total against the status count, allowing a small shortfall because the counter runs slightly ahead of what has been committed. And the memory only fills while the recorder is armed, so if the count never grows, the first thing to check is whether the device is recording at all.
Related pages
If you are working with the sensors rather than the instrument, thethermocouple calculator converts between temperature and EMF for type K and the other base-metal types and shows the cold-junction step explicitly. For the front-end electronics, see the sensor conditioningpage, and for the alternative sensor technology thePT100 and PT1000 calculator.
Common questions
- Which browsers can talk to the thermometer?
- Chrome, Edge, Opera and Brave on Windows, macOS, Linux and ChromeOS — anything Chromium-based on a desktop operating system. The page uses the Web Serial API, which Firefox and Safari have not implemented, and which no mobile browser offers at all. There is no polyfill: a web page genuinely cannot open a serial port without the browser providing the API, so on an unsupported browser the tool tells you up front instead of failing halfway through a connection. Everything else on the page — the protocol reference below — works everywhere.
- Do I need to install a driver or the vendor software?
- No, and you should in fact close the vendor software first. The meter enumerates as a standard USB CDC device (a virtual COM port) using a Holtek bridge chip, 0x04D9/0x8008, and every modern operating system has that driver built in. What you must avoid is two programs holding the port at once: a serial port is exclusive, so if Smart Logger 2 is running you will get an access-denied error when this page tries to connect. Quit it completely — not just its window — and try again.
- Does my temperature data get uploaded anywhere?
- No. There is no server involved and no account. The page is static, the protocol runs in JavaScript in your tab, and the readings, the downloaded logger memory and the CSV you export never leave your machine. You can confirm this the direct way: open the browser's network panel and watch it stay empty while the tool talks to the meter, or disconnect from the internet entirely after the page has loaded and carry on using it.
- Why do the live readings update in steps rather than continuously?
- Because the device cannot stream. It never sends anything on its own and has no read current value command — it only answers a request for its whole recorded memory. Live monitoring works by asking for that memory repeatedly and taking the newest record, so between two polls the displayed value is simply the last one fetched. The whole memory is sent every time, so the read gets slower as it fills, and the tool adapts its polling period to roughly twice the measured read time to avoid saturating the port. If you need full resolution, note that each download contains the complete history with the device's own timestamps — take one final read at the end and you get the logger's own one-second data rather than your polling rate.
- The record count is not moving. Is something broken?
- Probably not, but there are three innocent explanations before you suspect a fault. First, the device may not be recording at all — the memory only fills while the recorder is armed, so if nobody pressed REC and the start mode is manual, the memory is static and your readings will never change. Second, after an immediate-start setup the counter lags by about fifteen seconds before it moves at all, so wait at least thirty seconds before concluding anything. Third, a scheduled start does exactly what it says and will sit at zero until the time you set. The status panel shows the start mode the device is actually holding, which usually settles the question.
- What exactly does the setup command erase?
- Everything in the logger memory, immediately and without any undo. This is not a design choice of this page — the instrument has no separate erase command. Arming the recorder and wiping the memory are the same operation, which is why the vendor software's Clear Recorded Data button and its setup wizard send the identical frame. A scheduled start does not defer the wipe either: the memory goes at once and only the start of recording is deferred. Download anything you still want before sending it, which is why the tool refuses to send until you have ticked the confirmation.
- After a setup the device stopped recording. Why?
- The start mode byte. Setup writes a mode along with the erase, and if that mode is
0x00— manual — the device wipes its memory and then sits idle until somebody physically presses REC. It is easy to read that as a broken erase when it is actually a device waiting for a button. The device also remembers the last mode it was given, so a tool that does not set one inherits whatever was left behind. This page always makes you choose. - Why does the tool disconnect after a long monitoring session?
- The meter drops off the USB bus and re-enumerates, usually after an hour or more of once-a-second polling, and frequently comes back on a different port. Once that happens the old handle never recovers — every write fails from then on. There is no way to keep using it, so the tool detects the failure, reports it, and asks you to reconnect rather than quietly showing you a temperature that stopped updating an hour ago. Reconnecting picks the device up again by its USB identity.
- Can I port this to Python, or to my own application?
- Yes — the protocol reference below is written for exactly that, and the browser code on this page is a port itself. The things worth copying carefully are the ones that produce believable but wrong output rather than an obvious failure: the checksum is an arithmetic sum and not an XOR, the alarm thresholds use a different zero point from the temperature records, padding appears between memory blocks and not only at the end, and the first group of each 256-byte page is occasionally a header that decodes as a perfectly plausible temperature. Each of those is described below with the observed values.