From 632be887ad8421eaf4ab70c63f8d8d6488d1fdd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 5 Feb 2021 17:07:29 +0100 Subject: [PATCH 1/4] Pull changes from other branch --- ws2812fx/integrationpluginws2812fx.cpp | 520 ++++++++++--------------- ws2812fx/integrationpluginws2812fx.h | 17 +- ws2812fx/ws2812fx.pro | 6 + 3 files changed, 217 insertions(+), 326 deletions(-) diff --git a/ws2812fx/integrationpluginws2812fx.cpp b/ws2812fx/integrationpluginws2812fx.cpp index 7eb50c1dd..93ae75152 100644 --- a/ws2812fx/integrationpluginws2812fx.cpp +++ b/ws2812fx/integrationpluginws2812fx.cpp @@ -41,13 +41,15 @@ and \l{Vendor}{Vendors} of this \l{DevicePlugin}. For more details how to read this JSON file please check out the documentation for \l{The plugin JSON File}. - +s \quotefile plugins/deviceplugins/ws2812fx/devicepluginws2812fx.json */ #include #include "integrationpluginws2812fx.h" #include "plugininfo.h" +#include "nymealightserialinterface.h" + IntegrationPluginWs2812fx ::IntegrationPluginWs2812fx () { } @@ -63,36 +65,26 @@ void IntegrationPluginWs2812fx::setupThing(ThingSetupInfo *info) return; } - QSerialPort *serialPort = new QSerialPort(interface, this); + NymeaLightSerialInterface *lightInterface = new NymeaLightSerialInterface(interface, thing); + NymeaLight *light = new NymeaLight(lightInterface, this); + lightInterface->setParent(light); - serialPort->setBaudRate(115200); - serialPort->setDataBits(QSerialPort::DataBits::Data8); - serialPort->setParity(QSerialPort::Parity::NoParity); - serialPort->setStopBits(QSerialPort::StopBits::OneStop); - serialPort->setFlowControl(QSerialPort::FlowControl::NoFlowControl); - - if (!serialPort->open(QIODevice::ReadWrite)) { - qCWarning(dcWs2812fx()) << "Could not open serial port" << interface << serialPort->errorString(); - serialPort->deleteLater(); - return info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error opening serial port.")); + if (!lightInterface->open()) { + qCWarning(dcWs2812fx()) << "Could not open interface" << interface; + light->deleteLater(); + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error opening serial port.")); + return; } - connect(serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onSerialError(QSerialPort::SerialPortError))); - connect(serialPort, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + connect(light, &NymeaLight::availableChanged, thing, [=](bool available){ + qCDebug(dcWs2812fx()) << thing << "available changed" << available; + thing->setStateValue(ws2812fxConnectedStateTypeId, available); + }); qCDebug(dcWs2812fx()) << "Setup successfully serial port" << interface; thing->setStateValue(ws2812fxConnectedStateTypeId, true); m_usedInterfaces.append(interface); - m_serialPorts.insert(thing, serialPort); - - if(!m_reconnectTimer) { - m_reconnectTimer = new QTimer(this); - m_reconnectTimer->setSingleShot(true); - m_reconnectTimer->setInterval(5000); - - connect(m_reconnectTimer, &QTimer::timeout, this, &IntegrationPluginWs2812fx::onReconnectTimer); - } - + m_lights.insert(thing, light); info->finish(Thing::ThingErrorNoError); } @@ -122,182 +114,202 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) { Thing *thing = info->thing(); Action action = info->action(); - - - QByteArray command; - if (action.actionTypeId() == ws2812fxPowerActionTypeId) { - command.append("b "); - if (action.param(ws2812fxPowerActionPowerParamTypeId).value().toBool()) { - command.append("30"); - } else { - command.append("0"); - } - command.append("\r\n"); - return sendCommand(info, command, CommandType::Brightness); - } - - if (action.actionTypeId() == ws2812fxBrightnessActionTypeId) { - - command.append("b "); - command.append(action.param(ws2812fxBrightnessActionBrightnessParamTypeId).value().toString()); - command.append("\r\n"); - return sendCommand(info, command, CommandType::Brightness); - } - - if (action.actionTypeId() == ws2812fxSpeedActionTypeId) { - - command.append("s "); - command.append(action.param(ws2812fxSpeedActionSpeedParamTypeId).value().toString()); - command.append("\r\n"); - return sendCommand(info, command, CommandType::Speed); + NymeaLight *light = m_lights.value(thing); + if (!light || !light->available()) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; } if (action.actionTypeId() == ws2812fxColorActionTypeId) { - - QColor color; - color= action.param(ws2812fxColorActionColorParamTypeId).value().value(); - command.append("c "); - command.append(QString(color.name()).remove("#")); - command.append("\r\n"); - return sendCommand(info, command, CommandType::Color); - } - - if (action.actionTypeId() == ws2812fxColorTemperatureActionTypeId) { - - // minValue 153, maxValue 500 - QColor color; - color.setRgb(255, 255, static_cast((255.00-(((action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value().toDouble()-153.00)/347.00))*255.00))); - thing->setStateValue(ws2812fxColorTemperatureStateTypeId, action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value()); - command.append("c "); - command.append(QString(color.name()).remove("#")); - command.append("\r\n"); - return sendCommand(info, command, CommandType::Color); + QColor color = action.param(ws2812fxColorActionColorParamTypeId).value().value(); + qCDebug(dcWs2812fx()) << "Set color to" << color.name(QColor::HexRgb); + NymeaLightInterfaceReply *reply = light->setColor(color); + connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished); + connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){ + if (reply->status() != NymeaLightInterface::StatusSuccess) { + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + qCDebug(dcWs2812fx()) << "Set color finished successfully" << color.name(QColor::HexRgb); + thing->setStateValue(ws2812fxColorStateTypeId, color); + info->finish(Thing::ThingErrorNoError); + }); } - if (action.actionTypeId() == ws2812fxEffectModeActionTypeId) { - - QString effectMode = action.param(ws2812fxEffectModeActionEffectModeParamTypeId).value().toString(); - command.append("m "); - if (effectMode == "Static") { - command.append(QString::number(FX_MODE_STATIC)); - } else if (effectMode == "Blink") { - command.append(QString::number(FX_MODE_BLINK)); - } else if (effectMode == "Color Wipe") { - command.append(QString::number(FX_MODE_COLOR_WIPE)); - } else if (effectMode == "Color Wipe Inverse") { - command.append(QString::number(FX_MODE_COLOR_WIPE_INV)); - } else if (effectMode == "Color Wipe Reverse") { - command.append(QString::number(FX_MODE_COLOR_WIPE_REV)); - } else if (effectMode == "Color Wipe Reverse Inverse") { - command.append(QString::number(FX_MODE_COLOR_WIPE_REV_INV)); - } else if (effectMode == "Color Wipe Random") { - command.append(QString::number(FX_MODE_COLOR_WIPE_RANDOM)); - } else if (effectMode == "Random Color") { - command.append(QString::number(FX_MODE_RANDOM_COLOR)); - } else if (effectMode == "Single Dynamic") { - command.append(QString::number(FX_MODE_SINGLE_DYNAMIC)); - } else if (effectMode == "Multi Dynamic") { - command.append(QString::number(FX_MODE_MULTI_DYNAMIC)); - } else if (effectMode == "Rainbow") { - command.append(QString::number(FX_MODE_RAINBOW)); - } else if (effectMode == "Rainbow Cycle") { - command.append(QString::number(FX_MODE_RAINBOW_CYCLE)); - } else if (effectMode == "Scan") { - command.append(QString::number(FX_MODE_SCAN)); - } else if (effectMode == "Dual Scan") { - command.append(QString::number(FX_MODE_DUAL_SCAN)); - } else if (effectMode == "Fade") { - command.append(QString::number(FX_MODE_FADE)); - } else if (effectMode == "Theater Chase") { - command.append(QString::number(FX_MODE_THEATER_CHASE)); - } else if (effectMode == "Theater Chase Rainbow") { - command.append(QString::number(FX_MODE_THEATER_CHASE_RAINBOW)); - } else if (effectMode == "Running Lights") { - command.append(QString::number(FX_MODE_RUNNING_LIGHTS)); - } else if (effectMode == "Twinkle") { - command.append(QString::number(FX_MODE_TWINKLE)); - } else if (effectMode == "Twinkle Random") { - command.append(QString::number(FX_MODE_TWINKLE_RANDOM)); - } else if (effectMode == "Twinkle Fade") { - command.append(QString::number(FX_MODE_TWINKLE_FADE)); - } else if (effectMode == "Twinkle Fade Random") { - command.append(QString::number(FX_MODE_TWINKLE_FADE_RANDOM)); - } else if (effectMode == "Sparkle") { - command.append(QString::number(FX_MODE_SPARKLE)); - } else if (effectMode == "Flash Sparkle") { - command.append(QString::number(FX_MODE_FLASH_SPARKLE)); - } else if (effectMode == "Hyper Sparkle") { - command.append(QString::number(FX_MODE_HYPER_SPARKLE)); - } else if (effectMode == "Strobe") { - command.append(QString::number(FX_MODE_STROBE)); - } else if (effectMode == "Strobe Rainbow") { - command.append(QString::number(FX_MODE_STROBE_RAINBOW)); - } else if (effectMode == "Multi Strobe") { - command.append(QString::number(FX_MODE_MULTI_STROBE)); - } else if (effectMode == "Blink Rainbow") { - command.append(QString::number(FX_MODE_BLINK_RAINBOW)); - } else if (effectMode == "Chase White") { - command.append(QString::number(FX_MODE_CHASE_WHITE)); - } else if (effectMode == "Chase Color") { - command.append(QString::number(FX_MODE_CHASE_COLOR)); - } else if (effectMode == "Chase Random") { - command.append(QString::number(FX_MODE_CHASE_RANDOM)); - } else if (effectMode == "Chase Flash") { - command.append(QString::number(FX_MODE_CHASE_FLASH)); - } else if (effectMode == "Chase Flash Random") { - command.append(QString::number(FX_MODE_CHASE_FLASH_RANDOM)); - } else if (effectMode == "Chase Rainbow White") { - command.append(QString::number(FX_MODE_CHASE_RAINBOW_WHITE)); - } else if (effectMode == "Chase Blackout") { - command.append(QString::number(FX_MODE_CHASE_BLACKOUT)); - } else if (effectMode == "Chase Blackout Rainbow") { - command.append(QString::number(FX_MODE_CHASE_BLACKOUT_RAINBOW)); - } else if (effectMode == "Color Sweep Random") { - command.append(QString::number(FX_MODE_COLOR_SWEEP_RANDOM)); - } else if (effectMode == "Running Color") { - command.append(QString::number(FX_MODE_RUNNING_COLOR)); - } else if (effectMode == "Running Red Blue") { - command.append(QString::number(FX_MODE_RUNNING_RED_BLUE)); - } else if (effectMode == "Running Random") { - command.append(QString::number(FX_MODE_RUNNING_RANDOM)); - }else if (effectMode == "Larson Scanner") { - command.append(QString::number(FX_MODE_LARSON_SCANNER)); - }else if (effectMode == "Comet") { - command.append(QString::number(FX_MODE_COMET)); - }else if (effectMode == "Fireworks") { - command.append(QString::number(FX_MODE_FIREWORKS)); - }else if (effectMode == "Fireworks Random") { - command.append(QString::number(FX_MODE_FIREWORKS_RANDOM)); - }else if (effectMode == "Merry Christmas") { - command.append(QString::number(FX_MODE_MERRY_CHRISTMAS)); - }else if (effectMode == "Fire Flicker") { - command.append(QString::number(FX_MODE_FIRE_FLICKER)); - }else if (effectMode == "Fire Flicker (soft)") { - command.append(QString::number(FX_MODE_FIRE_FLICKER_SOFT)); - }else if (effectMode == "Fire Flicker (intense)") { - command.append(QString::number(FX_MODE_FIRE_FLICKER_INTENSE)); - }else if (effectMode == "Circus Combustus") { - command.append(QString::number(FX_MODE_CIRCUS_COMBUSTUS)); - }else if (effectMode == "Halloween") { - command.append(QString::number(FX_MODE_HALLOWEEN)); - }else if (effectMode == "Bicolor Chase") { - command.append(QString::number(FX_MODE_BICOLOR_CHASE)); - }else if (effectMode == "Tricolor Chase") { - command.append(QString::number(FX_MODE_TRICOLOR_CHASE)); - }else if (effectMode == "ICU") { - command.append(QString::number(FX_MODE_ICU)); - }else if (effectMode == "Custom 0") { - command.append(QString::number(FX_MODE_CUSTOM_0)); - }else if (effectMode == "Custom 1") { - command.append(QString::number(FX_MODE_CUSTOM_1)); - }else if (effectMode == "Custom 2") { - command.append(QString::number(FX_MODE_CUSTOM_2)); - }else if (effectMode == "Custom 3") { - command.append(QString::number(FX_MODE_CUSTOM_3)); - } - command.append("\r\n"); - return sendCommand(info, command, CommandType::Mode); - } +// QByteArray command; +// if (action.actionTypeId() == ws2812fxPowerActionTypeId) { +// command.append("b "); +// if (action.param(ws2812fxPowerActionPowerParamTypeId).value().toBool()) { +// command.append("30"); +// } else { +// command.append("0"); +// } +// command.append("\r\n"); +// return sendCommand(info, command, CommandType::Brightness); +// } + +// if (action.actionTypeId() == ws2812fxBrightnessActionTypeId) { + +// command.append("b "); +// command.append(action.param(ws2812fxBrightnessActionBrightnessParamTypeId).value().toString()); +// command.append("\r\n"); +// return sendCommand(info, command, CommandType::Brightness); +// } + +// if (action.actionTypeId() == ws2812fxSpeedActionTypeId) { + +// command.append("s "); +// command.append(action.param(ws2812fxSpeedActionSpeedParamTypeId).value().toString()); +// command.append("\r\n"); +// return sendCommand(info, command, CommandType::Speed); +// } + +// if (action.actionTypeId() == ws2812fxColorActionTypeId) { + +// QColor color; +// color= action.param(ws2812fxColorActionColorParamTypeId).value().value(); +// command.append("c "); +// command.append(QString(color.name()).remove("#")); +// command.append("\r\n"); +// return sendCommand(info, command, CommandType::Color); +// } + +// if (action.actionTypeId() == ws2812fxColorTemperatureActionTypeId) { + +// // minValue 153, maxValue 500 +// QColor color; +// color.setRgb(255, 255, static_cast((255.00-(((action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value().toDouble()-153.00)/347.00))*255.00))); +// thing->setStateValue(ws2812fxColorTemperatureStateTypeId, action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value()); +// command.append("c "); +// command.append(QString(color.name()).remove("#")); +// command.append("\r\n"); +// return sendCommand(info, command, CommandType::Color); +// } + +// if (action.actionTypeId() == ws2812fxEffectModeActionTypeId) { + +// QString effectMode = action.param(ws2812fxEffectModeActionEffectModeParamTypeId).value().toString(); +// command.append("m "); +// if (effectMode == "Static") { +// command.append(QString::number(FX_MODE_STATIC)); +// } else if (effectMode == "Blink") { +// command.append(QString::number(FX_MODE_BLINK)); +// } else if (effectMode == "Color Wipe") { +// command.append(QString::number(FX_MODE_COLOR_WIPE)); +// } else if (effectMode == "Color Wipe Inverse") { +// command.append(QString::number(FX_MODE_COLOR_WIPE_INV)); +// } else if (effectMode == "Color Wipe Reverse") { +// command.append(QString::number(FX_MODE_COLOR_WIPE_REV)); +// } else if (effectMode == "Color Wipe Reverse Inverse") { +// command.append(QString::number(FX_MODE_COLOR_WIPE_REV_INV)); +// } else if (effectMode == "Color Wipe Random") { +// command.append(QString::number(FX_MODE_COLOR_WIPE_RANDOM)); +// } else if (effectMode == "Random Color") { +// command.append(QString::number(FX_MODE_RANDOM_COLOR)); +// } else if (effectMode == "Single Dynamic") { +// command.append(QString::number(FX_MODE_SINGLE_DYNAMIC)); +// } else if (effectMode == "Multi Dynamic") { +// command.append(QString::number(FX_MODE_MULTI_DYNAMIC)); +// } else if (effectMode == "Rainbow") { +// command.append(QString::number(FX_MODE_RAINBOW)); +// } else if (effectMode == "Rainbow Cycle") { +// command.append(QString::number(FX_MODE_RAINBOW_CYCLE)); +// } else if (effectMode == "Scan") { +// command.append(QString::number(FX_MODE_SCAN)); +// } else if (effectMode == "Dual Scan") { +// command.append(QString::number(FX_MODE_DUAL_SCAN)); +// } else if (effectMode == "Fade") { +// command.append(QString::number(FX_MODE_FADE)); +// } else if (effectMode == "Theater Chase") { +// command.append(QString::number(FX_MODE_THEATER_CHASE)); +// } else if (effectMode == "Theater Chase Rainbow") { +// command.append(QString::number(FX_MODE_THEATER_CHASE_RAINBOW)); +// } else if (effectMode == "Running Lights") { +// command.append(QString::number(FX_MODE_RUNNING_LIGHTS)); +// } else if (effectMode == "Twinkle") { +// command.append(QString::number(FX_MODE_TWINKLE)); +// } else if (effectMode == "Twinkle Random") { +// command.append(QString::number(FX_MODE_TWINKLE_RANDOM)); +// } else if (effectMode == "Twinkle Fade") { +// command.append(QString::number(FX_MODE_TWINKLE_FADE)); +// } else if (effectMode == "Twinkle Fade Random") { +// command.append(QString::number(FX_MODE_TWINKLE_FADE_RANDOM)); +// } else if (effectMode == "Sparkle") { +// command.append(QString::number(FX_MODE_SPARKLE)); +// } else if (effectMode == "Flash Sparkle") { +// command.append(QString::number(FX_MODE_FLASH_SPARKLE)); +// } else if (effectMode == "Hyper Sparkle") { +// command.append(QString::number(FX_MODE_HYPER_SPARKLE)); +// } else if (effectMode == "Strobe") { +// command.append(QString::number(FX_MODE_STROBE)); +// } else if (effectMode == "Strobe Rainbow") { +// command.append(QString::number(FX_MODE_STROBE_RAINBOW)); +// } else if (effectMode == "Multi Strobe") { +// command.append(QString::number(FX_MODE_MULTI_STROBE)); +// } else if (effectMode == "Blink Rainbow") { +// command.append(QString::number(FX_MODE_BLINK_RAINBOW)); +// } else if (effectMode == "Chase White") { +// command.append(QString::number(FX_MODE_CHASE_WHITE)); +// } else if (effectMode == "Chase Color") { +// command.append(QString::number(FX_MODE_CHASE_COLOR)); +// } else if (effectMode == "Chase Random") { +// command.append(QString::number(FX_MODE_CHASE_RANDOM)); +// } else if (effectMode == "Chase Flash") { +// command.append(QString::number(FX_MODE_CHASE_FLASH)); +// } else if (effectMode == "Chase Flash Random") { +// command.append(QString::number(FX_MODE_CHASE_FLASH_RANDOM)); +// } else if (effectMode == "Chase Rainbow White") { +// command.append(QString::number(FX_MODE_CHASE_RAINBOW_WHITE)); +// } else if (effectMode == "Chase Blackout") { +// command.append(QString::number(FX_MODE_CHASE_BLACKOUT)); +// } else if (effectMode == "Chase Blackout Rainbow") { +// command.append(QString::number(FX_MODE_CHASE_BLACKOUT_RAINBOW)); +// } else if (effectMode == "Color Sweep Random") { +// command.append(QString::number(FX_MODE_COLOR_SWEEP_RANDOM)); +// } else if (effectMode == "Running Color") { +// command.append(QString::number(FX_MODE_RUNNING_COLOR)); +// } else if (effectMode == "Running Red Blue") { +// command.append(QString::number(FX_MODE_RUNNING_RED_BLUE)); +// } else if (effectMode == "Running Random") { +// command.append(QString::number(FX_MODE_RUNNING_RANDOM)); +// }else if (effectMode == "Larson Scanner") { +// command.append(QString::number(FX_MODE_LARSON_SCANNER)); +// }else if (effectMode == "Comet") { +// command.append(QString::number(FX_MODE_COMET)); +// }else if (effectMode == "Fireworks") { +// command.append(QString::number(FX_MODE_FIREWORKS)); +// }else if (effectMode == "Fireworks Random") { +// command.append(QString::number(FX_MODE_FIREWORKS_RANDOM)); +// }else if (effectMode == "Merry Christmas") { +// command.append(QString::number(FX_MODE_MERRY_CHRISTMAS)); +// }else if (effectMode == "Fire Flicker") { +// command.append(QString::number(FX_MODE_FIRE_FLICKER)); +// }else if (effectMode == "Fire Flicker (soft)") { +// command.append(QString::number(FX_MODE_FIRE_FLICKER_SOFT)); +// }else if (effectMode == "Fire Flicker (intense)") { +// command.append(QString::number(FX_MODE_FIRE_FLICKER_INTENSE)); +// }else if (effectMode == "Circus Combustus") { +// command.append(QString::number(FX_MODE_CIRCUS_COMBUSTUS)); +// }else if (effectMode == "Halloween") { +// command.append(QString::number(FX_MODE_HALLOWEEN)); +// }else if (effectMode == "Bicolor Chase") { +// command.append(QString::number(FX_MODE_BICOLOR_CHASE)); +// }else if (effectMode == "Tricolor Chase") { +// command.append(QString::number(FX_MODE_TRICOLOR_CHASE)); +// }else if (effectMode == "ICU") { +// command.append(QString::number(FX_MODE_ICU)); +// }else if (effectMode == "Custom 0") { +// command.append(QString::number(FX_MODE_CUSTOM_0)); +// }else if (effectMode == "Custom 1") { +// command.append(QString::number(FX_MODE_CUSTOM_1)); +// }else if (effectMode == "Custom 2") { +// command.append(QString::number(FX_MODE_CUSTOM_2)); +// }else if (effectMode == "Custom 3") { +// command.append(QString::number(FX_MODE_CUSTOM_3)); +// } +// command.append("\r\n"); +// return sendCommand(info, command, CommandType::Mode); +// } } @@ -306,122 +318,8 @@ void IntegrationPluginWs2812fx::thingRemoved(Thing *thing) if (thing->thingClassId() == ws2812fxThingClassId) { m_usedInterfaces.removeAll(thing->paramValue(ws2812fxThingSerialPortParamTypeId).toString()); - QSerialPort *serialPort = m_serialPorts.take(thing); - serialPort->flush(); - serialPort->close(); - serialPort->deleteLater(); - } - - if (myThings().empty()) { - m_reconnectTimer->stop(); - m_reconnectTimer->deleteLater(); + NymeaLight *light = m_lights.take(thing); + light->deleteLater(); } } -void IntegrationPluginWs2812fx::onReadyRead() -{ - QSerialPort *serialPort = static_cast(sender()); - Thing *thing = m_serialPorts.key(serialPort); - - QByteArray data; - while (serialPort->canReadLine()) { - data = serialPort->readLine(); - qDebug(dcWs2812fx()) << "Message received" << data; - - if (data.contains("mode")) { - if (m_pendingActions.contains(CommandType::Mode)) { - m_pendingActions.take(CommandType::Mode)->finish(Thing::ThingErrorNoError); - } - QString mode = data.split('-').at(1); - mode.remove(0, 1); - mode.remove("\r\n"); - qDebug(dcWs2812fx()) << "set mode to:" << mode; - thing->setStateValue(ws2812fxEffectModeStateTypeId, mode); - } - if (data.contains("brightness")) { - if (m_pendingActions.contains(CommandType::Brightness)) { - m_pendingActions.take(CommandType::Brightness)->finish(Thing::ThingErrorNoError); - } - QString rawBrightness = data.split(':').at(1); - rawBrightness.remove(" "); - rawBrightness.remove("\r\n"); - int brightness = rawBrightness.toInt(); - - qDebug(dcWs2812fx()) << "set brightness to:" << brightness; - thing->setStateValue(ws2812fxBrightnessStateTypeId, brightness); - if (brightness == 0) { - thing->setStateValue(ws2812fxPowerStateTypeId, false); - } else { - thing->setStateValue(ws2812fxPowerStateTypeId, true); - } - } - if (data.contains("speed")) { - if (m_pendingActions.contains(CommandType::Speed)) { - m_pendingActions.take(CommandType::Speed)->finish(Thing::ThingErrorNoError); - } - QString rawSpeed = data.split(':').at(1); - rawSpeed.remove(" "); - rawSpeed.remove("\r\n"); - int speed = data.split(':').at(1).toInt(); - - qDebug(dcWs2812fx()) << "set speed to:" << speed; - thing->setStateValue(ws2812fxSpeedStateTypeId, speed); - } - if (data.contains("color")) { - if (m_pendingActions.contains(CommandType::Color)) { - m_pendingActions.take(CommandType::Color)->finish(Thing::ThingErrorNoError); - } - QString rawColor = data.split(':').at(1); - rawColor.remove(" "); - rawColor.remove("0x"); - rawColor.remove("\r\n"); - rawColor.prepend("#"); - qDebug(dcWs2812fx()) << "set color to:" << rawColor; - thing->setStateValue(ws2812fxColorStateTypeId, rawColor); - } - } -} - -void IntegrationPluginWs2812fx::onReconnectTimer() -{ - foreach(Thing *thing, myThings()) { - if (!thing->stateValue(ws2812fxConnectedStateTypeId).toBool()) { - QSerialPort *serialPort = m_serialPorts.value(thing); - if (serialPort) { - if (serialPort->open(QSerialPort::ReadWrite)) { - thing->setStateValue(ws2812fxConnectedStateTypeId, true); - } else { - thing->setStateValue(ws2812fxConnectedStateTypeId, false); - m_reconnectTimer->start(); - } - } - } - } - -} - -void IntegrationPluginWs2812fx::onSerialError(QSerialPort::SerialPortError error) -{ - QSerialPort *serialPort = static_cast(sender()); - Thing *thing = m_serialPorts.key(serialPort); - - if (error != QSerialPort::NoError && serialPort->isOpen()) { - qCCritical(dcWs2812fx()) << "Serial port error:" << error << serialPort->errorString(); - m_reconnectTimer->start(); - serialPort->close(); - thing->setStateValue(ws2812fxConnectedStateTypeId, false); - } -} - -void IntegrationPluginWs2812fx::sendCommand(ThingActionInfo *info, const QByteArray &command, CommandType commandType) -{ - qDebug(dcWs2812fx()) << "Sending command" << command; - QSerialPort *serialPort = m_serialPorts.value(info->thing()); - if (!serialPort) - return info->finish(Thing::ThingErrorThingNotFound); - if (serialPort->write(command) != command.length()) { - qCWarning(dcWs2812fx) << "Error writing to serial port"; - return info->finish(Thing::ThingErrorHardwareFailure); - } - m_pendingActions.insert(commandType, info); -} diff --git a/ws2812fx/integrationpluginws2812fx.h b/ws2812fx/integrationpluginws2812fx.h index 28beb9705..139b06314 100644 --- a/ws2812fx/integrationpluginws2812fx.h +++ b/ws2812fx/integrationpluginws2812fx.h @@ -94,6 +94,7 @@ #define FX_MODE_CUSTOM_3 59 #include "integrations/integrationplugin.h" +#include "nymealight.h" #include #include @@ -115,24 +116,10 @@ class IntegrationPluginWs2812fx : public IntegrationPlugin void executeAction(ThingActionInfo *info) override; private: - enum CommandType { - Color, - Speed, - Brightness, - Mode - }; - - QHash m_serialPorts; + QHash m_lights; QList m_usedInterfaces; - QHash m_pendingActions; - - QTimer *m_reconnectTimer = nullptr; - void sendCommand(ThingActionInfo *info, const QByteArray &command, CommandType commandType); private slots: - void onReadyRead(); - void onReconnectTimer(); - void onSerialError(QSerialPort::SerialPortError error); signals: diff --git a/ws2812fx/ws2812fx.pro b/ws2812fx/ws2812fx.pro index 56e63728e..62c8a8737 100644 --- a/ws2812fx/ws2812fx.pro +++ b/ws2812fx/ws2812fx.pro @@ -6,7 +6,13 @@ TARGET = $$qtLibraryTarget(nymea_integrationpluginws2812fx) SOURCES += \ integrationpluginws2812fx.cpp \ + nymealight.cpp \ + nymealightinterface.cpp \ + nymealightserialinterface.cpp HEADERS += \ integrationpluginws2812fx.h \ + nymealight.h \ + nymealightinterface.h \ + nymealightserialinterface.h From b7b399d53ee45d02e4bab57d30dd3c24821ff572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 5 Feb 2021 17:08:26 +0100 Subject: [PATCH 2/4] Add nymea light classes --- ws2812fx/nymealight.cpp | 92 +++++++++++++++++ ws2812fx/nymealight.h | 41 ++++++++ ws2812fx/nymealightinterface.cpp | 6 ++ ws2812fx/nymealightinterface.h | 76 ++++++++++++++ ws2812fx/nymealightserialinterface.cpp | 133 +++++++++++++++++++++++++ ws2812fx/nymealightserialinterface.h | 39 ++++++++ 6 files changed, 387 insertions(+) create mode 100644 ws2812fx/nymealight.cpp create mode 100644 ws2812fx/nymealight.h create mode 100644 ws2812fx/nymealightinterface.cpp create mode 100644 ws2812fx/nymealightinterface.h create mode 100644 ws2812fx/nymealightserialinterface.cpp create mode 100644 ws2812fx/nymealightserialinterface.h diff --git a/ws2812fx/nymealight.cpp b/ws2812fx/nymealight.cpp new file mode 100644 index 000000000..7ccce8e19 --- /dev/null +++ b/ws2812fx/nymealight.cpp @@ -0,0 +1,92 @@ +#include "nymealight.h" +#include "extern-plugininfo.h" + +#include + +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(NymeaLightInterface::CommandSetColor); + stream << m_requestId++; + if (fadeDuration > 0) { + stream << static_cast(NymeaLightInterface::ModeFade); + } + stream << static_cast(color.red()); + stream << static_cast(color.green()); + stream << static_cast(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(data.at(0)); + quint8 requestId = static_cast(data.at(1)); + NymeaLightInterface::Status status = static_cast(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(); + } + +} + diff --git a/ws2812fx/nymealight.h b/ws2812fx/nymealight.h new file mode 100644 index 000000000..02e154eec --- /dev/null +++ b/ws2812fx/nymealight.h @@ -0,0 +1,41 @@ +#ifndef NYMEALIGHT_H +#define NYMEALIGHT_H + +#include +#include +#include + +#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 m_pendingRequests; + + NymeaLightInterfaceReply *createReply(const QByteArray &requestData); + void sendNextRequest(); + +private slots: + void onDataReceived(const QByteArray &data); + +signals: + void availableChanged(bool available); + +}; + + +#endif // NYMEALIGHT_H diff --git a/ws2812fx/nymealightinterface.cpp b/ws2812fx/nymealightinterface.cpp new file mode 100644 index 000000000..e80cfc33d --- /dev/null +++ b/ws2812fx/nymealightinterface.cpp @@ -0,0 +1,6 @@ +#include "nymealightinterface.h" + +NymeaLightInterface::NymeaLightInterface(QObject *parent) : QObject(parent) +{ + +} diff --git a/ws2812fx/nymealightinterface.h b/ws2812fx/nymealightinterface.h new file mode 100644 index 000000000..74c215b30 --- /dev/null +++ b/ws2812fx/nymealightinterface.h @@ -0,0 +1,76 @@ +#ifndef NYMEALIGHTINTERFACE_H +#define NYMEALIGHTINTERFACE_H + +#include + +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(m_requestData.at(0)); + m_requestId = static_cast(m_requestData.at(1)); + } + + QByteArray m_requestData; + NymeaLightInterface::Command m_command; + quint8 m_requestId; + NymeaLightInterface::Status m_status = NymeaLightInterface::StatusUnknownError; +}; + +#endif // NYMEALIGHTINTERFACE_H diff --git a/ws2812fx/nymealightserialinterface.cpp b/ws2812fx/nymealightserialinterface.cpp new file mode 100644 index 000000000..ad6126ecb --- /dev/null +++ b/ws2812fx/nymealightserialinterface.cpp @@ -0,0 +1,133 @@ +#include "nymealightserialinterface.h" +#include "extern-plugininfo.h" + +#include + +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(SlipProtocolEsc); + stream << static_cast(SlipProtocolTransposedEnd); + break; + case SlipProtocolEsc: + stream << static_cast(SlipProtocolEsc); + stream << static_cast(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(SlipProtocolEnd)); + m_protocolEscaping = false; + break; + case SlipProtocolTransposedEsc: + m_buffer.append(static_cast(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); + } +} diff --git a/ws2812fx/nymealightserialinterface.h b/ws2812fx/nymealightserialinterface.h new file mode 100644 index 000000000..d7fe34212 --- /dev/null +++ b/ws2812fx/nymealightserialinterface.h @@ -0,0 +1,39 @@ +#ifndef NYMEALIGHTSERIALINTERFACE_H +#define NYMEALIGHTSERIALINTERFACE_H + +#include +#include +#include + +#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 From 7f8c6977ceccfeb963780c30c5c8a2e172a41425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 17 Feb 2021 13:15:06 +0100 Subject: [PATCH 3/4] Update ws2812fx behaviour and test firmware --- ws2812fx/integrationpluginws2812fx.cpp | 386 +++++++++++++----------- ws2812fx/integrationpluginws2812fx.json | 4 +- ws2812fx/nymealight.cpp | 88 +++++- ws2812fx/nymealight.h | 3 + ws2812fx/nymealightinterface.h | 16 +- ws2812fx/nymealightserialinterface.cpp | 13 +- ws2812fx/nymealightserialinterface.h | 1 + 7 files changed, 313 insertions(+), 198 deletions(-) diff --git a/ws2812fx/integrationpluginws2812fx.cpp b/ws2812fx/integrationpluginws2812fx.cpp index 93ae75152..61620e0a5 100644 --- a/ws2812fx/integrationpluginws2812fx.cpp +++ b/ws2812fx/integrationpluginws2812fx.cpp @@ -45,6 +45,8 @@ s \quotefile plugins/deviceplugins/ws2812fx/devicepluginws2812fx.json */ #include +#include + #include "integrationpluginws2812fx.h" #include "plugininfo.h" @@ -120,7 +122,28 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) return; } - if (action.actionTypeId() == ws2812fxColorActionTypeId) { + if (action.actionTypeId() == ws2812fxPowerActionTypeId) { + bool power = action.param(ws2812fxPowerActionPowerParamTypeId).value().toBool(); + quint8 brightness = 0; + if (power) { + quint8 brightnessPercentage = thing->stateValue(ws2812fxBrightnessStateTypeId).toUInt(); + brightness = qRound(255.0 * brightnessPercentage / 100); + } + + qCDebug(dcWs2812fx()) << "Set power" << power; + NymeaLightInterfaceReply *reply = light->setBrightness(brightness, 500); + connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished); + connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){ + if (reply->status() != NymeaLightInterface::StatusSuccess) { + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + qCDebug(dcWs2812fx()) << "Set power finished successfully" << power; + thing->setStateValue(ws2812fxPowerStateTypeId, power); + info->finish(Thing::ThingErrorNoError); + }); + } else if (action.actionTypeId() == ws2812fxColorActionTypeId) { QColor color = action.param(ws2812fxColorActionColorParamTypeId).value().value(); qCDebug(dcWs2812fx()) << "Set color to" << color.name(QColor::HexRgb); NymeaLightInterfaceReply *reply = light->setColor(color); @@ -134,182 +157,193 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) thing->setStateValue(ws2812fxColorStateTypeId, color); info->finish(Thing::ThingErrorNoError); }); - } + } else if (action.actionTypeId() == ws2812fxColorTemperatureActionTypeId) { + // minValue 153, maxValue 500 + uint colorTemperature = action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value().toDouble(); + QColor color; + color.setRgb(255, 255, qRound((255.00 - (((colorTemperature - 153.00) / 347.00)) * 255.00))); + + qCDebug(dcWs2812fx()) << "Set color temperature" << colorTemperature << color.name(QColor::HexRgb); + NymeaLightInterfaceReply *reply = light->setColor(color); + connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished); + connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){ + if (reply->status() != NymeaLightInterface::StatusSuccess) { + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + qCDebug(dcWs2812fx()) << "Set color temperature finished successfully" << colorTemperature; + thing->setStateValue(ws2812fxColorTemperatureStateTypeId, colorTemperature); + info->finish(Thing::ThingErrorNoError); + }); + } else if (action.actionTypeId() == ws2812fxBrightnessActionTypeId) { + quint8 brightnessPercentage = action.param(ws2812fxBrightnessActionBrightnessParamTypeId).value().toUInt(); + quint8 brightness = qRound(255.0 * brightnessPercentage / 100); + qCDebug(dcWs2812fx()) << "Set brightness to" << brightnessPercentage << brightness; + NymeaLightInterfaceReply *reply = light->setBrightness(brightness, 1000); + connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished); + connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){ + if (reply->status() != NymeaLightInterface::StatusSuccess) { + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + qCDebug(dcWs2812fx()) << "Set brightness finished successfully" << brightness; + thing->setStateValue(ws2812fxBrightnessStateTypeId, brightnessPercentage); + info->finish(Thing::ThingErrorNoError); + }); + } else if (action.actionTypeId() == ws2812fxSpeedActionTypeId) { + quint16 speedPercentage = action.param(ws2812fxSpeedActionSpeedParamTypeId).value().toUInt(); + quint16 speed = 2000 - (speedPercentage * 20); + + qCDebug(dcWs2812fx()) << "Set speed" << speedPercentage << "%" << speed << "ms"; + NymeaLightInterfaceReply *reply = light->setSpeed(speed); + connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished); + connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){ + if (reply->status() != NymeaLightInterface::StatusSuccess) { + info->finish(Thing::ThingErrorHardwareFailure); + return; + } -// QByteArray command; -// if (action.actionTypeId() == ws2812fxPowerActionTypeId) { -// command.append("b "); -// if (action.param(ws2812fxPowerActionPowerParamTypeId).value().toBool()) { -// command.append("30"); -// } else { -// command.append("0"); -// } -// command.append("\r\n"); -// return sendCommand(info, command, CommandType::Brightness); -// } - -// if (action.actionTypeId() == ws2812fxBrightnessActionTypeId) { - -// command.append("b "); -// command.append(action.param(ws2812fxBrightnessActionBrightnessParamTypeId).value().toString()); -// command.append("\r\n"); -// return sendCommand(info, command, CommandType::Brightness); -// } - -// if (action.actionTypeId() == ws2812fxSpeedActionTypeId) { - -// command.append("s "); -// command.append(action.param(ws2812fxSpeedActionSpeedParamTypeId).value().toString()); -// command.append("\r\n"); -// return sendCommand(info, command, CommandType::Speed); -// } - -// if (action.actionTypeId() == ws2812fxColorActionTypeId) { - -// QColor color; -// color= action.param(ws2812fxColorActionColorParamTypeId).value().value(); -// command.append("c "); -// command.append(QString(color.name()).remove("#")); -// command.append("\r\n"); -// return sendCommand(info, command, CommandType::Color); -// } - -// if (action.actionTypeId() == ws2812fxColorTemperatureActionTypeId) { - -// // minValue 153, maxValue 500 -// QColor color; -// color.setRgb(255, 255, static_cast((255.00-(((action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value().toDouble()-153.00)/347.00))*255.00))); -// thing->setStateValue(ws2812fxColorTemperatureStateTypeId, action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value()); -// command.append("c "); -// command.append(QString(color.name()).remove("#")); -// command.append("\r\n"); -// return sendCommand(info, command, CommandType::Color); -// } - -// if (action.actionTypeId() == ws2812fxEffectModeActionTypeId) { - -// QString effectMode = action.param(ws2812fxEffectModeActionEffectModeParamTypeId).value().toString(); -// command.append("m "); -// if (effectMode == "Static") { -// command.append(QString::number(FX_MODE_STATIC)); -// } else if (effectMode == "Blink") { -// command.append(QString::number(FX_MODE_BLINK)); -// } else if (effectMode == "Color Wipe") { -// command.append(QString::number(FX_MODE_COLOR_WIPE)); -// } else if (effectMode == "Color Wipe Inverse") { -// command.append(QString::number(FX_MODE_COLOR_WIPE_INV)); -// } else if (effectMode == "Color Wipe Reverse") { -// command.append(QString::number(FX_MODE_COLOR_WIPE_REV)); -// } else if (effectMode == "Color Wipe Reverse Inverse") { -// command.append(QString::number(FX_MODE_COLOR_WIPE_REV_INV)); -// } else if (effectMode == "Color Wipe Random") { -// command.append(QString::number(FX_MODE_COLOR_WIPE_RANDOM)); -// } else if (effectMode == "Random Color") { -// command.append(QString::number(FX_MODE_RANDOM_COLOR)); -// } else if (effectMode == "Single Dynamic") { -// command.append(QString::number(FX_MODE_SINGLE_DYNAMIC)); -// } else if (effectMode == "Multi Dynamic") { -// command.append(QString::number(FX_MODE_MULTI_DYNAMIC)); -// } else if (effectMode == "Rainbow") { -// command.append(QString::number(FX_MODE_RAINBOW)); -// } else if (effectMode == "Rainbow Cycle") { -// command.append(QString::number(FX_MODE_RAINBOW_CYCLE)); -// } else if (effectMode == "Scan") { -// command.append(QString::number(FX_MODE_SCAN)); -// } else if (effectMode == "Dual Scan") { -// command.append(QString::number(FX_MODE_DUAL_SCAN)); -// } else if (effectMode == "Fade") { -// command.append(QString::number(FX_MODE_FADE)); -// } else if (effectMode == "Theater Chase") { -// command.append(QString::number(FX_MODE_THEATER_CHASE)); -// } else if (effectMode == "Theater Chase Rainbow") { -// command.append(QString::number(FX_MODE_THEATER_CHASE_RAINBOW)); -// } else if (effectMode == "Running Lights") { -// command.append(QString::number(FX_MODE_RUNNING_LIGHTS)); -// } else if (effectMode == "Twinkle") { -// command.append(QString::number(FX_MODE_TWINKLE)); -// } else if (effectMode == "Twinkle Random") { -// command.append(QString::number(FX_MODE_TWINKLE_RANDOM)); -// } else if (effectMode == "Twinkle Fade") { -// command.append(QString::number(FX_MODE_TWINKLE_FADE)); -// } else if (effectMode == "Twinkle Fade Random") { -// command.append(QString::number(FX_MODE_TWINKLE_FADE_RANDOM)); -// } else if (effectMode == "Sparkle") { -// command.append(QString::number(FX_MODE_SPARKLE)); -// } else if (effectMode == "Flash Sparkle") { -// command.append(QString::number(FX_MODE_FLASH_SPARKLE)); -// } else if (effectMode == "Hyper Sparkle") { -// command.append(QString::number(FX_MODE_HYPER_SPARKLE)); -// } else if (effectMode == "Strobe") { -// command.append(QString::number(FX_MODE_STROBE)); -// } else if (effectMode == "Strobe Rainbow") { -// command.append(QString::number(FX_MODE_STROBE_RAINBOW)); -// } else if (effectMode == "Multi Strobe") { -// command.append(QString::number(FX_MODE_MULTI_STROBE)); -// } else if (effectMode == "Blink Rainbow") { -// command.append(QString::number(FX_MODE_BLINK_RAINBOW)); -// } else if (effectMode == "Chase White") { -// command.append(QString::number(FX_MODE_CHASE_WHITE)); -// } else if (effectMode == "Chase Color") { -// command.append(QString::number(FX_MODE_CHASE_COLOR)); -// } else if (effectMode == "Chase Random") { -// command.append(QString::number(FX_MODE_CHASE_RANDOM)); -// } else if (effectMode == "Chase Flash") { -// command.append(QString::number(FX_MODE_CHASE_FLASH)); -// } else if (effectMode == "Chase Flash Random") { -// command.append(QString::number(FX_MODE_CHASE_FLASH_RANDOM)); -// } else if (effectMode == "Chase Rainbow White") { -// command.append(QString::number(FX_MODE_CHASE_RAINBOW_WHITE)); -// } else if (effectMode == "Chase Blackout") { -// command.append(QString::number(FX_MODE_CHASE_BLACKOUT)); -// } else if (effectMode == "Chase Blackout Rainbow") { -// command.append(QString::number(FX_MODE_CHASE_BLACKOUT_RAINBOW)); -// } else if (effectMode == "Color Sweep Random") { -// command.append(QString::number(FX_MODE_COLOR_SWEEP_RANDOM)); -// } else if (effectMode == "Running Color") { -// command.append(QString::number(FX_MODE_RUNNING_COLOR)); -// } else if (effectMode == "Running Red Blue") { -// command.append(QString::number(FX_MODE_RUNNING_RED_BLUE)); -// } else if (effectMode == "Running Random") { -// command.append(QString::number(FX_MODE_RUNNING_RANDOM)); -// }else if (effectMode == "Larson Scanner") { -// command.append(QString::number(FX_MODE_LARSON_SCANNER)); -// }else if (effectMode == "Comet") { -// command.append(QString::number(FX_MODE_COMET)); -// }else if (effectMode == "Fireworks") { -// command.append(QString::number(FX_MODE_FIREWORKS)); -// }else if (effectMode == "Fireworks Random") { -// command.append(QString::number(FX_MODE_FIREWORKS_RANDOM)); -// }else if (effectMode == "Merry Christmas") { -// command.append(QString::number(FX_MODE_MERRY_CHRISTMAS)); -// }else if (effectMode == "Fire Flicker") { -// command.append(QString::number(FX_MODE_FIRE_FLICKER)); -// }else if (effectMode == "Fire Flicker (soft)") { -// command.append(QString::number(FX_MODE_FIRE_FLICKER_SOFT)); -// }else if (effectMode == "Fire Flicker (intense)") { -// command.append(QString::number(FX_MODE_FIRE_FLICKER_INTENSE)); -// }else if (effectMode == "Circus Combustus") { -// command.append(QString::number(FX_MODE_CIRCUS_COMBUSTUS)); -// }else if (effectMode == "Halloween") { -// command.append(QString::number(FX_MODE_HALLOWEEN)); -// }else if (effectMode == "Bicolor Chase") { -// command.append(QString::number(FX_MODE_BICOLOR_CHASE)); -// }else if (effectMode == "Tricolor Chase") { -// command.append(QString::number(FX_MODE_TRICOLOR_CHASE)); -// }else if (effectMode == "ICU") { -// command.append(QString::number(FX_MODE_ICU)); -// }else if (effectMode == "Custom 0") { -// command.append(QString::number(FX_MODE_CUSTOM_0)); -// }else if (effectMode == "Custom 1") { -// command.append(QString::number(FX_MODE_CUSTOM_1)); -// }else if (effectMode == "Custom 2") { -// command.append(QString::number(FX_MODE_CUSTOM_2)); -// }else if (effectMode == "Custom 3") { -// command.append(QString::number(FX_MODE_CUSTOM_3)); -// } -// command.append("\r\n"); -// return sendCommand(info, command, CommandType::Mode); -// } + qCDebug(dcWs2812fx()) << "Set speed finished successfully" << speedPercentage << "%" << speed << "ms"; + thing->setStateValue(ws2812fxSpeedStateTypeId, speedPercentage); + info->finish(Thing::ThingErrorNoError); + }); + } else if (action.actionTypeId() == ws2812fxEffectModeActionTypeId) { + QString effectMode = action.param(ws2812fxEffectModeActionEffectModeParamTypeId).value().toString(); + quint8 mode = FX_MODE_STATIC; + if (effectMode == "Static") { + mode = FX_MODE_STATIC; + } else if (effectMode == "Blink") { + mode = FX_MODE_BLINK; + } else if (effectMode == "Color Wipe") { + mode = FX_MODE_COLOR_WIPE; + } else if (effectMode == "Color Wipe Inverse") { + mode = FX_MODE_COLOR_WIPE_INV; + } else if (effectMode == "Color Wipe Reverse") { + mode = FX_MODE_COLOR_WIPE_REV; + } else if (effectMode == "Color Wipe Reverse Inverse") { + mode = FX_MODE_COLOR_WIPE_REV_INV; + } else if (effectMode == "Color Wipe Random") { + mode = FX_MODE_COLOR_WIPE_RANDOM; + } else if (effectMode == "Random Color") { + mode = FX_MODE_RANDOM_COLOR; + } else if (effectMode == "Single Dynamic") { + mode = FX_MODE_SINGLE_DYNAMIC; + } else if (effectMode == "Multi Dynamic") { + mode = FX_MODE_MULTI_DYNAMIC; + } else if (effectMode == "Rainbow") { + mode = FX_MODE_RAINBOW; + } else if (effectMode == "Rainbow Cycle") { + mode = FX_MODE_RAINBOW_CYCLE; + } else if (effectMode == "Scan") { + mode = FX_MODE_SCAN; + } else if (effectMode == "Dual Scan") { + mode = FX_MODE_DUAL_SCAN; + } else if (effectMode == "Fade") { + mode = FX_MODE_FADE; + } else if (effectMode == "Theater Chase") { + mode = FX_MODE_THEATER_CHASE; + } else if (effectMode == "Theater Chase Rainbow") { + mode = FX_MODE_THEATER_CHASE_RAINBOW; + } else if (effectMode == "Running Lights") { + mode = FX_MODE_RUNNING_LIGHTS; + } else if (effectMode == "Twinkle") { + mode = FX_MODE_TWINKLE; + } else if (effectMode == "Twinkle Random") { + mode = FX_MODE_TWINKLE_RANDOM; + } else if (effectMode == "Twinkle Fade") { + mode = FX_MODE_TWINKLE_FADE; + } else if (effectMode == "Twinkle Fade Random") { + mode = FX_MODE_TWINKLE_FADE_RANDOM; + } else if (effectMode == "Sparkle") { + mode = FX_MODE_SPARKLE; + } else if (effectMode == "Flash Sparkle") { + mode = FX_MODE_FLASH_SPARKLE; + } else if (effectMode == "Hyper Sparkle") { + mode = FX_MODE_HYPER_SPARKLE; + } else if (effectMode == "Strobe") { + mode = FX_MODE_STROBE; + } else if (effectMode == "Strobe Rainbow") { + mode = FX_MODE_STROBE_RAINBOW; + } else if (effectMode == "Multi Strobe") { + mode = FX_MODE_MULTI_STROBE; + } else if (effectMode == "Blink Rainbow") { + mode = FX_MODE_BLINK_RAINBOW; + } else if (effectMode == "Chase White") { + mode = FX_MODE_CHASE_WHITE; + } else if (effectMode == "Chase Color") { + mode = FX_MODE_CHASE_COLOR; + } else if (effectMode == "Chase Random") { + mode = FX_MODE_CHASE_RANDOM; + } else if (effectMode == "Chase Flash") { + mode = FX_MODE_CHASE_FLASH; + } else if (effectMode == "Chase Flash Random") { + mode = FX_MODE_CHASE_FLASH_RANDOM; + } else if (effectMode == "Chase Rainbow White") { + mode = FX_MODE_CHASE_RAINBOW_WHITE; + } else if (effectMode == "Chase Blackout") { + mode = FX_MODE_CHASE_BLACKOUT; + } else if (effectMode == "Chase Blackout Rainbow") { + mode = FX_MODE_CHASE_BLACKOUT_RAINBOW; + } else if (effectMode == "Color Sweep Random") { + mode = FX_MODE_COLOR_SWEEP_RANDOM; + } else if (effectMode == "Running Color") { + mode = FX_MODE_RUNNING_COLOR; + } else if (effectMode == "Running Red Blue") { + mode = FX_MODE_RUNNING_RED_BLUE; + } else if (effectMode == "Running Random") { + mode = FX_MODE_RUNNING_RANDOM; + }else if (effectMode == "Larson Scanner") { + mode = FX_MODE_LARSON_SCANNER; + }else if (effectMode == "Comet") { + mode = FX_MODE_COMET; + }else if (effectMode == "Fireworks") { + mode = FX_MODE_FIREWORKS; + }else if (effectMode == "Fireworks Random") { + mode = FX_MODE_FIREWORKS_RANDOM; + }else if (effectMode == "Merry Christmas") { + mode = FX_MODE_MERRY_CHRISTMAS; + }else if (effectMode == "Fire Flicker") { + mode = FX_MODE_FIRE_FLICKER; + }else if (effectMode == "Fire Flicker (soft)") { + mode = FX_MODE_FIRE_FLICKER_SOFT; + }else if (effectMode == "Fire Flicker (intense)") { + mode = FX_MODE_FIRE_FLICKER_INTENSE; + }else if (effectMode == "Circus Combustus") { + mode = FX_MODE_CIRCUS_COMBUSTUS; + }else if (effectMode == "Halloween") { + mode = FX_MODE_HALLOWEEN; + }else if (effectMode == "Bicolor Chase") { + mode = FX_MODE_BICOLOR_CHASE; + }else if (effectMode == "Tricolor Chase") { + mode = FX_MODE_TRICOLOR_CHASE; + }else if (effectMode == "ICU") { + mode = FX_MODE_ICU; + }else if (effectMode == "Custom 0") { + mode = FX_MODE_CUSTOM_0; + }else if (effectMode == "Custom 1") { + mode = FX_MODE_CUSTOM_1; + }else if (effectMode == "Custom 2") { + mode = FX_MODE_CUSTOM_2; + }else if (effectMode == "Custom 3") { + mode = FX_MODE_CUSTOM_3; + } + + qCDebug(dcWs2812fx()) << "Set mode" << effectMode << mode; + NymeaLightInterfaceReply *reply = light->setEffect(mode); + connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished); + connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){ + if (reply->status() != NymeaLightInterface::StatusSuccess) { + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + qCDebug(dcWs2812fx()) << "Set mode finished successfully" << effectMode << mode; + thing->setStateValue(ws2812fxEffectModeStateTypeId, effectMode); + info->finish(Thing::ThingErrorNoError); + }); + } } diff --git a/ws2812fx/integrationpluginws2812fx.json b/ws2812fx/integrationpluginws2812fx.json index 07cbe7d70..c7de2fd6a 100644 --- a/ws2812fx/integrationpluginws2812fx.json +++ b/ws2812fx/integrationpluginws2812fx.json @@ -100,6 +100,7 @@ "displayNameAction": "Set effect mode", "type": "QString", "defaultValue": "Static", + "writable": true, "possibleValues": [ "Static", "Blink", @@ -161,8 +162,7 @@ "Custom 1", "Custom 2", "Custom 3" - ], - "writable": true + ] } ] } diff --git a/ws2812fx/nymealight.cpp b/ws2812fx/nymealight.cpp index 7ccce8e19..a92d11b55 100644 --- a/ws2812fx/nymealight.cpp +++ b/ws2812fx/nymealight.cpp @@ -8,8 +8,8 @@ NymeaLight::NymeaLight(NymeaLightInterface *interface, QObject *parent) : m_interface(interface) { connect(m_interface, &NymeaLightInterface::availableChanged, this, [=](bool available){ - qCDebug(dcWs2812fx()) << "Interface available changed" << available; - emit availableChanged(available); + qCDebug(dcWs2812fx()) << "Interface available changed" << available; + emit availableChanged(available); }); connect(m_interface, &NymeaLightInterface::dataReceived, this, &NymeaLight::onDataReceived); @@ -22,20 +22,61 @@ NymeaLightInterfaceReply *NymeaLight::setColor(const QColor &color, quint16 fade QDataStream stream(&requestData, QIODevice::WriteOnly); stream << static_cast(NymeaLightInterface::CommandSetColor); stream << m_requestId++; - if (fadeDuration > 0) { - stream << static_cast(NymeaLightInterface::ModeFade); - } + stream << fadeDuration; stream << static_cast(color.red()); stream << static_cast(color.green()); stream << static_cast(color.blue()); - if (fadeDuration > 0) { - stream << fadeDuration; - } NymeaLightInterfaceReply *reply = createReply(requestData); m_pendingRequests.enqueue(reply); sendNextRequest(); + return reply; +} + +NymeaLightInterfaceReply *NymeaLight::setBrightness(quint8 brightness, quint16 fadeDuration) +{ + // Build the request + QByteArray requestData; + QDataStream stream(&requestData, QIODevice::WriteOnly); + stream << static_cast(NymeaLightInterface::CommandSetBrightness); + stream << m_requestId++; + stream << fadeDuration; + stream << static_cast(brightness); + + NymeaLightInterfaceReply *reply = createReply(requestData); + m_pendingRequests.enqueue(reply); + sendNextRequest(); + return reply; +} + +NymeaLightInterfaceReply *NymeaLight::setSpeed(quint16 speed, quint16 fadeDuration) +{ + // Build the request + QByteArray requestData; + QDataStream stream(&requestData, QIODevice::WriteOnly); + stream << static_cast(NymeaLightInterface::CommandSetSpeed); + stream << m_requestId++; + stream << fadeDuration; + stream << speed; + + NymeaLightInterfaceReply *reply = createReply(requestData); + m_pendingRequests.enqueue(reply); + sendNextRequest(); + return reply; +} + +NymeaLightInterfaceReply *NymeaLight::setEffect(quint8 effect) +{ + // Build the request + QByteArray requestData; + QDataStream stream(&requestData, QIODevice::WriteOnly); + stream << static_cast(NymeaLightInterface::CommandSetEffect); + stream << m_requestId++; + stream << effect; + NymeaLightInterfaceReply *reply = createReply(requestData); + m_pendingRequests.enqueue(reply); + sendNextRequest(); return reply; } @@ -47,7 +88,13 @@ bool NymeaLight::available() const NymeaLightInterfaceReply *NymeaLight::createReply(const QByteArray &requestData) { NymeaLightInterfaceReply *reply = new NymeaLightInterfaceReply(requestData, this); - connect(reply, &NymeaLightInterfaceReply::finished, reply, &NymeaLightInterfaceReply::deleteLater); + connect(reply, &NymeaLightInterfaceReply::finished, reply, [=](){ + reply->deleteLater(); + if (reply == m_currentReply) { + m_currentReply = nullptr; + } + sendNextRequest(); + }); return reply; } @@ -64,25 +111,36 @@ void NymeaLight::sendNextRequest() 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()); + m_currentReply->m_timer->start(); } void NymeaLight::onDataReceived(const QByteArray &data) { - qCDebug(dcWs2812fx()) << "Recived data" << data; Q_ASSERT(data.length() >= 3); - NymeaLightInterface::Command command = static_cast(data.at(0)); + NymeaLightInterface::Command command = static_cast(static_cast((data.at(0)))); quint8 requestId = static_cast(data.at(1)); + //qCDebug(dcWs2812fx()) << "Recived data" << command << requestId << data.toHex(); + + if (command == NymeaLightInterface::CommandDebug) { + qCDebug(dcWs2812fx()) << "Firmware debug:" << QString::fromUtf8(data.right(data.length() - 2)); + return; + } + NymeaLightInterface::Status status = static_cast(data.at(2)); if (m_currentReply) { if (m_currentReply->command() == command && m_currentReply->requestId() == requestId) { + m_currentReply->m_timer->stop(); m_currentReply->m_status = status; - qCDebug(dcWs2812fx()) << "Request finished" << command << m_currentReply->requestId() << status; - emit m_currentReply->finished(); - m_currentReply = nullptr; - sendNextRequest(); + if (status != NymeaLightInterface::StatusSuccess) { + qCWarning(dcWs2812fx()) << "Request finished with error" << command << m_currentReply->requestId() << status; + } else { + qCDebug(dcWs2812fx()) << "Request finished" << command << m_currentReply->requestId() << status; + } + + emit m_currentReply->finished(); } } else { qCWarning(dcWs2812fx()) << "Received unhandled data" << data.toHex(); diff --git a/ws2812fx/nymealight.h b/ws2812fx/nymealight.h index 02e154eec..9f5584c76 100644 --- a/ws2812fx/nymealight.h +++ b/ws2812fx/nymealight.h @@ -16,6 +16,9 @@ class NymeaLight : public QObject // 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); + NymeaLightInterfaceReply *setBrightness(quint8 brightness, quint16 fadeDuration = 0); + NymeaLightInterfaceReply *setSpeed(quint16 speed, quint16 fadeDuration = 0); + NymeaLightInterfaceReply *setEffect(quint8 effect); bool available() const; diff --git a/ws2812fx/nymealightinterface.h b/ws2812fx/nymealightinterface.h index 74c215b30..48dc0399c 100644 --- a/ws2812fx/nymealightinterface.h +++ b/ws2812fx/nymealightinterface.h @@ -2,6 +2,7 @@ #define NYMEALIGHTINTERFACE_H #include +#include class NymeaLightInterface : public QObject { @@ -12,6 +13,7 @@ class NymeaLightInterface : public QObject CommandSetBrightness = 0x01, CommandSetSpeed = 0x02, CommandSetEffect = 0x03, + CommandDebug = 0xFE, CommandCustom = 0xFF }; Q_ENUM(Command) @@ -21,6 +23,7 @@ class NymeaLightInterface : public QObject StatusInvalidProtocol = 0x01, StatusInvalidCommand = 0x02, StatusInvalidPlayload = 0x03, + StatusTimeout = 0xfe, StatusUnknownError = 0xff }; Q_ENUM(Status) @@ -45,6 +48,7 @@ class NymeaLightInterface : public QObject }; + class NymeaLightInterfaceReply : public QObject { Q_OBJECT @@ -62,14 +66,24 @@ class NymeaLightInterfaceReply : public QObject private: explicit NymeaLightInterfaceReply(const QByteArray &requestData, QObject *parent = nullptr) : QObject(parent), m_requestData(requestData) { + Q_ASSERT(m_requestData.length() >= 2); m_command = static_cast(m_requestData.at(0)); m_requestId = static_cast(m_requestData.at(1)); + + m_timer = new QTimer(this); + m_timer->setInterval(2000); + m_timer->setSingleShot(true); + connect(m_timer, &QTimer::timeout, this, [=](){ + m_status = NymeaLightInterface::StatusTimeout; + emit finished(); + }); } + QTimer *m_timer = nullptr; QByteArray m_requestData; NymeaLightInterface::Command m_command; - quint8 m_requestId; + quint8 m_requestId = 0; NymeaLightInterface::Status m_status = NymeaLightInterface::StatusUnknownError; }; diff --git a/ws2812fx/nymealightserialinterface.cpp b/ws2812fx/nymealightserialinterface.cpp index ad6126ecb..0ab8e928d 100644 --- a/ws2812fx/nymealightserialinterface.cpp +++ b/ws2812fx/nymealightserialinterface.cpp @@ -56,10 +56,10 @@ bool NymeaLightSerialInterface::available() void NymeaLightSerialInterface::sendData(const QByteArray &data) { // Stream bytes using SLIP - qCDebug(dcWs2812fx()) << "Write data" << data.toHex(); - QByteArray message; QDataStream stream(&message, QIODevice::WriteOnly); + stream << static_cast(SlipProtocolEnd); + for (int i = 0; i < data.length(); i++) { quint8 dataByte = data.at(i); switch (dataByte) { @@ -76,6 +76,9 @@ void NymeaLightSerialInterface::sendData(const QByteArray &data) break; } } + stream << static_cast(SlipProtocolEnd); + + qCDebug(dcWs2812fx()) << "UART -->" << message.toHex(); m_serialPort->write(message); m_serialPort->flush(); } @@ -83,7 +86,6 @@ void NymeaLightSerialInterface::sendData(const QByteArray &data) 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); @@ -106,7 +108,10 @@ void NymeaLightSerialInterface::onReadyRead() switch (receivedByte) { case SlipProtocolEnd: // We are done with this package, process it and reset the buffer - emit dataReceived(m_buffer); + if (!m_buffer.isEmpty() && m_buffer.length() >= 3) { + qCDebug(dcWs2812fx()) << "UART <--" << m_buffer.toHex(); + emit dataReceived(m_buffer); + } m_buffer.clear(); m_protocolEscaping = false; break; diff --git a/ws2812fx/nymealightserialinterface.h b/ws2812fx/nymealightserialinterface.h index d7fe34212..2def124a1 100644 --- a/ws2812fx/nymealightserialinterface.h +++ b/ws2812fx/nymealightserialinterface.h @@ -34,6 +34,7 @@ class NymeaLightSerialInterface : public NymeaLightInterface private slots: void onReadyRead(); void onSerialError(QSerialPort::SerialPortError error); + }; #endif // NYMEALIGHTSERIALINTERFACE_H From 5b766a406ae90993b623ac79a14fa304c8bda064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sat, 1 May 2021 12:45:33 +0200 Subject: [PATCH 4/4] Add firmware and first working version --- ws2812fx/firmware/include/README | 39 +++ ws2812fx/firmware/lib/Adafruit_NeoPixel | 1 + ws2812fx/firmware/lib/README | 46 ++++ ws2812fx/firmware/lib/WS2812FX | 1 + ws2812fx/firmware/platformio.ini | 14 + ws2812fx/firmware/src/main.cpp | 20 ++ ws2812fx/firmware/src/nymealight.cpp | 330 ++++++++++++++++++++++++ ws2812fx/firmware/src/nymealight.h | 166 ++++++++++++ ws2812fx/firmware/test/README | 11 + ws2812fx/integrationpluginws2812fx.cpp | 132 +++++----- ws2812fx/integrationpluginws2812fx.h | 1 + ws2812fx/integrationpluginws2812fx.json | 12 +- ws2812fx/nymealight.cpp | 137 ++++++++-- ws2812fx/nymealight.h | 14 + ws2812fx/nymealightinterface.cpp | 6 - ws2812fx/nymealightinterface.h | 22 +- ws2812fx/nymealightserialinterface.cpp | 25 +- ws2812fx/nymealightserialinterface.h | 6 +- ws2812fx/ws2812fx.pro | 1 - 19 files changed, 867 insertions(+), 117 deletions(-) create mode 100644 ws2812fx/firmware/include/README create mode 160000 ws2812fx/firmware/lib/Adafruit_NeoPixel create mode 100644 ws2812fx/firmware/lib/README create mode 160000 ws2812fx/firmware/lib/WS2812FX create mode 100644 ws2812fx/firmware/platformio.ini create mode 100644 ws2812fx/firmware/src/main.cpp create mode 100644 ws2812fx/firmware/src/nymealight.cpp create mode 100644 ws2812fx/firmware/src/nymealight.h create mode 100644 ws2812fx/firmware/test/README delete mode 100644 ws2812fx/nymealightinterface.cpp diff --git a/ws2812fx/firmware/include/README b/ws2812fx/firmware/include/README new file mode 100644 index 000000000..194dcd432 --- /dev/null +++ b/ws2812fx/firmware/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/ws2812fx/firmware/lib/Adafruit_NeoPixel b/ws2812fx/firmware/lib/Adafruit_NeoPixel new file mode 160000 index 000000000..8595ee3f5 --- /dev/null +++ b/ws2812fx/firmware/lib/Adafruit_NeoPixel @@ -0,0 +1 @@ +Subproject commit 8595ee3f5880d5096d71551c621d7a2d3818f974 diff --git a/ws2812fx/firmware/lib/README b/ws2812fx/firmware/lib/README new file mode 100644 index 000000000..6debab1e8 --- /dev/null +++ b/ws2812fx/firmware/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/ws2812fx/firmware/lib/WS2812FX b/ws2812fx/firmware/lib/WS2812FX new file mode 160000 index 000000000..4774866a1 --- /dev/null +++ b/ws2812fx/firmware/lib/WS2812FX @@ -0,0 +1 @@ +Subproject commit 4774866a176eb4ff474f712ce63a06404385e911 diff --git a/ws2812fx/firmware/platformio.ini b/ws2812fx/firmware/platformio.ini new file mode 100644 index 000000000..ea23b772d --- /dev/null +++ b/ws2812fx/firmware/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:uno] +platform = atmelavr +board = uno +framework = arduino diff --git a/ws2812fx/firmware/src/main.cpp b/ws2812fx/firmware/src/main.cpp new file mode 100644 index 000000000..928708c8d --- /dev/null +++ b/ws2812fx/firmware/src/main.cpp @@ -0,0 +1,20 @@ +#include + +#include "nymealight.h" + +// TEST +NymeaLight *light = nullptr; + +void setup() { + // Get rid of unused warning from lib + (void)&_modes; + + light = new NymeaLight(Serial, new WS2812FX(10, 13, NEO_GRB + NEO_KHZ800)); + light->init(); +} + +void loop() { + light->process(); +} + + diff --git a/ws2812fx/firmware/src/nymealight.cpp b/ws2812fx/firmware/src/nymealight.cpp new file mode 100644 index 000000000..ea2b40bfc --- /dev/null +++ b/ws2812fx/firmware/src/nymealight.cpp @@ -0,0 +1,330 @@ +#include "nymealight.h" + +NymeaLight::NymeaLight(WS2812FX *strip) : + m_strip(strip) +{ + +} + + +NymeaLight::NymeaLight(SoftwareSerial *serial, WS2812FX *strip) : + m_softwareSerial(serial), + m_strip(strip) +{ + +} + +NymeaLight::NymeaLight(HardwareSerial &serial, WS2812FX *strip) : + m_hardwareSerial(&serial), + m_strip(strip) +{ + +} + +WS2812FX *NymeaLight::strip() const +{ + return m_strip; +} + +void NymeaLight::init() +{ + // Get rid of unused warning from lib + (void)&_modes; + + // Initialize serial port + if (m_hardwareSerial) { + m_hardwareSerial->begin(115200); + } else if (m_softwareSerial) { + m_softwareSerial->begin(115200); + } + + // Initialize strip + m_strip->init(); + if (m_power) { + m_strip->setBrightness(m_brightness); + } else { + m_strip->setBrightness(0); + } + m_strip->setSpeed(m_speed); + m_strip->setColor(0xff, 0xff, 0xff); + m_strip->setMode(FX_MODE_STATIC); + m_strip->start(); + + m_previouseTime = millis(); + sendReadyNotification(); +} + +void NymeaLight::process() +{ + // Read incoming data if there is any + readData(); + + //doAnimations(); + + // Perform service for WS2812FX + m_strip->service(); +} + +void NymeaLight::doAnimations() +{ + // Perform animation steps every 50 ms + uint32_t currentTimestamp = millis(); + if (m_previouseTime - currentTimestamp > 50000) { + //debugPrint("Tick"); + m_previouseTime = currentTimestamp; + + if (m_brightnessFadeDuration > 0 && m_brightnessTargetValue != m_strip->getBrightness()) { + // Calculate step for brightness animation + if (m_brightnessStartValue < m_brightnessTargetValue) { + m_strip->setBrightness(m_strip->getBrightness() - 1); + } else { + m_strip->setBrightness(m_strip->getBrightness() + 1); + } + } + } +} + +void NymeaLight::debugPrint(const char message[]) +{ + streamByte(SlipProtocolEnd, true); + streamByte(NotificationDebugMessage); + streamByte(m_notificationId); + m_notificationId++; + for (size_t i = 0; i < strlen(message); i++) { + streamByte(message[i]); + } + streamByte(SlipProtocolEnd, true); + flushSerial(); +} + +void NymeaLight::flushSerial() +{ + if (m_hardwareSerial) { + m_hardwareSerial->flush(); + } else if (m_softwareSerial) { + m_softwareSerial->flush(); + } +} + +void NymeaLight::sendReadyNotification() +{ + streamByte(SlipProtocolEnd, true); + streamByte(NotificationReady); + streamByte(m_notificationId); + m_notificationId++; + streamByte(SlipProtocolEnd, true); + flushSerial(); +} + +void NymeaLight::readData() +{ + if (m_hardwareSerial) { + while (m_hardwareSerial->available()) { + uint8_t receivedByte = m_hardwareSerial->read(); + processReceivedByte(receivedByte); + } + } else if (m_softwareSerial) { + while (m_softwareSerial->available()) { + uint8_t receivedByte = m_softwareSerial->read(); + processReceivedByte(receivedByte); + } + } +} + +void NymeaLight::processData(uint8_t buffer[], uint8_t length) +{ + uint8_t command = buffer[0]; + uint8_t requestId = buffer[1]; + + switch (command) { + case CommandGetStatus: { + if (length != 2) { + sendResponse(command, requestId, StatusInvalidPlayload); + return; + } + + sendResponse(command, requestId, StatusSuccess); + break; + } + + case CommandSetPower: { + if (length != 5) { + sendResponse(command, requestId, StatusInvalidPlayload); + return; + } + + // uint16_t fadeDuration = (buffer[2] << 8 ) | buffer[3]; + uint8_t powerInt = buffer[4]; + if (powerInt == 0) { + m_power = false; + } else { + m_power = true; + } + + if (m_power) { + m_strip->setBrightness(m_brightness); + } else { + m_strip->setBrightness(0); + } + + // TODO: animate brightness with fade duration + + sendResponse(command, requestId, StatusSuccess); + break; + } + + case CommandSetColor: { + if (length != 7) { + sendResponse(command, requestId, StatusInvalidPlayload); + return; + } + + //uint32_t currentColor = m_strip->getColor(); + //uint32_t targetColor = ((uint32_t) << 16) | ((uint32_t)buffer[5] << 8) | buffer[6]; + // uint16_t fadeDuration = (buffer[2] << 8 ) | buffer[3]; + uint8_t red = buffer[4]; + uint8_t green = buffer[5]; + uint8_t blue = buffer[6]; + m_strip->setColor(red, green, blue); + delayMicroseconds(1000); + sendResponse(command, requestId, StatusSuccess); + break; + } + + case CommandSetBrightness: { + if (length != 5) { + sendResponse(command, requestId, StatusInvalidPlayload); + return; + } + + //uint16_t fadeDuration = (buffer[2] << 8 ) | buffer[3]; + // m_brightnessStartValue = m_strip->getBrightness(); + + m_brightness = buffer[4]; + if (m_power) { + m_strip->setBrightness(m_brightness); + } + + delayMicroseconds(1000); + sendResponse(command, requestId, StatusSuccess); + break; + } + + case CommandSetSpeed: { + if (length != 6) { + sendResponse(command, requestId, StatusInvalidPlayload); + return; + } + + // uint16_t fadeDuration = (buffer[2] << 8 ) | buffer[3]; + uint16_t speed = (buffer[4] << 8 ) | buffer[5]; + m_strip->setSpeed(speed); + delayMicroseconds(1000); + sendResponse(command, requestId, StatusSuccess); + break; + } + + case CommandSetEffect: { + if (length != 3) { + sendResponse(command, requestId, StatusInvalidPlayload); + return; + } + + m_strip->setMode(buffer[2]); + delayMicroseconds(1000); + sendResponse(command, requestId, StatusSuccess); + break; + } + + case CommandCustom: + + break; + + default: + sendResponse(command, requestId, StatusInvalidCommand); + break; + } + + m_strip->show(); + m_strip->service(); +} + +void NymeaLight::sendResponse(uint8_t command, uint8_t requestId, Status status) +{ + streamByte(SlipProtocolEnd, true); + streamByte(command); + streamByte(requestId); + streamByte(status); + streamByte(SlipProtocolEnd, true); + + flushSerial(); +} + +void NymeaLight::processReceivedByte(uint8_t receivedByte) +{ + if (m_protocolEscaping) { + switch (receivedByte) { + case SlipProtocolTransposedEnd: + m_buffer[m_bufferIndex++] = SlipProtocolEnd; + m_protocolEscaping = false; + break; + case SlipProtocolTransposedEsc: + m_buffer[m_bufferIndex++] = 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 + if (m_bufferIndex > 0) { + processData(m_buffer, m_bufferIndex); + } + m_bufferIndex = 0; + 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[m_bufferIndex++] = receivedByte; + break; + } +} + +void NymeaLight::streamByte(uint8_t dataByte, boolean specialCharacter) +{ + // If this is a special character, write it without escaping + if (specialCharacter) { + writeByte(dataByte); + } else { + switch (dataByte) { + case SlipProtocolEnd: + writeByte(SlipProtocolEsc); + writeByte(SlipProtocolTransposedEnd); + break; + case SlipProtocolEsc: + writeByte(SlipProtocolEsc); + writeByte(SlipProtocolTransposedEsc); + break; + default: + writeByte(dataByte); + break; + } + } +} + +void NymeaLight::writeByte(uint8_t dataByte) +{ + if (m_hardwareSerial) { + m_hardwareSerial->write(dataByte); + } else if (m_softwareSerial) { + m_softwareSerial->write(dataByte); + } +} diff --git a/ws2812fx/firmware/src/nymealight.h b/ws2812fx/firmware/src/nymealight.h new file mode 100644 index 000000000..9c50f486d --- /dev/null +++ b/ws2812fx/firmware/src/nymealight.h @@ -0,0 +1,166 @@ + +/* + SLIP transfere: https://tools.ietf.org/html/rfc1055 + + Request package format: + -------------------------- + uint8 : command (< 0xF0) + uint8 : requestId + uint8[] : payload (dynamic size, max: 253) + + + Response format: + -------------------------- + uint8 : command (< 0xF0) + uint8 : requestId (same as request) + uint8 : status + uint8[] : payload (optional, max: 253) + + + Notification package format: + -------------------------- + uint8 : command (>= 0xF0) + uint8 : notificationId + uint8[] : payload (dynamic size, max: 253) + + + Commands: + -------------------------- + 0x00: get ready status + payload: empty + + + 0x01: set power + payload: + uint16 : fade duration in ms + uint8 : power (0x00 = off, 0x01 = on) + + + 0x02: set color + payload: + uint16 : fade duration in ms + uint8 : red + uint8 : green + uint8 : blue + + + 0x03: set brightness + payload: + uint16 : fade duration in ms + uint8 : brightness (0-255) + + + 0x04: set speed + payload: + uint16 : fade duration in ms + uint16 : speed in ms (big endian) + + + 0x05: set effect + payload: + uint8 : effect, see effect list from WS2812FX + + + 0xef: custom command + The payload can be defined free and custom functionality can be implemented + + + Notifications: + -------------------------- + 0xF0: Status ready notification + payload: empty + + + 0xF1: Debug message notification + payload: debug message characters + + +*/ + +#include +#include +#include + +class NymeaLight +{ +public: + enum Command { + CommandGetStatus = 0x00, + CommandSetPower = 0x01, + CommandSetColor = 0x02, + CommandSetBrightness = 0x03, + CommandSetSpeed = 0x04, + CommandSetEffect = 0x05, + CommandCustom = 0xEF + }; + + enum Notification { + NotificationReady = 0xF0, + NotificationDebugMessage = 0xF1 + }; + + enum Status { + StatusSuccess = 0x00, + StatusInvalidProtocol = 0x01, + StatusInvalidCommand = 0x02, + StatusInvalidPlayload = 0x03, + StatusUnknownError = 0xff + }; + + NymeaLight(WS2812FX *strip); + NymeaLight(SoftwareSerial *serial, WS2812FX *strip); + NymeaLight(HardwareSerial &serial, WS2812FX *strip); + + WS2812FX *strip() const; + + void init(); + void process(); + +private: + enum SlipProtocol { + SlipProtocolEnd = 0xC0, + SlipProtocolEsc = 0xDB, + SlipProtocolTransposedEnd = 0xDC, + SlipProtocolTransposedEsc = 0xDD + }; + + HardwareSerial *m_hardwareSerial = nullptr; + SoftwareSerial *m_softwareSerial = nullptr; + WS2812FX *m_strip = nullptr; + uint8_t m_notificationId = 0; + + // Light states + bool m_power = false; + uint8_t m_brightness = 0xff; + uint32_t m_color = 0x00ffffff; + uint8_t m_effect = FX_MODE_STATIC; + uint16_t m_speed = 2000; + + // UART read + uint8_t m_buffer[255]; + uint8_t m_bufferIndex = 0; + boolean m_protocolEscaping = false; + + // Animations + uint32_t m_previouseTime = 0; + + // Brightness animation + uint32_t m_brightnessProgress = 0; // us + uint8_t m_brightnessStartValue = 0; + uint8_t m_brightnessTargetValue = 0; + uint8_t m_brightnessFadeDuration = 0; //ms + + void doAnimations(); + void debugPrint(const char message[]); + void flushSerial(); + void sendReadyNotification(); + +protected: + virtual void readData(); + virtual void processReceivedByte(uint8_t receivedByte); + virtual void processData(uint8_t buffer[], uint8_t length); + virtual void sendResponse(uint8_t command, uint8_t requestId, Status status); + virtual void streamByte(uint8_t dataByte, boolean specialCharacter = false); + virtual void writeByte(uint8_t dataByte); + +}; diff --git a/ws2812fx/firmware/test/README b/ws2812fx/firmware/test/README new file mode 100644 index 000000000..b94d0890f --- /dev/null +++ b/ws2812fx/firmware/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/ws2812fx/integrationpluginws2812fx.cpp b/ws2812fx/integrationpluginws2812fx.cpp index 61620e0a5..71c02f8d3 100644 --- a/ws2812fx/integrationpluginws2812fx.cpp +++ b/ws2812fx/integrationpluginws2812fx.cpp @@ -28,22 +28,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/*! - \page ws2812fx.html - \title WS2812FX Control - \brief Plug-In to control WS2812FX over USB - - \ingroup plugins - \ingroup nymea-plugins - - \chapter Plugin properties - Following JSON file contains the definition and the description of all available \l{ThingClass}{DeviceClasses} - and \l{Vendor}{Vendors} of this \l{DevicePlugin}. - - For more details how to read this JSON file please check out the documentation for \l{The plugin JSON File}. -s - \quotefile plugins/deviceplugins/ws2812fx/devicepluginws2812fx.json -*/ + #include #include @@ -60,34 +45,46 @@ void IntegrationPluginWs2812fx::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); - QString interface = thing->paramValue(ws2812fxThingSerialPortParamTypeId).toString(); + if (thing->thingClassId() == nymeaLightSerialThingClassId) { + QString interface = thing->paramValue(nymeaLightSerialThingSerialPortParamTypeId).toString(); - if (m_usedInterfaces.contains(interface)) { - info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("This serial port is already used.")); - return; - } + if (m_usedInterfaces.contains(interface)) { + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("This serial port is already in use.")); + return; + } - NymeaLightSerialInterface *lightInterface = new NymeaLightSerialInterface(interface, thing); - NymeaLight *light = new NymeaLight(lightInterface, this); - lightInterface->setParent(light); + NymeaLightSerialInterface *lightInterface = new NymeaLightSerialInterface(interface, thing); + NymeaLight *light = new NymeaLight(lightInterface, this); + lightInterface->setParent(light); + m_usedInterfaces.append(interface); + m_lights.insert(thing, light); - if (!lightInterface->open()) { - qCWarning(dcWs2812fx()) << "Could not open interface" << interface; - light->deleteLater(); - info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error opening serial port.")); - return; - } - connect(light, &NymeaLight::availableChanged, thing, [=](bool available){ - qCDebug(dcWs2812fx()) << thing << "available changed" << available; - thing->setStateValue(ws2812fxConnectedStateTypeId, available); - }); + connect(light, &NymeaLight::availableChanged, thing, [=](bool available){ + qCDebug(dcWs2812fx()) << thing << "available changed" << available; + thing->setStateValue(nymeaLightSerialConnectedStateTypeId, available); - qCDebug(dcWs2812fx()) << "Setup successfully serial port" << interface; - thing->setStateValue(ws2812fxConnectedStateTypeId, true); - m_usedInterfaces.append(interface); - m_lights.insert(thing, light); - info->finish(Thing::ThingErrorNoError); + if (available) { + // Set the light to the current states + light->setPower(thing->stateValue(nymeaLightSerialPowerStateTypeId).toBool()); + light->setBrightness(thing->stateValue(nymeaLightSerialBrightnessStateTypeId).toUInt()); + light->setColor(thing->stateValue(nymeaLightSerialColorStateTypeId).value()); + light->setEffect(thing->stateValue(nymeaLightSerialEffectModeStateTypeId).toUInt()); + light->setSpeed(thing->stateValue(nymeaLightSerialSpeedStateTypeId).toUInt()); + } + }); + + + info->finish(Thing::ThingErrorNoError); + } +} + +void IntegrationPluginWs2812fx::postSetupThing(Thing *thing) +{ + if (thing->thingClassId() == nymeaLightSerialThingClassId) { + NymeaLight *light = m_lights.value(thing); + light->enable(); + } } @@ -96,15 +93,14 @@ void IntegrationPluginWs2812fx::discoverThings(ThingDiscoveryInfo *info) // Create the list of available serial interfaces Q_FOREACH(QSerialPortInfo port, QSerialPortInfo::availablePorts()) { - qCDebug(dcWs2812fx()) << "Found serial port:" << port.portName(); - QString description = port.manufacturer() + " " + port.description(); - ThingDescriptor descriptor(info->thingClassId(), port.portName(), description); - foreach (Thing *existingThing, myThings().filterByParam(ws2812fxThingSerialPortParamTypeId, port.portName())) { + QString description = port.systemLocation() + " " + port.manufacturer() + " " + port.description(); + ThingDescriptor descriptor(info->thingClassId(), QT_TR_NOOP("Nymea light"), description); + foreach (Thing *existingThing, myThings().filterByParam(nymeaLightSerialThingSerialPortParamTypeId, port.systemLocation())) { descriptor.setThingId(existingThing->id()); } ParamList parameters; - parameters.append(Param(ws2812fxThingSerialPortParamTypeId, port.portName())); + parameters.append(Param(nymeaLightSerialThingSerialPortParamTypeId, port.systemLocation())); descriptor.setParams(parameters); info->addThingDescriptor(descriptor); } @@ -122,16 +118,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) return; } - if (action.actionTypeId() == ws2812fxPowerActionTypeId) { - bool power = action.param(ws2812fxPowerActionPowerParamTypeId).value().toBool(); - quint8 brightness = 0; - if (power) { - quint8 brightnessPercentage = thing->stateValue(ws2812fxBrightnessStateTypeId).toUInt(); - brightness = qRound(255.0 * brightnessPercentage / 100); - } + if (action.actionTypeId() == nymeaLightSerialPowerActionTypeId) { + bool power = action.param(nymeaLightSerialPowerActionPowerParamTypeId).value().toBool(); qCDebug(dcWs2812fx()) << "Set power" << power; - NymeaLightInterfaceReply *reply = light->setBrightness(brightness, 500); + NymeaLightInterfaceReply *reply = light->setPower(power, 500); connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished); connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){ if (reply->status() != NymeaLightInterface::StatusSuccess) { @@ -140,11 +131,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) } qCDebug(dcWs2812fx()) << "Set power finished successfully" << power; - thing->setStateValue(ws2812fxPowerStateTypeId, power); + thing->setStateValue(nymeaLightSerialPowerStateTypeId, power); info->finish(Thing::ThingErrorNoError); }); - } else if (action.actionTypeId() == ws2812fxColorActionTypeId) { - QColor color = action.param(ws2812fxColorActionColorParamTypeId).value().value(); + } else if (action.actionTypeId() == nymeaLightSerialColorActionTypeId) { + QColor color = action.param(nymeaLightSerialColorActionColorParamTypeId).value().value(); qCDebug(dcWs2812fx()) << "Set color to" << color.name(QColor::HexRgb); NymeaLightInterfaceReply *reply = light->setColor(color); connect(info, &ThingActionInfo::aborted, reply, &NymeaLightInterfaceReply::finished); @@ -154,12 +145,12 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) return; } qCDebug(dcWs2812fx()) << "Set color finished successfully" << color.name(QColor::HexRgb); - thing->setStateValue(ws2812fxColorStateTypeId, color); + thing->setStateValue(nymeaLightSerialColorStateTypeId, color); info->finish(Thing::ThingErrorNoError); }); - } else if (action.actionTypeId() == ws2812fxColorTemperatureActionTypeId) { + } else if (action.actionTypeId() == nymeaLightSerialColorTemperatureActionTypeId) { // minValue 153, maxValue 500 - uint colorTemperature = action.param(ws2812fxColorTemperatureActionColorTemperatureParamTypeId).value().toDouble(); + uint colorTemperature = action.param(nymeaLightSerialColorTemperatureActionColorTemperatureParamTypeId).value().toDouble(); QColor color; color.setRgb(255, 255, qRound((255.00 - (((colorTemperature - 153.00) / 347.00)) * 255.00))); @@ -173,11 +164,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) } qCDebug(dcWs2812fx()) << "Set color temperature finished successfully" << colorTemperature; - thing->setStateValue(ws2812fxColorTemperatureStateTypeId, colorTemperature); + thing->setStateValue(nymeaLightSerialColorTemperatureStateTypeId, colorTemperature); info->finish(Thing::ThingErrorNoError); }); - } else if (action.actionTypeId() == ws2812fxBrightnessActionTypeId) { - quint8 brightnessPercentage = action.param(ws2812fxBrightnessActionBrightnessParamTypeId).value().toUInt(); + } else if (action.actionTypeId() == nymeaLightSerialBrightnessActionTypeId) { + quint8 brightnessPercentage = action.param(nymeaLightSerialBrightnessActionBrightnessParamTypeId).value().toUInt(); quint8 brightness = qRound(255.0 * brightnessPercentage / 100); qCDebug(dcWs2812fx()) << "Set brightness to" << brightnessPercentage << brightness; NymeaLightInterfaceReply *reply = light->setBrightness(brightness, 1000); @@ -189,11 +180,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) } qCDebug(dcWs2812fx()) << "Set brightness finished successfully" << brightness; - thing->setStateValue(ws2812fxBrightnessStateTypeId, brightnessPercentage); + thing->setStateValue(nymeaLightSerialBrightnessStateTypeId, brightnessPercentage); info->finish(Thing::ThingErrorNoError); }); - } else if (action.actionTypeId() == ws2812fxSpeedActionTypeId) { - quint16 speedPercentage = action.param(ws2812fxSpeedActionSpeedParamTypeId).value().toUInt(); + } else if (action.actionTypeId() == nymeaLightSerialSpeedActionTypeId) { + quint16 speedPercentage = action.param(nymeaLightSerialSpeedActionSpeedParamTypeId).value().toUInt(); quint16 speed = 2000 - (speedPercentage * 20); qCDebug(dcWs2812fx()) << "Set speed" << speedPercentage << "%" << speed << "ms"; @@ -206,11 +197,11 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) } qCDebug(dcWs2812fx()) << "Set speed finished successfully" << speedPercentage << "%" << speed << "ms"; - thing->setStateValue(ws2812fxSpeedStateTypeId, speedPercentage); + thing->setStateValue(nymeaLightSerialSpeedStateTypeId, speedPercentage); info->finish(Thing::ThingErrorNoError); }); - } else if (action.actionTypeId() == ws2812fxEffectModeActionTypeId) { - QString effectMode = action.param(ws2812fxEffectModeActionEffectModeParamTypeId).value().toString(); + } else if (action.actionTypeId() == nymeaLightSerialEffectModeActionTypeId) { + QString effectMode = action.param(nymeaLightSerialEffectModeActionEffectModeParamTypeId).value().toString(); quint8 mode = FX_MODE_STATIC; if (effectMode == "Static") { mode = FX_MODE_STATIC; @@ -340,7 +331,7 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) } qCDebug(dcWs2812fx()) << "Set mode finished successfully" << effectMode << mode; - thing->setStateValue(ws2812fxEffectModeStateTypeId, effectMode); + thing->setStateValue(nymeaLightSerialEffectModeStateTypeId, effectMode); info->finish(Thing::ThingErrorNoError); }); } @@ -349,9 +340,8 @@ void IntegrationPluginWs2812fx::executeAction(ThingActionInfo *info) void IntegrationPluginWs2812fx::thingRemoved(Thing *thing) { - if (thing->thingClassId() == ws2812fxThingClassId) { - - m_usedInterfaces.removeAll(thing->paramValue(ws2812fxThingSerialPortParamTypeId).toString()); + if (thing->thingClassId() == nymeaLightSerialThingClassId) { + m_usedInterfaces.removeAll(thing->paramValue(nymeaLightSerialThingSerialPortParamTypeId).toString()); NymeaLight *light = m_lights.take(thing); light->deleteLater(); } diff --git a/ws2812fx/integrationpluginws2812fx.h b/ws2812fx/integrationpluginws2812fx.h index 139b06314..29ccd6aca 100644 --- a/ws2812fx/integrationpluginws2812fx.h +++ b/ws2812fx/integrationpluginws2812fx.h @@ -111,6 +111,7 @@ class IntegrationPluginWs2812fx : public IntegrationPlugin explicit IntegrationPluginWs2812fx(); void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; void thingRemoved(Thing *thing) override; void discoverThings(ThingDiscoveryInfo *info) override; void executeAction(ThingActionInfo *info) override; diff --git a/ws2812fx/integrationpluginws2812fx.json b/ws2812fx/integrationpluginws2812fx.json index c7de2fd6a..794d31b78 100644 --- a/ws2812fx/integrationpluginws2812fx.json +++ b/ws2812fx/integrationpluginws2812fx.json @@ -10,8 +10,8 @@ "thingClasses": [ { "id": "24364e36-b199-4e35-b468-c44da58c009c", - "name": "ws2812fx", - "displayName": "WS2812FX", + "name": "nymeaLightSerial", + "displayName": "Serial nymea light", "createMethods": ["user", "discovery"], "interfaces": ["colorlight", "connectable"], "paramTypes": [ @@ -21,7 +21,7 @@ "displayName": "Serial port", "type": "QString", "inputType": "TextLine", - "defaultValue": "ttyAMC0" + "defaultValue": "/dev/ttyAMC0" } ], "stateTypes": [ @@ -63,7 +63,7 @@ "displayNameEvent": "color changed", "displayNameAction": "Set color", "type": "QColor", - "defaultValue": "#000000", + "defaultValue": "#FFFFFF", "writable": true }, { @@ -74,7 +74,7 @@ "displayNameAction": "Set brigtness", "type": "int", "unit": "Percentage", - "defaultValue": 0, + "defaultValue": 100, "minValue": 0, "maxValue": 100, "writable": true @@ -87,7 +87,7 @@ "displayNameAction": "Set speed", "type": "int", "unit": "Percentage", - "defaultValue": 0, + "defaultValue": 50, "minValue": 0, "maxValue": 100, "writable": true diff --git a/ws2812fx/nymealight.cpp b/ws2812fx/nymealight.cpp index a92d11b55..47436b9e9 100644 --- a/ws2812fx/nymealight.cpp +++ b/ws2812fx/nymealight.cpp @@ -7,12 +7,36 @@ NymeaLight::NymeaLight(NymeaLightInterface *interface, QObject *parent) : QObject(parent), m_interface(interface) { + connect(m_interface, &NymeaLightInterface::dataReceived, this, &NymeaLight::onDataReceived); connect(m_interface, &NymeaLightInterface::availableChanged, this, [=](bool available){ - qCDebug(dcWs2812fx()) << "Interface available changed" << available; - emit availableChanged(available); + m_interfaceAvailable = available; + if (m_interfaceAvailable) { + qCDebug(dcWs2812fx()) << "Nymea light interface is now available. Start polling status of the light controller..."; + m_pollStatusRetryCount = 0; + pollStatus(); + } else { + m_ready = false; + m_requestId = 0; + emit availableChanged(false); + } }); - connect(m_interface, &NymeaLightInterface::dataReceived, this, &NymeaLight::onDataReceived); +} + +NymeaLightInterfaceReply *NymeaLight::setPower(bool power, quint16 fadeDuration) +{ + // Build the request + QByteArray requestData; + QDataStream stream(&requestData, QIODevice::WriteOnly); + stream << static_cast(NymeaLightInterface::CommandSetPower); + stream << m_requestId++; + stream << fadeDuration; + stream << static_cast(power ? 0x01 : 0x00); + + NymeaLightInterfaceReply *reply = createReply(requestData); + m_pendingRequests.enqueue(reply); + sendNextRequest(); + return reply; } NymeaLightInterfaceReply *NymeaLight::setColor(const QColor &color, quint16 fadeDuration) @@ -82,7 +106,19 @@ NymeaLightInterfaceReply *NymeaLight::setEffect(quint8 effect) bool NymeaLight::available() const { - return m_interface->available(); + return m_interfaceAvailable && m_ready; +} + +void NymeaLight::enable() +{ + m_interface->open(); + qCDebug(dcWs2812fx()) << "Nymea light enabled"; +} + +void NymeaLight::disable() +{ + m_interface->close(); + qCDebug(dcWs2812fx()) << "Nymea light disabled"; } NymeaLightInterfaceReply *NymeaLight::createReply(const QByteArray &requestData) @@ -114,36 +150,93 @@ void NymeaLight::sendNextRequest() m_currentReply->m_timer->start(); } +void NymeaLight::pollStatus() +{ + // Request ready state from controller + NymeaLightInterfaceReply *reply = getStatus(); + connect(reply, &NymeaLightInterfaceReply::finished, this, [=](){ + if (reply->status() == NymeaLightInterface::StatusSuccess) { + qCDebug(dcWs2812fx()) << "Get status request finished successfully. The firmware is ready to operate."; + m_ready = true; + m_pollStatusRetryCount = 0; + emit availableChanged(true); + } else { + m_pollStatusRetryCount++; + if (m_pollStatusRetryCount >= m_pollStatusRetryLimit) { + qCWarning(dcWs2812fx()) << "Firmware did not respond to get status request after" << m_pollStatusRetryCount << "attempts. Giving up."; + m_ready = false; + } else { + if (!m_ready && m_interfaceAvailable) { + qCDebug(dcWs2812fx()) << "Get status request finished with error" << reply->status() << "Retry" << m_pollStatusRetryCount << "/" << m_pollStatusRetryLimit; + pollStatus(); + } else { + qCDebug(dcWs2812fx()) << "Get status request finished with error, but that's ok since we received the ready notification." << reply->status(); + } + } + } + }); +} + +NymeaLightInterfaceReply *NymeaLight::getStatus() +{ + qCDebug(dcWs2812fx()) << "Request status of nymea light"; + QByteArray requestData; + QDataStream stream(&requestData, QIODevice::WriteOnly); + stream << static_cast(NymeaLightInterface::CommandGetStatus); + stream << m_requestId++; + + NymeaLightInterfaceReply *reply = createReply(requestData); + m_pendingRequests.enqueue(reply); + sendNextRequest(); + return reply; +} + void NymeaLight::onDataReceived(const QByteArray &data) { Q_ASSERT(data.length() >= 3); - NymeaLightInterface::Command command = static_cast(static_cast((data.at(0)))); + quint8 commandInt = static_cast((data.at(0))); quint8 requestId = static_cast(data.at(1)); - //qCDebug(dcWs2812fx()) << "Recived data" << command << requestId << data.toHex(); - if (command == NymeaLightInterface::CommandDebug) { - qCDebug(dcWs2812fx()) << "Firmware debug:" << QString::fromUtf8(data.right(data.length() - 2)); - return; - } + qCDebug(dcWs2812fx()) << "Recived data" << commandInt << requestId << data.toHex(); - NymeaLightInterface::Status status = static_cast(data.at(2)); + // Check if command or notification + if (commandInt < 0xF0) { + NymeaLightInterface::Command command = static_cast(commandInt); - if (m_currentReply) { - if (m_currentReply->command() == command && m_currentReply->requestId() == requestId) { - m_currentReply->m_timer->stop(); - m_currentReply->m_status = status; + NymeaLightInterface::Status status = static_cast(data.at(2)); - if (status != NymeaLightInterface::StatusSuccess) { - qCWarning(dcWs2812fx()) << "Request finished with error" << command << m_currentReply->requestId() << status; - } else { - qCDebug(dcWs2812fx()) << "Request finished" << command << m_currentReply->requestId() << status; - } + if (m_currentReply) { + if (m_currentReply->command() == command && m_currentReply->requestId() == requestId) { + m_currentReply->m_timer->stop(); + m_currentReply->m_status = status; + + if (status != NymeaLightInterface::StatusSuccess) { + qCWarning(dcWs2812fx()) << "Request finished with error" << command << m_currentReply->requestId() << status; + } else { + qCDebug(dcWs2812fx()) << "Request finished" << command << m_currentReply->requestId() << status; + } - emit m_currentReply->finished(); + emit m_currentReply->finished(); + } + } else { + qCWarning(dcWs2812fx()) << "Received unhandled command response data" << data.toHex(); } } else { - qCWarning(dcWs2812fx()) << "Received unhandled data" << data.toHex(); + NymeaLightInterface::Notification notification = static_cast(commandInt); + switch (notification) { + case NymeaLightInterface::NotificationReady: + qCDebug(dcWs2812fx()) << "Controller ready notification received"; + m_ready = true; + emit availableChanged(true); + break; + case NymeaLightInterface::NotificationDebugMessage: + qCDebug(dcWs2812fx()) << "Firmware debug:" << QString::fromUtf8(data.right(data.length() - 2)); + break; + default: + qCWarning(dcWs2812fx()) << "Unhandled notification received" << data.toHex(); + break; + } } } diff --git a/ws2812fx/nymealight.h b/ws2812fx/nymealight.h index 9f5584c76..f1cc848dc 100644 --- a/ws2812fx/nymealight.h +++ b/ws2812fx/nymealight.h @@ -15,6 +15,7 @@ class NymeaLight : public QObject // 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 *setPower(bool power, quint16 fadeDuration = 0); NymeaLightInterfaceReply *setColor(const QColor &color, quint16 fadeDuration = 0); NymeaLightInterfaceReply *setBrightness(quint8 brightness, quint16 fadeDuration = 0); NymeaLightInterfaceReply *setSpeed(quint16 speed, quint16 fadeDuration = 0); @@ -22,9 +23,17 @@ class NymeaLight : public QObject bool available() const; +public slots: + void enable(); + void disable(); + private: NymeaLightInterface *m_interface = nullptr; quint8 m_requestId = 0; + bool m_interfaceAvailable = false; + bool m_ready = false; + int m_pollStatusRetryCount = 0; + int m_pollStatusRetryLimit = 5; NymeaLightInterfaceReply *m_currentReply = nullptr; QQueue m_pendingRequests; @@ -32,6 +41,11 @@ class NymeaLight : public QObject NymeaLightInterfaceReply *createReply(const QByteArray &requestData); void sendNextRequest(); + + void pollStatus(); + NymeaLightInterfaceReply *getStatus(); + + private slots: void onDataReceived(const QByteArray &data); diff --git a/ws2812fx/nymealightinterface.cpp b/ws2812fx/nymealightinterface.cpp deleted file mode 100644 index e80cfc33d..000000000 --- a/ws2812fx/nymealightinterface.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "nymealightinterface.h" - -NymeaLightInterface::NymeaLightInterface(QObject *parent) : QObject(parent) -{ - -} diff --git a/ws2812fx/nymealightinterface.h b/ws2812fx/nymealightinterface.h index 48dc0399c..d088d1a2e 100644 --- a/ws2812fx/nymealightinterface.h +++ b/ws2812fx/nymealightinterface.h @@ -9,15 +9,22 @@ class NymeaLightInterface : public QObject Q_OBJECT public: enum Command { - CommandSetColor = 0x00, - CommandSetBrightness = 0x01, - CommandSetSpeed = 0x02, - CommandSetEffect = 0x03, - CommandDebug = 0xFE, - CommandCustom = 0xFF + CommandGetStatus = 0x00, + CommandSetPower = 0x01, + CommandSetColor = 0x02, + CommandSetBrightness = 0x03, + CommandSetSpeed = 0x04, + CommandSetEffect = 0x05, + CommandCustom = 0xEF }; Q_ENUM(Command) + enum Notification { + NotificationReady = 0xF0, + NotificationDebugMessage = 0xF1 + }; + Q_ENUM(Notification) + enum Status { StatusSuccess = 0x00, StatusInvalidProtocol = 0x01, @@ -34,7 +41,8 @@ class NymeaLightInterface : public QObject }; Q_ENUM(Mode) - explicit NymeaLightInterface(QObject *parent = nullptr); + inline explicit NymeaLightInterface(QObject *parent = nullptr) : QObject(parent) { }; + virtual ~NymeaLightInterface() = default; virtual bool open() = 0; virtual void close() = 0; diff --git a/ws2812fx/nymealightserialinterface.cpp b/ws2812fx/nymealightserialinterface.cpp index 0ab8e928d..ea8315166 100644 --- a/ws2812fx/nymealightserialinterface.cpp +++ b/ws2812fx/nymealightserialinterface.cpp @@ -4,9 +4,10 @@ #include NymeaLightSerialInterface::NymeaLightSerialInterface(const QString &name, QObject *parent) : - NymeaLightInterface(parent) + NymeaLightInterface(parent), + m_serialPortName(name) { - m_serialPort = new QSerialPort(name, this); + m_serialPort = new QSerialPort(m_serialPortName, this); m_serialPort->setBaudRate(115200); m_serialPort->setDataBits(QSerialPort::DataBits::Data8); m_serialPort->setParity(QSerialPort::Parity::NoParity); @@ -33,8 +34,26 @@ NymeaLightSerialInterface::NymeaLightSerialInterface(const QString &name, QObjec bool NymeaLightSerialInterface::open() { + bool serialPortFound = false; + foreach(const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) { + if (serialPortInfo.systemLocation() == m_serialPortName) { + serialPortFound = true; + break; + } + } + + // Prevent repeating warnings... + if (!serialPortFound) { + if (!m_reconnectTimer->isActive()) { + m_reconnectTimer->start(); + } + return false; + } + + // The serial port is available...lt's try to open it if (!m_serialPort->open(QIODevice::ReadWrite)) { qCWarning(dcWs2812fx()) << "Could not open serial port" << m_serialPort->portName() << m_serialPort->errorString(); + m_reconnectTimer->start(); return false; } @@ -130,7 +149,7 @@ void NymeaLightSerialInterface::onReadyRead() void NymeaLightSerialInterface::onSerialError(QSerialPort::SerialPortError error) { if (error != QSerialPort::NoError && m_serialPort->isOpen()) { - qCCritical(dcWs2812fx()) << "Serial port error:" << error << m_serialPort->errorString(); + qCWarning(dcWs2812fx()) << "Serial port error:" << error << m_serialPort->errorString(); m_reconnectTimer->start(); m_serialPort->close(); emit availableChanged(false); diff --git a/ws2812fx/nymealightserialinterface.h b/ws2812fx/nymealightserialinterface.h index 2def124a1..beb4863a3 100644 --- a/ws2812fx/nymealightserialinterface.h +++ b/ws2812fx/nymealightserialinterface.h @@ -1,9 +1,10 @@ #ifndef NYMEALIGHTSERIALINTERFACE_H #define NYMEALIGHTSERIALINTERFACE_H +#include #include #include -#include +#include #include "nymealightinterface.h" @@ -12,10 +13,12 @@ class NymeaLightSerialInterface : public NymeaLightInterface Q_OBJECT public: explicit NymeaLightSerialInterface(const QString &name, QObject *parent = nullptr); + ~NymeaLightSerialInterface() override = default; bool open() override; void close() override; bool available() override; + void sendData(const QByteArray &data) override; private: @@ -26,6 +29,7 @@ class NymeaLightSerialInterface : public NymeaLightInterface SlipProtocolTransposedEsc = 0xDD }; + QString m_serialPortName; QTimer *m_reconnectTimer = nullptr; QSerialPort *m_serialPort = nullptr; QByteArray m_buffer; diff --git a/ws2812fx/ws2812fx.pro b/ws2812fx/ws2812fx.pro index 62c8a8737..33f5bbd88 100644 --- a/ws2812fx/ws2812fx.pro +++ b/ws2812fx/ws2812fx.pro @@ -7,7 +7,6 @@ TARGET = $$qtLibraryTarget(nymea_integrationpluginws2812fx) SOURCES += \ integrationpluginws2812fx.cpp \ nymealight.cpp \ - nymealightinterface.cpp \ nymealightserialinterface.cpp