Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
server.cpp 16.74 KiB
#include "server.h"
#include "SHmessage.h"
#include "webSocketMsg.h"

#include <QDebug>
#include <QHostAddress>
#include <QTimer>
#include <unistd.h>

/*
 * Konstruktor
 */
Server::Server(QString TCPhostName, QObject *parent) :
    QObject(parent),
    m_gatewayHostName(TCPhostName),
    m_gatewayTCPsocket(this)
{
    qDebug() << "Starting ... ";

    // Verbindung zum MySensors Controller:
    m_gatewayTCPsocket.connectToHost(QHostAddress(m_gatewayHostName), 5003);

    connect(&m_gatewayTCPsocket, &QTcpSocket::readyRead,
            this, &Server::onReadyReadSensorValueTCP);
    qDebug() << "TCPsocket" << m_gatewayHostName << "Port 5003" ;

    SHactuator::setSocket(&m_gatewayTCPsocket);

    // HTTP Requests für Tasmota:
    m_tasmota = new QNetworkAccessManager();
    // m_tasmota->setTransferTimeout(1500);  // ms -- ab Qt 5.15

    connect(m_tasmota, &QNetworkAccessManager::finished,
            this, &Server::tasmotaReply);

    SHsensor::initStatics(m_tasmota); // Map für alle Namen und Tasmota Manager


    // serielle Schnittstelle ansehen und starten:
    // startSerial();

    // Sensoren:
    // temparature
    SHsensor *t_wrom = new SHsensor(NodeIdTempWR, SHsensor::tempSensor, "http://192.168.178.72");
    SHsensor *t_lrom = new SHsensor(NodeIDLivingroom, SHsensor::tempSensor, "http://192.168.178.102");
    SHsensor *t_bath = new SHsensor(NodeIDBadUntenT, SHsensor::tempSensor, "http://192.168.178.100");
    // SHsensor *t_dummy = new SHsensor(NodeIdDummy, SHsensor::tempSensor, "http://192.168.178.72");

    // producer
    // SHsensor *solar = new SHsensor(NodeIdLightSensor, SHsensor::lightSensor);
    SHsensor *central = new SHsensor(NodeIDCentral, SHsensor::lightSensor, "http://192.168.178.142");


    // consumer
    // SHsensor *wash = new SHsensor(NodeIDWashMachine, SHsensor::powerSensor, "http://192.168.178.105");
    // SHsensor *ecar = new SHsensor(NodeIDeCar, SHsensor::powerSensor);
    // SHsensor *airc = new SHsensor(NodeIdAirconditioner, SHsensor::powerSensor, "http://192.168.178.49");

    // Repeater:
    // SHsensor *dummy = new SHsensor("Dummy", NodeIdDummy, SHsensor::none); addAllSensors einbeziehen
    // SHsensor *repeat2 = new SHsensor(NodeIDRepeater2, SHsensor::none);

    //
    // Schalter + Controller
    //
    // Bad;8;1;   Off;22.5;18.5;07:15;21:00
    // Arbeit;7;1;Off;22.5;18.5;07:15;21:00
    // Wohn;5;1;  Off;22.5;18.5;07:15;23:00
    // Kamin;2;1; Off;22.5;18.5;07:15;23:00
    // Bad unten:
    SHactuator *sBath300 = new SHactuator(NodeIDBadUnten, 320, SHactuator::SensorType::Switch, "http://192.168.178.93");
    HeatControl *controlBath300  = new HeatControl(t_bath, sBath300,  22.5, 18.5, QTime(7,10), QTime(21,0));

    // Arbeitszimmer
    SHactuator *sWrom750 = new SHactuator(NodeIdSolarheatWR, 745, SHactuator::SensorType::Switch, "http://192.168.178.24");
    HeatControl * controlWRom750  = new HeatControl(t_wrom, sWrom750,  22.5, 18.5, QTime(7,10), QTime(21,0));

    // Wohn- und Esszimmer
    SHactuator *sLRom1000 = new SHactuator(NodeIdSolarheat, 1115, SHactuator::SensorType::Switch, "http://192.168.178.68");
    HeatControl *controlKamn1000 = new HeatControl(t_lrom, sLRom1000, 22.5, 18.5, QTime(7,12), QTime(23,0));
    SHactuator *sWall900 = new SHactuator(NodeIDWallLR, 910, SHactuator::SensorType::Switch, "http://192.168.178.30");
    HeatControl *controlWall900 = new HeatControl(t_lrom, sWall900, 22.5, 18.5, QTime(7,14), QTime(23,0));
    SHactuator *sWall450 = new SHactuator(NodeIDWallM, 455, SHactuator::SensorType::Switch, "http://192.168.178.96");
    HeatControl *controlWall450 = new HeatControl(t_lrom, sWall450, 22.5, 18.5, QTime(7,16), QTime(23,0));
    SHactuator *sFlor150 = new SHactuator(NodeIdSolarheatFloor, 155, SHactuator::SensorType::Switch, "http://192.168.178.63");
    HeatControl *controlFlor150  = new HeatControl(t_lrom, sFlor150,  22.5, 18.5, QTime(7,16), QTime(23,0));

    // Heißwasser:
    SHactuator *sHW_60_2500 = new SHactuator(NodeIdHW_60, 2500, SHactuator::SensorType::Switch, "http://192.168.178.122");
    SHactuator *sHW_40_2500 = new SHactuator(NodeIdHW_40, 2500, SHactuator::SensorType::Switch, "http://192.168.178.74");
    HotwaterControl *controlHW_60_2500 = new HotwaterControl(sHW_60_2500);
    HotwaterControl *controlHW_40_2500 = new HotwaterControl(sHW_40_2500);


    // solar controllers
    m_solarControl =
            new SolarControl(
                    central, /* solar, */
                    /* {wash, ecar, airc}, */
                    {controlHW_40_2500, controlHW_60_2500, controlKamn1000, controlWall900, controlWRom750, controlWall450, controlBath300, controlFlor150} // absteigende Sortierung bzw. Priorisierung
                );


    // Ping: TODO

    // einlesen:


    HeatControl::readCSV();

    // Timer starten:
    startTimers();

    // WebSocketServer
    startWebSocketServer();


    // SHsensor::displayMem("Server::Server ENDE");
}


