Add nymea light classes

This commit is contained in:
Simon Stürz 2021-02-05 17:08:26 +01:00 committed by Boernsman
parent 17dc0f7186
commit 274668ed60
6 changed files with 387 additions and 0 deletions

92
ws2812fx/nymealight.cpp Normal file
View File

@ -0,0 +1,92 @@
#include "nymealight.h"
#include "extern-plugininfo.h"
#include <QDataStream>
NymeaLight::NymeaLight(NymeaLightInterface *interface, QObject *parent) :
QObject(parent),
m_interface(interface)
{
connect(m_interface, &NymeaLightInterface::availableChanged, this, [=](bool available){
qCDebug(dcWs2812fx()) << "Interface available changed" << available;
emit availableChanged(available);
});
connect(m_interface, &NymeaLightInterface::dataReceived, this, &NymeaLight::onDataReceived);
}
NymeaLightInterfaceReply *NymeaLight::setColor(const QColor &color, quint16 fadeDuration)
{
// Build the request
QByteArray requestData;
QDataStream stream(&requestData, QIODevice::WriteOnly);
stream << static_cast<quint8>(NymeaLightInterface::CommandSetColor);
stream << m_requestId++;
if (fadeDuration > 0) {
stream << static_cast<quint8>(NymeaLightInterface::ModeFade);
}
stream << static_cast<quint8>(color.red());
stream << static_cast<quint8>(color.green());
stream << static_cast<quint8>(color.blue());
if (fadeDuration > 0) {
stream << fadeDuration;
}
NymeaLightInterfaceReply *reply = createReply(requestData);
m_pendingRequests.enqueue(reply);
sendNextRequest();
return reply;
}
bool NymeaLight::available() const
{
return m_interface->available();
}
NymeaLightInterfaceReply *NymeaLight::createReply(const QByteArray &requestData)
{
NymeaLightInterfaceReply *reply = new NymeaLightInterfaceReply(requestData, this);
connect(reply, &NymeaLightInterfaceReply::finished, reply, &NymeaLightInterfaceReply::deleteLater);
return reply;
}
void NymeaLight::sendNextRequest()
{
if (m_currentReply)
return;
if (m_pendingRequests.isEmpty())
return;
// TODO: if not available, finish all replies with unknown error
m_currentReply = m_pendingRequests.dequeue();
qCDebug(dcWs2812fx()) << "Sending request" << m_currentReply->command() << m_currentReply->requestId() << m_currentReply->requestData().toHex();
m_interface->sendData(m_currentReply->requestData());
}
void NymeaLight::onDataReceived(const QByteArray &data)
{
qCDebug(dcWs2812fx()) << "Recived data" << data;
Q_ASSERT(data.length() >= 3);
NymeaLightInterface::Command command = static_cast<NymeaLightInterface::Command>(data.at(0));
quint8 requestId = static_cast<quint8>(data.at(1));
NymeaLightInterface::Status status = static_cast<NymeaLightInterface::Status>(data.at(2));
if (m_currentReply) {
if (m_currentReply->command() == command && m_currentReply->requestId() == requestId) {
m_currentReply->m_status = status;
qCDebug(dcWs2812fx()) << "Request finished" << command << m_currentReply->requestId() << status;
emit m_currentReply->finished();
m_currentReply = nullptr;
sendNextRequest();
}
} else {
qCWarning(dcWs2812fx()) << "Received unhandled data" << data.toHex();
}
}

41
ws2812fx/nymealight.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef NYMEALIGHT_H
#define NYMEALIGHT_H
#include <QObject>
#include <QColor>
#include <QQueue>
#include "nymealightinterface.h"
class NymeaLight : public QObject
{
Q_OBJECT
public:
explicit NymeaLight(NymeaLightInterface *interface, QObject *parent = nullptr);
// Set the color. If fade duration is 0, the color will be set immediatly,
// otherwise it will fade to the color with the given fade duration
NymeaLightInterfaceReply *setColor(const QColor &color, quint16 fadeDuration = 0);
bool available() const;
private:
NymeaLightInterface *m_interface = nullptr;
quint8 m_requestId = 0;
NymeaLightInterfaceReply *m_currentReply = nullptr;
QQueue<NymeaLightInterfaceReply *> m_pendingRequests;
NymeaLightInterfaceReply *createReply(const QByteArray &requestData);
void sendNextRequest();
private slots:
void onDataReceived(const QByteArray &data);
signals:
void availableChanged(bool available);
};
#endif // NYMEALIGHT_H

