198 lines
6.2 KiB
C++
198 lines
6.2 KiB
C++
/*
|
|
dmxwidget.cpp
|
|
|
|
Copyright (c) 2023 Kevin Matz (kevin.matz@gmail.com)
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
#include "dmxwidget.h"
|
|
#include <QDebug>
|
|
|
|
DmxWidget::DmxWidget(QObject *parent)
|
|
: QObject{parent}
|
|
, port_(new QSerialPort(this))
|
|
{
|
|
connect(this, &DmxWidget::serialDataRead, this, &DmxWidget::parseMessageBuffer);
|
|
connect(port_, &QSerialPort::errorOccurred, this, &DmxWidget::serialErrorOccured);
|
|
connect(port_, &QSerialPort::readyRead, this, [this]() {
|
|
message_rx_buffer_.append(port_->readAll());
|
|
emit serialDataRead();
|
|
});
|
|
}
|
|
|
|
|
|
DmxWidget::~DmxWidget()
|
|
{
|
|
DmxWidget::close();
|
|
}
|
|
|
|
|
|
void DmxWidget::open()
|
|
{
|
|
auto wait_for = [this](bool &reply) {
|
|
reply = false; // reset the reply state variale
|
|
for (int i = 0; i < 10; i++) { // serial reply may not be first in the read buffer
|
|
port_->waitForReadyRead(100); // wait for the port read buffer to have content
|
|
if (reply)
|
|
break; // break the loop if a reply was received
|
|
}
|
|
return reply;
|
|
};
|
|
|
|
if (!port_->open(QIODeviceBase::ReadWrite)) // open the port
|
|
return;
|
|
ENTTEC::Widget::open(); // open the base class widget
|
|
|
|
getSerialNumber(); // request a serial number
|
|
getParameters(0); // get the metadata while we're here
|
|
if (wait_for(reply_serial)) // widget is found if a serial number reply is received
|
|
{
|
|
setModeBridge(); // widget defaults to bridge, so sync our state
|
|
emit connectedChanged(isConnected());
|
|
}
|
|
else
|
|
close();
|
|
}
|
|
|
|
|
|
void DmxWidget::close()
|
|
{
|
|
port_->close();
|
|
ENTTEC::Widget::close();
|
|
emit connectedChanged(isConnected());
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief DmxWidget::parseMessageBuffer
|
|
*/
|
|
void DmxWidget::parseMessageBuffer()
|
|
{
|
|
OperatingMode other_side = USBunknown;
|
|
switch (usb_mode) {
|
|
case ENTTEC::Widget::USBdevice:
|
|
other_side = USBhost;
|
|
break;
|
|
case ENTTEC::Widget::USBhost:
|
|
other_side = USBdevice;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
uint16_t length;
|
|
const int fixed_length = 5; // start + label + 2xlength + end
|
|
auto fast_forward = [this](int offset = 0) {
|
|
if (message_rx_buffer_.isEmpty())
|
|
return false;
|
|
if (offset == 0)
|
|
offset = message_rx_buffer_.indexOf(ENTTEC::Pro::START_DELIMITER);
|
|
if (offset < 0)
|
|
message_rx_buffer_.clear(); // start delimiter not found
|
|
else if (offset > 0)
|
|
message_rx_buffer_.remove(0, offset); // delimiter now at start of buffer
|
|
return (message_rx_buffer_.size() >= fixed_length
|
|
&& (uint8_t)message_rx_buffer_[0] == ENTTEC::Pro::START_DELIMITER);
|
|
};
|
|
|
|
while(fast_forward())
|
|
{
|
|
// read and check the data length
|
|
length = message_rx_buffer_[2] | message_rx_buffer_[3] << 8;
|
|
if (message_rx_buffer_.size() < length + fixed_length)
|
|
return; // message in buffer is incomplete
|
|
// look for the end delimeter
|
|
if ((uint8_t)message_rx_buffer_[length + fixed_length - 1] != ENTTEC::Pro::END_DELIMITER) {
|
|
fast_forward(length + fixed_length - 1); // discard corrupted bytes
|
|
continue;
|
|
}
|
|
// make a new message data
|
|
auto msg = MessageDataFactory((MESSAGE_LABEL)message_rx_buffer_[1], other_side);
|
|
// fill it with data
|
|
std::shared_ptr<bufferstream> stream(new bufferstream(
|
|
reinterpret_cast<uint8_t*>(message_rx_buffer_.data()+fixed_length-1),
|
|
length, bufferstream::endian::little));
|
|
msg->iStream(stream);
|
|
// ship it
|
|
routeRxMessage(msg);
|
|
// discard processed bytes
|
|
fast_forward(length + fixed_length);
|
|
}
|
|
}
|
|
|
|
|
|
void DmxWidget::sendMessage(std::shared_ptr<ENTTEC::Pro::MessageData> msg) const
|
|
{
|
|
uint16_t length = msg->streamSize();
|
|
const int fixed_length = 5;
|
|
|
|
char buffer[length + fixed_length];
|
|
std::shared_ptr<bufferstream> stream(new bufferstream(reinterpret_cast<uint8_t*>(buffer),
|
|
sizeof(buffer),
|
|
bufferstream::endian::little));
|
|
*stream << ENTTEC::Pro::START_DELIMITER;
|
|
*stream << msg->label;
|
|
*stream << length;
|
|
msg->oStream(stream);
|
|
*stream << ENTTEC::Pro::END_DELIMITER;
|
|
|
|
port_->write(buffer, sizeof(buffer));
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief DmxWidget::serialErrorOccured
|
|
* @param error
|
|
*/
|
|
void DmxWidget::serialErrorOccured(QSerialPort::SerialPortError error)
|
|
{
|
|
switch (error) {
|
|
case QSerialPort::WriteError:
|
|
case QSerialPort::ReadError:
|
|
case QSerialPort::ResourceError:
|
|
case QSerialPort::TimeoutError:
|
|
close();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief DmxWidget::availableWidgets
|
|
* @return
|
|
*/
|
|
QList<std::shared_ptr<DmxWidget>> DmxWidget::availableWidgets()
|
|
{
|
|
QList<std::shared_ptr<DmxWidget>> wdgts;
|
|
// evaluate all system serial ports
|
|
const auto ports = QSerialPortInfo::availablePorts();
|
|
for (const QSerialPortInfo &port : ports) {
|
|
// try to open a widget on each port
|
|
auto widget = std::make_shared<DmxWidget>();
|
|
widget->setPort(port);
|
|
widget->open();
|
|
if (widget->isConnected())
|
|
wdgts.push_back(widget); // add valid widgets to the list
|
|
else
|
|
widget->close();
|
|
}
|
|
return wdgts;
|
|
}
|