29constexpr int kMockTickIntervalMs = 100;
30constexpr float kMockSphereRadiusM = 0.10f;
31constexpr int kMockPointsPerLap = 60;
32constexpr int kStreamPauseMs = 300;
40 m_mockTimer.setInterval(kMockTickIntervalMs);
41 connect(&m_mockTimer, &QTimer::timeout,
this, &PolhemusConnection::onMockTick);
43 m_streamPauseTimer.setSingleShot(
true);
44 m_streamPauseTimer.setInterval(kStreamPauseMs);
45 connect(&m_streamPauseTimer, &QTimer::timeout,
this, &PolhemusConnection::onStreamPauseTimeout);
63 if (!portName.isEmpty()) {
64 if (openSerial(portName, cfg)) {
94 m_isConnected =
false;
95 m_backendName.clear();
103 return m_isConnected;
108 return m_backendName;
118 const auto ports = QSerialPortInfo::availablePorts();
119 names.reserve(ports.size());
120 for (
const auto& info : ports) {
121 names << info.portName();
128 constexpr quint16 kPolhemusVid = 0x0F44;
129 constexpr quint16 kFtdiVid = 0x0403;
131 QString ftdiCandidate;
132 for (
const auto& info : QSerialPortInfo::availablePorts()) {
133 if (!info.hasVendorIdentifier()) {
136 const quint16 vid = info.vendorIdentifier();
137 if (vid == kPolhemusVid) {
138 return info.portName();
140 if (vid == kFtdiVid && ftdiCandidate.isEmpty()) {
141 ftdiCandidate = info.portName();
144 return ftdiCandidate;
151bool PolhemusConnection::openMock()
153 m_backendName = QStringLiteral(
"mock");
159void PolhemusConnection::closeMock()
164void PolhemusConnection::onMockTick()
166 const double phase = (2.0 *
M_PI * (m_mockSampleIdx % kMockPointsPerLap))
167 /
static_cast<double>(kMockPointsPerLap);
168 const double elevation = 0.3 * std::sin(phase * 3.0);
170 kMockSphereRadiusM *
static_cast<float>(std::cos(phase)),
171 kMockSphereRadiusM *
static_cast<float>(std::sin(phase)),
172 kMockSphereRadiusM *
static_cast<float>(elevation));
176 const QVector3D penPos(
177 kMockSphereRadiusM *
static_cast<float>(std::cos(phase + 0.3)),
178 kMockSphereRadiusM *
static_cast<float>(std::sin(phase + 0.3)),
179 kMockSphereRadiusM *
static_cast<float>(elevation) + 0.02f);
180 const QQuaternion penOri = QQuaternion::fromAxisAndAngle(
181 QVector3D(0, 0, 1),
static_cast<float>(phase * 180.0 /
M_PI));
194 auto* port =
new QSerialPort(
this);
195 port->setPortName(portName);
197 port->setDataBits(QSerialPort::Data8);
198 port->setParity(QSerialPort::NoParity);
199 port->setStopBits(QSerialPort::OneStop);
200 port->setFlowControl(QSerialPort::NoFlowControl);
202 if (!port->open(QIODevice::ReadWrite)) {
203 emit
errorOccurred(QStringLiteral(
"Serial open failed (%1): %2")
204 .arg(portName, port->errorString()));
211 m_parser.setUnits(cfg.
units);
213 connect(m_pSerial, &QSerialPort::readyRead,
this, &PolhemusConnection::onSerialReadyRead);
214 connect(m_pSerial, &QSerialPort::errorOccurred,
this, &PolhemusConnection::onSerialError);
221 for (
int st = 1; st <= 4; ++st) {
222 m_pSerial->write(QStringLiteral(
"l%1,1\r").arg(st).toLatin1());
223 m_pSerial->write(QStringLiteral(
"O%1,2,4,1\r").arg(st).toLatin1());
232 for (
int st = 1; st <= 4; ++st) {
233 const QByteArray cmd = QStringLiteral(
"H%1,%2,%3,%4\r")
235 .arg(h.x(), 0,
'f', 1).arg(h.y(), 0,
'f', 1).arg(h.z(), 0,
'f', 1)
237 m_pSerial->write(cmd);
240 qInfo() <<
"Fastrak: hemisphere set to" << cfg.
hemisphere;
247 m_backendName = QStringLiteral(
"Fastrak (%1 @ %2 baud)")
253void PolhemusConnection::closeSerial()
258 m_streamPauseTimer.stop();
259 m_pSerial->disconnect(
this);
260 if (m_pSerial->isOpen()) {
261 m_pSerial->write(QByteArrayLiteral(
"P\r"));
265 m_pSerial->deleteLater();
270void PolhemusConnection::onSerialReadyRead()
275 m_parser.append(m_pSerial->readAll());
279void PolhemusConnection::onSerialError(QSerialPort::SerialPortError err)
281 if (err == QSerialPort::NoError) {
287 const QString msg = QStringLiteral(
"Serial error: %1").arg(m_pSerial->errorString());
290 if (err == QSerialPort::ResourceError || err == QSerialPort::DeviceNotFoundError) {
295void PolhemusConnection::drainParser()
298 while (m_parser.nextSample(s)) {
303 m_streamPauseTimer.start();
307void PolhemusConnection::onStreamPauseTimeout()
309 if (!m_pSerial || !m_isConnected)
return;
311 for (
auto it = m_lastSamples.constBegin(); it != m_lastSamples.constEnd(); ++it) {
312 emit
penButtonPressed(it.key(), it.value().position, it.value().orientation);
315 m_pSerial->write(QByteArrayLiteral(
"C\r"));
Polhemus digitizer connection abstraction.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
QVector3D position
Position in metres (always normalised to SI).
QQuaternion orientation
Sensor orientation; identity if not provided.
int station
1-based station id reported by the device.
Settings used to open a real Polhemus serial connection.
FastrakParser::Units units
~PolhemusConnection() override
void connectedChanged(bool isConnected)
void penButtonPressed(int station, const QVector3D &position, const QQuaternion &orientation)
void pointReceived(int station, const QVector3D &position, const QQuaternion &orientation)
PolhemusConnection(QObject *parent=nullptr)
void errorOccurred(const QString &message)
bool open(const QString &portName, const PolhemusSerialConfig &cfg=PolhemusSerialConfig{})
static QString autoDetectPortName()
Best-effort auto-detection of an attached Fastrak/FastSCAN.
QString backendName() const
static QStringList availablePorts()
Enumerate every serial port currently visible to the OS.