/*
 *
 */
Server::Server() // private: nur zum Testen
{
    SHsensor::initStatics(nullptr);
}

/*
 *
 */
void Server::startTimers()
{
    // Timer zum regelmäßigen Übersenden der Werte an den / die Clients:
    QTimer *submitTimer = new QTimer;
    connect(submitTimer, &QTimer::timeout, this, &Server::showSensorValues);
    submitTimer->start(17 * 1000); // ms
    // Kontrolle der Zeitstempel:
    QTimer *timeStampTimer = new QTimer(this);
    connect(timeStampTimer, &QTimer::timeout, this, &SHsensor::checkTimeStampALL);
    timeStampTimer->start(SHsensor::CONTROL_INTERVALL * 1000); // ms


    // heatControl Timer:
    QTimer *heatControlTimer = new QTimer(this);
    connect(heatControlTimer, &QTimer::timeout, this, &HeatControl::processALL);
    heatControlTimer->start(61 * 1000); // ms

    // SolarControl Timer:
    QTimer *SolarControlTimer = new QTimer(this);
    connect(SolarControlTimer, &QTimer::timeout, m_solarControl, &SolarControl::process);
    SolarControlTimer->start(61 * 1000); // ms

    // SHsensor::displayMem("Server::startTimers ENDE");
}



/*
 *
 */
void Server::startWebSocketServer()
{
    for (int i=10; i>=0; i--) { // wait some time for previous TCP connection to get started
        qDebug() << "Count down" << i;
        sleep(1);
    }

    m_webSocketServer = new QWebSocketServer(QStringLiteral("SH Server"), QWebSocketServer::NonSecureMode, this);

    qDebug() << "webSocketServer Interface started";

    const quint16 port = 1235;

    if (m_webSocketServer->listen(QHostAddress::Any, port)) {
        qDebug() << "webSocketServer listening on port " + QString::number(port);

        connect(m_webSocketServer, &QWebSocketServer::newConnection,
                this, &Server::onNewClientConnection);
        // connect(m_webSocketServer, &QWebSocketServer::closed,
        //        this, &Server::closed);
        // mutex.unlock();
    } else {
        qDebug() << "ERROR: m_pWebSocketServer->listen " + QString::number(port);
    }

    // SHsensor::displayMem("Server::startWebSocketServer ENDE");
}






/*
 *
 */
void Server::sendToClient(const QJsonObject & jsonObj)
{
    mutex.lock();

    QWebSocket *pClient = nullptr;

    if (m_webSocketClients.size() > 0)
         pClient = qobject_cast<QWebSocket *>(sender());

    // find matching client
    for (int i=0; i < m_webSocketClients.size(); i++)
        if (m_webSocketClients[i] == pClient) {
            // send the json message
            QString message = QJsonDocument(jsonObj).toJson(QJsonDocument::Compact);

            if (m_webSocketClients[i]) {
                // qDebug() << "Sending to " + QString::number(application->getClientID());

                m_webSocketClients[i]->sendTextMessage(message);
            } else
                qDebug() << "Sending ERROR message '" + message + "' to " << i;

            break;
    }


    mutex.unlock();

}




#ifdef OLD
/*
 *
 */