View File

@ -0,0 +1,6 @@
#include "nymealightinterface.h"
NymeaLightInterface::NymeaLightInterface(QObject *parent) : QObject(parent)
{
}

View File

@ -0,0 +1,76 @@
#ifndef NYMEALIGHTINTERFACE_H
#define NYMEALIGHTINTERFACE_H
#include <QObject>
class NymeaLightInterface : public QObject
{
Q_OBJECT
public:
enum Command {
CommandSetColor = 0x00,
CommandSetBrightness = 0x01,
CommandSetSpeed = 0x02,
CommandSetEffect = 0x03,
CommandCustom = 0xFF
};
Q_ENUM(Command)
enum Status {
StatusSuccess = 0x00,
StatusInvalidProtocol = 0x01,
StatusInvalidCommand = 0x02,
StatusInvalidPlayload = 0x03,
StatusUnknownError = 0xff
};
Q_ENUM(Status)
enum Mode {
ModeDirect = 0x00,
ModeFade = 0x01
};
Q_ENUM(Mode)
explicit NymeaLightInterface(QObject *parent = nullptr);
virtual bool open() = 0;
virtual void close() = 0;
virtual bool available() = 0;
virtual void sendData(const QByteArray &data) = 0;
signals:
void availableChanged(bool available);
void dataReceived(const QByteArray &data);
};
class NymeaLightInterfaceReply : public QObject
{
Q_OBJECT
friend class NymeaLight;
public:
QByteArray requestData() const { return m_requestData; };
NymeaLightInterface::Command command() const { return m_command; };
quint8 requestId() const { return m_requestId; };
NymeaLightInterface::Status status() const { return m_status; };
signals:
void finished();
private:
explicit NymeaLightInterfaceReply(const QByteArray &requestData, QObject *parent = nullptr) : QObject(parent), m_requestData(requestData) {
Q_ASSERT(m_requestData.length() >= 2);
m_command = static_cast<NymeaLightInterface::Command>(m_requestData.at(0));
m_requestId = static_cast<quint8>(m_requestData.at(1));
}
QByteArray m_requestData;
NymeaLightInterface::Command m_command;
quint8 m_requestId;
NymeaLightInterface::Status m_status = NymeaLightInterface::StatusUnknownError;
};
#endif // NYMEALIGHTINTERFACE_H

View File

