Summary
This article desribes the development of the low level driver for the Phoenix system
Sources
Item | Location | Purpose |
|---|---|---|
1 | Captures serial communication | |
2 | Converts HHD dmslog files to readable JSON | |
3 | Contains the Phoenix low level driver |
Capture serial communication
Install HHD Device Monitoring Studio, a 14 day trial is available.
Start Device Monitoring Studio, in the next examples the Phoenix system is connected to COM9 which is RS422.

Don't change anything on the next screen

Add Data Recording and specify the output file.

Next click start on the bottom right
Data recording is now active and will continue until the stop button is clicked

Start VZSoft
Make sure to start the VZSoft as administrator, otherwise it will not run.
Click on tools then Data Source Selection and connect, wait until you see an asterix at the port of connection. No need to push any buttons.

You can continue the datacapture doing all kinds of stuff, once done click on the stop button of the HHD software.
Analyze serial communication
On github there is a tool to convert the dmslog file produced by HHD to JSON format
https://github.com/lucwens/PTIConvert.git
Use the Teamviewer file tool to fetch the dmslog file

It is a CMake project, so once the dmslog file is tranferred, make sure to specify the args correctly

Run the program.
The output will be in the same folder with extension JSON.
{
"metadata": {
"sourceFile": "D:\\PTIConvert\\Data\\detect.dmslog8",
"sessionTimestamp": "2026-02-14T13:05:33.033940Z",
"device": "[[[s:120:[[[b[[[uSerial Port Properties]]]]]]]]]\n\n[[[bMax. TX Queue]]]\tnone\n[[[bMax. RX Queue]]]\tnone\n[[[bMax. Baud Rate]]]\tconfigurable\n[[[bProvider]]]\tRS232\n[[[bCapabilities]]]\tDTR/DSR, RTS/CTS, RLSD (DCD), Parity Check, XON/XOFF, Settable XON/XOFF, Total time-outs, Interval time-outs\n[[[bSettable Parameters]]]\tParity, Baud, Data Bits, Stop Bits, Handshaking, Parity Check, RLSD (DCD)\n[[[bBaud Rates]]]\t75, 110, 134.5, 150, 300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 38400, 56K, 128K, 115200, configurable\n[[[bData Bits]]]\t5, 6, 7, 8\n[[[bStop Bits]]]\t1, 1.5, 2\n[[[bParity]]]\tNone, Odd, Even, Mark, Space\n[[[bCurrent Tx Queue]]]\tunavailable\n[[[bCurrent Rx Queue]]]\t130,944 bytes\n\n",
"portConfig": "2,500,000, data bits: 8, stop bits: 1, parity: None",
"protocol": "Phoenix Visualeyez VZK10 RS-422"
},
"summary": {
"totalFrames": 2,
"commands": 1,
"dataSets": 0,
"messages": 0,
"initMessages": 1,
"unknownFrames": 0
},
"frames": [
{
"index": 0,
"timestamp": "2026-02-14T13:09:17.409371Z",
"direction": "RX",
"type": "initMessage",
"initMessage": {
"serialNumber": "5152535455565758",
"status": 1,
"initialized": true
},
"rawHex": "0102030451525354555657580d0e0110111213"
},
{
"index": 1,
"timestamp": "2026-02-14T13:09:17.409374Z",
"direction": "TX",
"type": "command",
"command": {
"code": "?",
"index": "1",
"name": "Query/Identify",
"bytesPerParam": 0,
"numParams": 0,
"description": "&?1 (Query/Identify)"
},
"rawHex": "263f3130300d"
}
]
}
"metadata" and "summary" are extra blocks, the actual captured frames are in "frames".
So we can see on connecting we received a block which contains the serial number of the tracker.
Garbled communication
At this moment I'm unable to establish decend communication with the VZtracker.
Things to check:
- What are the exact communication parameters
How is the initial communication
- Can I send a &7 Do nothing return ack
- Do I need to do a reset on pin 2, this would send an initial message with the serial number
- Do I need to send a &` to perform a reset?
Test programs
- One by claude
- And our original driver
Exact communication parameters
Procmon
I sampled with Procmon the events, and did a save of the filtered results using the filter "Multi".
Gemini declined to help.
Opening the csv files with Excel and setting claude to work did give some more results
Summary: Communication Parameters for COM9
Here's what I found in the ProcMon logfile regarding the serial port communication parameters used by Multi-Tracker Detection_x64.exe (Phoenix Technologies Visualeyez VZSoft 3.0.2):
1. Application-Level Parameters (VZServer Registry)
Found in rows A633:A640 — the app reads HKLM\SOFTWARE\Phoenix Technologies Inc.\VZServer\Parameters:
| Parameter | Value | Type | Interpretation |
|---|---|---|---|
| Host Connection | 16 | REG_DWORD | Likely the COM port number (COM16) or a connection type identifier |
| Comm Rate | 3 | REG_DWORD | Baud rate index — most likely maps to 57,600 baud (if 0=9600) or 9,600 baud (if 0=1200) |
| Priority | 128 | REG_DWORD | Thread/process priority |
| Jumping Filter | 1 | REG_DWORD | Motion data filter enabled |
| Jumping Filter Constant | 300, 300, 300 | REG_BINARY | Filter threshold for X, Y, Z axes |
| Adaptive Calibration | 1 | REG_DWORD | Auto-calibration enabled |
2. Hardware-Level Parameters (Oxford Semiconductor UART)
Found in rows A3388:A3399 — the app reads two serial ports from the Oxford Semiconductor PCI Express multifunction card:
| Parameter | Port 0 (65000000) | Port 1 (65000001) |
|---|---|---|
| Clock Rate | 62,500,000 Hz (62.5 MHz) | 62,500,000 Hz |
| 95xSpecific | 148 bytes of chip-specific config | 148 bytes of chip-specific config |
3. What's NOT in the Log
The ProcMon capture does not contain:
- A direct CreateFile call to \\.\COM9 (the filter may have excluded file system operations on device handles)
- Any DeviceIoControl / IOCTL_SERIAL_SET_BAUD_RATE calls
- Explicit baud rate, parity, stop bits, or flow control settings via Windows API
Key Takeaway
The "Comm Rate" = 3 from the VZServer\Parameters registry is the primary communication speed parameter. Currently one standard PCI card slot is required to control up to 4 trackers www.ptiphoenix.com for this Visualeyez motion capture system, and the Oxford Semiconductor OXPCIEMF PCI Express card provides the serial ports. Without VZSoft's internal documentation, the exact baud rate mapping for index 3 is most likely either 57,600 baud (common mapping: 0→9600, 1→19200, 2→38400, 3→57600) or 9,600 baud (common mapping: 0→1200, 1→2400, 2→4800, 3→9600).
The "Host Connection" = 16 value likely identifies which port/connection type the VZServer uses to communicate with the tracker hardware. If it maps directly to a COM port number, it would be COM16 (not COM9) — which could mean COM9 was referenced elsewhere outside this capture, or that the port numbering changed.
=> So even Claude did dive in deep, no usefull information here
Using HHD
Use Request View for the capture.
Note: all captured files are in d:\pticonvert\data

The analysis of this by gemini
Initial detection communication
Here is the enhanced chronological overview, decoded using the manufacturer's low-level protocol:
Phase 1: Hardware Reset via DTR (18:35:29)
What happened: The application opened the COM port at 2 Mbps and rapidly toggled the DTR (Data Terminal Ready) line.
SDK Context: According to the DB9 Data Connector pinout in the manual, Pin 2 is the Hardware RESET pin. The manual explicitly states that pulling this pin low initiates a hardware reset, and the host must wait for the tracker to reboot. The application is using the DTR serial control line to physically trigger this hardware reset on the tracker.
Phase 2: Polling for the "Initial Message Set" (18:35:29 - 18:35:30)
What happened: The application went into a polling loop, asking the COM port if any data had arrived. After about 1.5 seconds, exactly 19 bytes arrived in the receive queue.
SDK Context: This perfectly matches the SDK specification. The manual states: "Immediately after a hardware reset (including the power-up reset), a Visualeyez tracker will output a message set". Furthermore, it strictly defines that "each output from the tracker to the host is always 19 bytes long".
+1
What was in those 19 bytes: According to the "Initial Message Set Definitions", those 19 bytes contained the tracker's initialization confirmation code (01h), along with the Tracker Serial Number embedded in bytes 5 through 12.
Phase 3: The Application Sends a Command (18:35:30.925)
What happened: Immediately after receiving the 19-byte initialization message, the application sent the 6-byte hex payload 26 3f 31 30 30 0d.
SDK Context: Using the SDK's "Command Format" table, we can parse this exact hex string into the proprietary Visualeyez command structure:
0x26 = & (Command Header: Indicates the start of a command)
0x3f = ? (Command Code: The specific action to perform)
0x31 = 1 (Command Index: Specifies the target object/module)
0x30 = 0 (# bytes per parameter)
0x30 = 0 (# of parameters to follow)
0x0d = <CR> (Carriage Return: Indicates the end of the command word)
Note on the ? command: While ? is not listed in the public command shortlists (like &L, &R, or &7), the structure perfectly matches a zero-parameter inquiry command. Given that it happens right after boot, the application is likely pinging the tracker for its firmware version, device capabilities, or simply acknowledging the bootup.
+2
Phase 4: Switching to Production Speed (18:35:31)
What happened: The application reconfigured the COM port, changing the baud rate from 2,000,000 to 2,500,000 (2.5 Mbps).
SDK Context: This is specifically mandated by the manufacturer. Section 2 ("Operation Requirements") strictly declares: "The host computer's serial port must be set to these settings for communication: Baud rate - 2.5 MBaud, Start bits - 1, Data bits - 8, Stop bits - 1".
Summary of the connection sequence:
The app forces a hardware reboot via the DTR pin.
It waits ~1.5 seconds for the tracker to boot up and announce its 19-byte Initial Message Set (which contains its Serial Number).
The app replies with an &?100<CR> protocol command to acknowledge/query the tracker.
The app shifts the baud rate to the mandatory 2.5 Mbps to prepare for high-speed, 10,000 Hz 3D motion capture data streaming.
Test programs
Testing the detection
https://github.com/lucwens/PhoenixDetect.git
Converting dmslog8 files to JSON
https://github.com/lucwens/PTIConvert.git
The actual driver
https://github.com/lucwens/CTrack.Proxies.git
Status
At tag 1.0.1 we have
- Detection runs by pooling COM 1 to 16 , the serial number of the tracker does look like nonsense
- We can cycle start->measure->stop
We have a ValidateMeasurementSetup function that checks for dangerous pitfalls
- Check document OperationalLimits.md
- For each marker we get quality information per lense
- We have a configuration detection that works on visibility, no electronic checking possible as with NDI
- Added Doc/abbreviations.md
Next steps
Imlement proxy driver
- Transfer low level routines
- Create 3D group responder by having a TCM selector
- Create Settings dialog
- Create Parameters dialog
- Per marker we also need quality channels similar to NDI approach
Test proxy driver
- Should work independently
Implement multi-tracker support
- Each trackers has his own proxy
- Fuse function required that does a general job for everyone