/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2020, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * This project including source code and documentation is protected by * copyright law, and remains the property of nymea GmbH. All rights, including * reproduction, publication, editing and translation, are reserved. The use of * this project is subject to the terms of a license agreement to be concluded * with nymea GmbH in accordance with the terms of use of nymea GmbH, available * under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; version 3. This project is distributed in the hope that * it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this project. If not, see . * * For any further details and any questions please contact us under * contact@nymea.io or see our FAQ/Licensing Information on * https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*! \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}. \quotefile plugins/deviceplugins/ws2812fx/devicepluginws2812fx.json */ #include #include "integrationpluginws2812fx.h" #include "plugininfo.h" IntegrationPluginWs2812fx ::IntegrationPluginWs2812fx () { } void IntegrationPluginWs2812fx::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); QString interface = thing->paramValue(ws2812fxThingSerialPortParamTypeId).toString(); if (m_usedInterfaces.contains(interface)) { info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("This serial port is already used.")); return; } QSerialPort *serialPort = new QSerialPort(interface, this); 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.")); } connect(serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onSerialError(QSerialPort::SerialPortError))); connect(serialPort, SIGNAL(readyRead()), this, SLOT(onReadyRead())); 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); } info->finish(Thing::ThingErrorNoError); } 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())) { descriptor.setThingId(existingThing->id()); } ParamList parameters; parameters.append(Param(ws2812fxThingSerialPortParamTypeId, port.portName())); descriptor.setParams(parameters); info->addThingDescriptor(descriptor); } info->finish(Thing::ThingErrorNoError); } 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); } 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); } } 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(); } } 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); }