226 lines
6.7 KiB
C++
226 lines
6.7 KiB
C++
#include "xplinstance.h"
|
|
#include "xplmessage.h"
|
|
|
|
#include <QUdpSocket>
|
|
#include <QLinkedList>
|
|
#include <QNetworkInterface>
|
|
|
|
#include <QDebug>
|
|
#include <QTimer>
|
|
|
|
class xPLInstancePrivate {
|
|
public:
|
|
xPLDevice *thisDevice;
|
|
QLinkedList<xPLDevice> devices;
|
|
QUdpSocket *socket;
|
|
xPLInstance::OperationMode mode;
|
|
QHostAddress address;
|
|
bool isAttatched;
|
|
QTimer *timer;
|
|
};
|
|
|
|
xPLInstance::xPLInstance( const xPLDevice &device, QObject *parent )
|
|
:QObject(parent)
|
|
{
|
|
d = new xPLInstancePrivate;
|
|
d->thisDevice = new xPLDevice(device);
|
|
d->mode = Disconnected;
|
|
d->timer = new QTimer(this);
|
|
this->init();
|
|
}
|
|
|
|
xPLInstance::xPLInstance( const QString &vendor, const QString &device, QObject *parent )
|
|
:QObject(parent)
|
|
{
|
|
d = new xPLInstancePrivate;
|
|
d->thisDevice = new xPLDevice(vendor, device);
|
|
d->mode = Disconnected;
|
|
d->timer = new QTimer(this);
|
|
this->init();
|
|
}
|
|
|
|
xPLInstance::~xPLInstance() {
|
|
delete d->thisDevice;
|
|
delete d;
|
|
}
|
|
|
|
void xPLInstance::init() {
|
|
foreach(QHostAddress address, QNetworkInterface::allAddresses()) {
|
|
if (address.protocol() == QAbstractSocket::IPv4Protocol && address != QHostAddress::LocalHost) {
|
|
d->address = address;
|
|
break;
|
|
}
|
|
}
|
|
d->isAttatched = false;
|
|
d->socket = new QUdpSocket(this);
|
|
d->thisDevice->setPort(this->bindToPort());
|
|
if (d->thisDevice->port() == 0) {
|
|
return;
|
|
}
|
|
d->timer->setInterval( 3000 );
|
|
connect(d->socket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
|
|
connect(d->timer, SIGNAL(timeout()), this, SLOT(poll()));
|
|
this->sendHeartbeat();
|
|
d->timer->start();
|
|
}
|
|
|
|
bool xPLInstance::attatched() const {
|
|
return this->d->isAttatched;
|
|
}
|
|
|
|
int xPLInstance::bindToPort() {
|
|
//Hub-mode not supported
|
|
/*if (d->socket->bind( XPL_PORT )) {
|
|
d->mode = Hub;
|
|
qDebug("Bind succeded as hub");
|
|
return XPL_PORT;
|
|
}*/
|
|
for (int port = 49152; port <= 65535; ++port) {
|
|
if (d->socket->bind( port, QUdpSocket::DontShareAddress )) {
|
|
d->mode = Client;
|
|
qDebug() << "Bind succeded as client on" << port;
|
|
return port;
|
|
}
|
|
}
|
|
qDebug("Bind failed");
|
|
return 0;
|
|
}
|
|
|
|
xPLInstance::OperationMode xPLInstance::operationMode() const {
|
|
return d->mode;
|
|
}
|
|
|
|
void xPLInstance::processPendingDatagrams() {
|
|
while (d->socket->hasPendingDatagrams()) {
|
|
QByteArray datagram;
|
|
datagram.resize(d->socket->pendingDatagramSize());
|
|
d->socket->readDatagram(datagram.data(), datagram.size());
|
|
xPLMessage *message = xPLMessage::createMessageFromString( datagram.data() );
|
|
if (message) {
|
|
processMessage( *message );
|
|
delete message;
|
|
}
|
|
}
|
|
}
|
|
|
|
void xPLInstance::poll() {
|
|
if (!attatched()) {
|
|
sendHeartbeat();
|
|
return;
|
|
}
|
|
if (d->thisDevice->lastHeartBeat().secsTo( QDateTime::currentDateTime() ) >= d->thisDevice->heartBeatInterval() * 60) {
|
|
sendHeartbeat();
|
|
}
|
|
//Loop all devices to see if they have timed out
|
|
for( QLinkedList<xPLDevice>::iterator it = d->devices.begin(); it != d->devices.end(); ) {
|
|
if ((*it).lastHeartBeat().secsTo( QDateTime::currentDateTime() ) >= (*it).heartBeatInterval() * 60 * 2) {
|
|
qDebug() << "Device removed (timeout):" << (*it).deviceName();
|
|
it = d->devices.erase( it );
|
|
continue;
|
|
}
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void xPLInstance::sendMessage( const xPLMessage &msg ) const {
|
|
xPLMessage message(msg); //Copy so we don't have const
|
|
message.setSource(d->thisDevice->deviceName());
|
|
QByteArray datagram = message.toString().toAscii();
|
|
d->socket->writeDatagram(datagram.data(), datagram.size(), QHostAddress::Broadcast, XPL_PORT);
|
|
}
|
|
|
|
void xPLInstance::sendMessage( xPLMessage * message ) const {
|
|
this->sendMessage(*message);
|
|
}
|
|
|
|
void xPLInstance::sendMessage( const xPLMessage &message, const xPLDevice &device ) const {
|
|
QByteArray datagram = message.toString().toAscii();
|
|
d->socket->writeDatagram(datagram.data(), datagram.size(), device.address(), device.port());
|
|
}
|
|
|
|
void xPLInstance::processHeartBeat( const xPLMessage &message ) {
|
|
if (message.messageSchemeIdentifier() == "hbeat.request") {
|
|
sendHeartbeat();
|
|
return;
|
|
}
|
|
for( QLinkedList<xPLDevice>::iterator it = d->devices.begin(); it != d->devices.end(); ++it ) {
|
|
if ( (*it).deviceName() == message.source() ) {
|
|
if (message.messageSchemeIdentifier() == "hbeat.end") {
|
|
qDebug() << "Device removed:" << message.source();
|
|
d->devices.erase( it );
|
|
return;
|
|
} else {
|
|
(*it).setLastHeartBeat( QDateTime::currentDateTime() );
|
|
(*it).setHeartBeatInterval( message.bodyItem( "interval" ).toInt() );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (!message.messageSchemeIdentifier().endsWith("app") && !message.messageSchemeIdentifier().endsWith("basic")) {
|
|
return;
|
|
}
|
|
qDebug() << "New device:" << message.source();
|
|
xPLDevice device( message.source() );
|
|
device.setHeartBeatInterval( message.bodyItem("interval").toInt() );
|
|
device.setLastHeartBeat( QDateTime::currentDateTime() );
|
|
if (message.messageSchemeIdentifier() == "hbeat.app") {
|
|
device.setAddress( QHostAddress(message.bodyItem("remote-ip")) );
|
|
device.setPort( message.bodyItem("port").toInt() );
|
|
}
|
|
d->devices.append( device );
|
|
}
|
|
|
|
void xPLInstance::processMessage( const xPLMessage &message ) {
|
|
if (!attatched() && message.messageSchemeIdentifier() == "hbeat.app" && message.source() == d->thisDevice->deviceName()) {
|
|
//Our own echo
|
|
setAttatched( true );
|
|
return;
|
|
}
|
|
if (message.source() == d->thisDevice->deviceName()) {
|
|
//Ignore messages from ourselves
|
|
return;
|
|
}
|
|
if (message.messageSchemeIdentifier().startsWith("hbeat") || message.messageSchemeIdentifier().startsWith("config")) {
|
|
processHeartBeat( message );
|
|
}
|
|
if (d->mode == xPLInstance::Hub) {
|
|
//Reply the message to all clients
|
|
for( QLinkedList<xPLDevice>::iterator it = d->devices.begin(); it != d->devices.end(); ++it ) {
|
|
sendMessage(message, *it);
|
|
}
|
|
}
|
|
if (message.target() != d->thisDevice->deviceName() && message.target() != "*") {
|
|
//Message not for us
|
|
return;
|
|
}
|
|
xPLMessage newMsg(message);
|
|
emit messageReceived(&newMsg);
|
|
}
|
|
|
|
void xPLInstance::sendHeartbeat() {
|
|
xPLMessage message( xPLMessage::xplstat );
|
|
message.setMessageSchemeIdentifier( "hbeat.app" );
|
|
message.setTarget("*");
|
|
message.setSource( d->thisDevice->deviceName() );
|
|
message.addBodyItem( "interval", QString::number(d->thisDevice->heartBeatInterval()) );
|
|
message.addBodyItem( "port", QString::number(d->thisDevice->port()) );
|
|
message.addBodyItem( "remote-ip", d->address.toString() );
|
|
sendMessage( message );
|
|
d->thisDevice->setLastHeartBeat( QDateTime::currentDateTime() );
|
|
}
|
|
|
|
void xPLInstance::setAttatched( bool attatched ) {
|
|
d->isAttatched = attatched;
|
|
if (d->isAttatched) {
|
|
qDebug() << "Attached to network!";
|
|
d->timer->setInterval( 60000 ); //Once a minute
|
|
|
|
xPLMessage message( xPLMessage::xplcmnd );
|
|
message.setTarget( "*" );
|
|
message.setSource( d->thisDevice->deviceName() );
|
|
message.setMessageSchemeIdentifier( "hbeat.request" );
|
|
message.addBodyItem("command", "request");
|
|
sendMessage( message );
|
|
emit attachedToNetwork();
|
|
}
|
|
}
|