@ -0,0 +1,133 @@
#include "nymealightserialinterface.h"
#include "extern-plugininfo.h"
#include <QDataStream>
NymeaLightSerialInterface::NymeaLightSerialInterface(const QString &name, QObject *parent) :
NymeaLightInterface(parent)
{
m_serialPort = new QSerialPort(name, this);
m_serialPort->setBaudRate(115200);
m_serialPort->setDataBits(QSerialPort::DataBits::Data8);
m_serialPort->setParity(QSerialPort::Parity::NoParity);
m_serialPort->setStopBits(QSerialPort::StopBits::OneStop);
m_serialPort->setFlowControl(QSerialPort::FlowControl::NoFlowControl);
connect(m_serialPort, &QSerialPort::readyRead, this, &NymeaLightSerialInterface::onReadyRead);
connect(m_serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onSerialError(QSerialPort::SerialPortError)));
m_reconnectTimer = new QTimer(this);
m_reconnectTimer->setInterval(5000);
m_reconnectTimer->setSingleShot(false);
connect(m_reconnectTimer, &QTimer::timeout, this, [=](){
if (m_serialPort->isOpen()) {
m_reconnectTimer->stop();
return;
} else {
if (open()) {
m_reconnectTimer->stop();
}
}
});
}
bool NymeaLightSerialInterface::open()
{
if (!m_serialPort->open(QIODevice::ReadWrite)) {
qCWarning(dcWs2812fx()) << "Could not open serial port" << m_serialPort->portName() << m_serialPort->errorString();
return false;
}
emit availableChanged(true);
return true;
}
void NymeaLightSerialInterface::close()
{
m_serialPort->close();
emit availableChanged(false);
}
bool NymeaLightSerialInterface::available()
{
return m_serialPort->isOpen();
}
void NymeaLightSerialInterface::sendData(const QByteArray &data)
{
// Stream bytes using SLIP
qCDebug(dcWs2812fx()) << "Write data" << data.toHex();
QByteArray message;
QDataStream stream(&message, QIODevice::WriteOnly);
for (int i = 0; i < data.length(); i++) {
quint8 dataByte = data.at(i);
switch (dataByte) {
case SlipProtocolEnd:
stream << static_cast<quint8>(SlipProtocolEsc);
stream << static_cast<quint8>(SlipProtocolTransposedEnd);
break;
case SlipProtocolEsc:
stream << static_cast<quint8>(SlipProtocolEsc);
stream << static_cast<quint8>(SlipProtocolTransposedEsc);
break;
default:
stream << dataByte;
break;
}
}
m_serialPort->write(message);
m_serialPort->flush();
}
void NymeaLightSerialInterface::onReadyRead()
{
QByteArray data = m_serialPort->readAll();
qCDebug(dcWs2812fx()) << "Received data" << data.toHex();
for (int i = 0; i < data.length(); i++) {
quint8 receivedByte = data.at(i);
if (m_protocolEscaping) {
switch (receivedByte) {
case SlipProtocolTransposedEnd:
m_buffer.append(static_cast<quint8>(SlipProtocolEnd));
m_protocolEscaping = false;
break;
case SlipProtocolTransposedEsc:
m_buffer.append(static_cast<quint8>(SlipProtocolEsc));
m_protocolEscaping = false;
break;
default:
// SLIP protocol violation...received escape, but it is not an escaped byte
break;
}
}
switch (receivedByte) {
case SlipProtocolEnd:
// We are done with this package, process it and reset the buffer
emit dataReceived(m_buffer);
m_buffer.clear();
m_protocolEscaping = false;
break;
case SlipProtocolEsc:
// The next byte will be escaped, lets wait for it
m_protocolEscaping = true;
break;
default:
// Nothing special, just add to buffer
m_buffer.append(receivedByte);
break;
}
}
}
void NymeaLightSerialInterface::onSerialError(QSerialPort::SerialPortError error)
{
if (error != QSerialPort::NoError && m_serialPort->isOpen()) {
qCCritical(dcWs2812fx()) << "Serial port error:" << error << m_serialPort->errorString();
m_reconnectTimer->start();
m_serialPort->close();
emit availableChanged(false);
}
}

View File

@ -0,0 +1,39 @@
#ifndef NYMEALIGHTSERIALINTERFACE_H
#define NYMEALIGHTSERIALINTERFACE_H
#include <QObject>
#include <QSerialPort>
#include <QTimer>
#include "nymealightinterface.h"
class NymeaLightSerialInterface : public NymeaLightInterface
{
Q_OBJECT
public:
explicit NymeaLightSerialInterface(const QString &name, QObject *parent = nullptr);
bool open() override;
void close() override;
bool available() override;
void sendData(const QByteArray &data) override;
private:
enum SlipProtocol {
SlipProtocolEnd = 0xC0,
SlipProtocolEsc = 0xDB,
SlipProtocolTransposedEnd = 0xDC,
SlipProtocolTransposedEsc = 0xDD
};
QTimer *m_reconnectTimer = nullptr;
QSerialPort *m_serialPort = nullptr;
QByteArray m_buffer;
bool m_protocolEscaping = false;
private slots:
void onReadyRead();
void onSerialError(QSerialPort::SerialPortError error);
};
#endif // NYMEALIGHTSERIALINTERFACE_H