void Server::startSerial() {
    m_serial = nullptr; // weil im Fehlerfall diese Methode erneut aufgerufen wird (siehe connect unten)

    const char blankString[] = QT_TRANSLATE_NOOP("SettingsDialog", "N/A");
    const auto infos = QSerialPortInfo::availablePorts();

    QString port;

    // Infos einsammeln:
    for (const QSerialPortInfo &info : infos) {
        QStringList list;
        QString description = info.description();
        QString manufacturer = info.manufacturer();
        QString serialNumber = info.serialNumber();

        list << info.portName()
             << (!description.isEmpty() ? description : blankString)
             << (!manufacturer.isEmpty() ? manufacturer : blankString)
             << (!serialNumber.isEmpty() ? serialNumber : blankString)
             << info.systemLocation()
             << (info.vendorIdentifier() ? QString::number(info.vendorIdentifier(), 16) : blankString)
             << (info.productIdentifier() ? QString::number(info.productIdentifier(), 16) : blankString);

        qDebug() << list;

        if (not manufacturer.isEmpty())
            port = info.systemLocation();
    }


    if (not port.isEmpty()) {
        // Öffnen:
        m_serial = new QSerialPort(this);

        connect(m_serial, &QSerialPort::readyRead, this, &Server::onReadyReadSensorValueSerial);
        // connect(m_serial, &QSerialPort::errorOccurred, this, &Server::startSerial);// d.h. reconnect serial

        m_serial->setPortName(port);
        m_serial->setBaudRate(115200);
        m_serial->setDataBits(QSerialPort::DataBits::Data8);
        m_serial->setParity(QSerialPort::Parity::NoParity);
        m_serial->setStopBits(QSerialPort::StopBits::OneStop);
        m_serial->setFlowControl(QSerialPort::FlowControl::NoFlowControl);

        qDebug() << "open" << port << "...";

        if (m_serial->open(QIODevice::ReadWrite))
            qDebug() << "Serielle Schnittstelle zu" << port;
        else
            qDebug() << "Keine serielle Schnittstelle";
    } else {
        qDebug() << "Keine serielle Schnittstelle";
    }

    SHactuator::setSerial(m_serial);

    // SHsensor::displayMem("Server::startSerial ENDE");

}
#endif // OLD



/*
 *
 */
void Server::showSensorValues()
{

    SHsensor::printALL();
    SHsensor::pollTasmota();

    // SHsensor::displayMem("Server::showSensorValues ENDE");

}




/*
 *
 */
static bool getValueFromJSON(const QJsonObject & jsonInput, const QVector<QString> &P, QString & retValue) {
    QJsonObject jsonObj = jsonInput;

    for (int i = 0; i<P.size()-1; i++) {
        QString p = P[i];

        QJsonValue value = jsonObj.value(p);
//        qDebug() << value;
        jsonObj = value.toObject();
//        qDebug() << "jsonObj" << jsonObj;
    }

    // last:

    /* in case of string value get value and convert into string*/
    QString last = P[P.size()-1];
//    qDebug() << "last: " << jsonObj[last];
    QJsonValue value = jsonObj[last];
//    qDebug() << "value:" << value.toString();

    /* in case of array get array and convert into string*/
//    qWarning() << tr("QJsonObject[appName] of value: ") << item["imp"];
//    QJsonArray test = item["imp"].toArray();
//    qWarning() << test[1].toString();


    if (value.isDouble())
        retValue = QString::number(value.toDouble());
    else if (value.isArray()) {
        QJsonArray qja = value.toArray();

        QJsonValue v0 = qja[0];
        QJsonValue v1 = qja[1];
        QJsonValue v2 = qja[2];

        if (v0.isDouble() and v1.isDouble() and v2.isDouble()) // solar power
            retValue = QString::number(-(v0.toDouble() + v1.toDouble() + v2.toDouble()));
        else
            retValue = "";
    } else
        retValue = value.toString();

    if (retValue == "")
        return false;

    return true;

}

/*
 *
 */
void Server::processTasmotaMsg(QString jsonMSG)
{

    // parse the json message:
    QJsonParseError err;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonMSG.toUtf8(), &err);

    if (err.error != QJsonParseError::NoError) {
        qDebug() << "# ERROR Server::tasmotaReply(): wrong JSON format";

        return;
    }

    QJsonObject jsonObj = jsonDoc.object();

    QString devName, state, value;
    bool isError = true;

    if (getValueFromJSON(jsonObj, {"POWER"}, state)) {
        // qDebug() << "# ACK message" << state;
        isError = false;
    } else if (getValueFromJSON(jsonObj, {"Status","DeviceName"}, devName) and
                getValueFromJSON(jsonObj, {"StatusSTS", "POWER"}, state) and
                getValueFromJSON(jsonObj, {"StatusSNS", "ENERGY", "Power"}, value)) {
        // neuer Wert für NOUS:

        int nid = SHsensor::nid(devName);

        // qDebug() << "# message received for " << devName << "NID" << nid;

        if (nid != -1) {
            SHsensor::setValue(nid, SID_RELAY, state);
            SHsensor::setValue(nid, SID_POWER, value);

            // qDebug() << "# message values" << devName << state << value;

            isError = false;
        }
    } else if (getValueFromJSON(jsonObj, {"Status","DeviceName"}, devName) and
               getValueFromJSON(jsonObj, {"StatusSNS", "DS18B20", "Temperature"}, value)) {
        // neuer Temperatur Wert:

       int nid = SHsensor::nid(devName);

       // qDebug() << "# message received for " << devName << "NID" << nid;

       if (nid != -1) {
           SHsensor::setValue(nid, SID_TEMP, value);

           // qDebug() << "# message values" << devName << value;

           isError = false;
       }
   }

    if (isError)
        qDebug() << "# invalid HTTP reply message: ignored";

}


