// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-plugins. * * nymea-plugins is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nymea-plugins 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nymea-plugins. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "lifxlan.h" #include "extern-plugininfo.h" #include #include LifxLan::LifxLan(const QHostAddress &address, quint16 port, QObject *parent) : QObject(parent), m_host(address), m_port(port) { m_clientId = std::rand(); m_socket = new QUdpSocket(this); m_socket->setSocketOption(QAbstractSocket::MulticastTtlOption, QVariant(1)); m_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, QVariant(1)); } LifxLan::~LifxLan() { if (m_socket) { m_socket->waitForBytesWritten(1000); m_socket->close(); } } bool LifxLan::enable() { // Bind udp socket and join multicast group if(!m_socket->bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress)){ qCWarning(dcLifx()) << "could not bind to port" << m_port; delete m_socket; m_socket = nullptr; return false; } if(!m_socket->joinMulticastGroup(QHostAddress("239.255.255.250"))){ qCWarning(dcLifx()) << "could not join multicast group"; delete m_socket; m_socket = nullptr; return false; } connect(m_socket, &QUdpSocket::readyRead, this, &LifxLan::onReadyRead); return true; } void LifxLan::setHostAddress(const QHostAddress &address) { m_host = address; } void LifxLan::setPort(quint16 port) { m_port = port; } int LifxLan::setColorTemperature(uint mirad, uint msFadeTime) { Q_UNUSED(mirad) Q_UNUSED(msFadeTime) int requestId = std::rand(); Message message; sendMessage(message); return requestId; } int LifxLan::setColor(QColor color, uint msFadeTime) { Q_UNUSED(color) Q_UNUSED(msFadeTime) int requestId = std::rand(); Message message; //TODO create LAN message sendMessage(message); return requestId; } int LifxLan::setBrightness(uint percentage, uint msFadeTime) { Q_UNUSED(percentage) Q_UNUSED(msFadeTime) int requestId = std::rand(); Message message; sendMessage(message); //TODO create LAN message return requestId; } int LifxLan::setPower(bool power, uint msFadeTime) { Q_UNUSED(power) Q_UNUSED(msFadeTime) int requestId = std::rand(); Message message; sendMessage(message); //TODO create LAN message return requestId; } void LifxLan::sendMessage(const LifxLan::Message &message) { QByteArray header; // -- FRAME -- // Protocol number: must be 1024 (decimal) quint16 protocol = 1024; protocol |= (0x0001 << 4); //Message includes a target address: must be one (1) protocol |= (message.frame.Tagged << 5); // Determines usage of the Frame Address target field protocol &= ~(0x0003); // Message origin indicator: must be zero (0) header.append(protocol >> 8); header.append(protocol & 0xff); //Source identifier: unique value set by the client, used by responses header.append(m_clientId); // -- FRAME ADDRESS -- //Target - frame address starts with 64 bits //ADD RESERVED SECTION a reserved section of 48 bits (6 bytes) //header.append(6, '\x00'); //that must be all zeros. //ADD ACK and RES //header.append(2, '\x01'); //ADD SEQUENCE NUMBER 1Byte //header.append(m_sequenceNumber++); //Protocol header. which begins with 64 reserved bits (8 bytes). Set these all to zero. //header.append(8, '\x00'); //that must be all zeros. //ADD MESSAGE TYPE //header.append(static_cast(LightMessages::SetColor)); // Finally another reserved field of 16 bits (2 bytes). //header.append(2, '\x00'); //ADD SIZE //header.append(((static_cast(header.length()+1) & 0xff00) >> 8)); //header.append((static_cast(header.length()+1) & 0x00ff)); //Finally another reserved field of 16 bits (2 bytes). //header.append(2, '\x00'); QByteArray fullMessage; //fullMessage = QByteArray::fromHex("0x310000340000000000000000000000000000000000000000000000000000000066000000005555FFFFFFFFAC0D00040000"); // test message - set all lights green //std::reverse(fullMessage.begin(), fullMessage.end()); m_socket->writeDatagram(fullMessage, m_host, m_port); } void LifxLan::onStateChanged(QAbstractSocket::SocketState state) { switch (state) { case QAbstractSocket::SocketState::ConnectedState: emit connectionChanged(true); break; case QAbstractSocket::SocketState::UnconnectedState: m_reconnectTimer->start(10 * 1000); emit connectionChanged(false); break; default: emit connectionChanged(false); break; } } void LifxLan::onReadyRead() { QByteArray data = m_socket->readAll(); qCDebug(dcLifx()) << "Message received" << data; }