/*
 *
 */
void Server::tasmotaReply(QNetworkReply *reply)
{
    // SHsensor::displayMem("Server::tasmotaReply");

    if (reply->error()) {
        qDebug() << reply->errorString();
        return;
    }

    QString jsonMSG = reply->readAll();

    reply->deleteLater(); // sonst Memory Leak

    processTasmotaMsg(jsonMSG);

    // SHsensor::displayMem("Server::tasmotaReply ENDE");

}







/*
 *
 */
void Server::onReadyReadSensorValueTCP()
{
    // SHsensor::displayMem("Server::onReadyReadSensorValueTCP");

    QByteArray data = m_gatewayTCPsocket.readAll();
    qDebug() << "TCPMsg=" << data;

    SHmessage msg(data);

    if (msg.command() == C_SET)
        SHsensor::setValue(msg.nodeId(), msg.sensorId(), msg.payload());

    // SHsensor::displayMem("Server::onReadyReadSensorValueTCP ENDE");
}





#ifdef OLD
/*
 *
 */
void Server::onReadyReadSensorValueSerial()
{
    // SHsensor::displayMem("Server::onReadyReadSensorValueSerial");

    QByteArray collected;

    const QByteArray data = m_serial->readAll();
    m_serial->clear();

    // qDebug() << "SerialMsg=" << data;


    for (auto i : data) {
        collected = collected + i;

        if (i == '\n') {
            // QString qs = QString(collected);
            // qDebug() << qs;

            SHmessage * msg = new SHmessage(collected);

            if (not msg->error() and msg->command() == C_SET)
                SHsensor::setValue(msg->nodeId(), msg->sensorId(), msg->payload());

            delete msg;
            collected.clear();

            // SHsensor::displayMem("Server::onReadyReadSensorValueSerial CLEAR");
        }

    }

    // SHsensor::displayMem("Server::onReadyReadSensorValueSerial ENDE");
}

#endif // OLD



/*
 *
 */
void Server::onNewClientConnection()
{
    mutex.lock();

    QWebSocket *pSocket = m_webSocketServer->nextPendingConnection();

    if (m_webSocketClients.size() < maxClientNo) {
        if (pSocket and pSocket->peerPort() != 0) {
            connect(pSocket, &QWebSocket::textMessageReceived, this, &Server::processClientMessage);
            // connect(pSocket, &QWebSocket::binaryMessageReceived, this, &ServerInterface::processBinaryMessage);
            connect(pSocket, &QWebSocket::disconnected, this, &Server::socketClientDisconnected);

            m_webSocketClients.push_back(pSocket);

            qDebug() << "New Connection No" << m_webSocketClients.size() << "ID" << pSocket->peerPort();
            // showClientsInfo();
        } else
            qDebug() << "No new Connection (strange) " + QString::number(pSocket->peerPort());
    } else
        qDebug() << "No new Connection (too many clients) " + QString::number(pSocket->peerPort());

    mutex.unlock();

}






/*
 *
 */
void Server::processClientMessage(QString message)
{
    webSocketMsg wmsg(message);

    if (not wmsg.corrupted()) {
        if (wmsg.request()) { // fulfill request
            QJsonObject jsonObj = wmsg.processGet();
            sendToClient(jsonObj);
        } else { // set value / mode:
            wmsg.processSet();
        }
    }
}


/*
 *
 */
void Server::socketClientDisconnected()
{
    mutex.lock();

    QWebSocket *pClient = nullptr;

    if (m_webSocketClients.size() > 0)
         pClient = qobject_cast<QWebSocket *>(sender());

    // find disconnected client and erase from vector
    for (int i=0; i < m_webSocketClients.size(); i++)
        if (m_webSocketClients[i] == pClient) {
            m_webSocketClients.erase(m_webSocketClients.begin()+i);

            qDebug() << "Delete Connection No" << i << "ID" << pClient->peerPort();

            break;
    }

    // showClientsInfo();

    mutex.unlock();


}