Skip to content
Snippets Groups Projects
server.cpp 17 KiB
Newer Older
  • Learn to ignore specific revisions
  • Peter Altenbernd's avatar
    Peter Altenbernd committed
    #include "server.h"
    #include "SHmessage.h"
    
    #include "webSocketMsg.h"
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    #include <QDebug>
    #include <QHostAddress>
    #include <QTimer>
    
    #include <unistd.h>
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    /*
     * Konstruktor
     */
    Server::Server(QString TCPhostName, QObject *parent) :
        QObject(parent),
        m_gatewayHostName(TCPhostName),
        m_gatewayTCPsocket(this)
    {
    
        qDebug() << "Starting ... ";
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        // 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);
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        // 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:
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        // Sensoren:
        // temparature
    
        SHsensor *t_wrom = new SHsensor(NodeIDBadObenT, 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");
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        // producer
    
        // SHsensor *solar = new SHsensor(NodeIdLightSensor, SHsensor::lightSensor);
        SHsensor *central = new SHsensor(NodeIDCentral, SHsensor::lightSensor, "http://192.168.178.142");
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        // 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");
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        // Repeater:
        // SHsensor *dummy = new SHsensor("Dummy", NodeIdDummy, SHsensor::none); addAllSensors einbeziehen
    
        // SHsensor *repeat2 = new SHsensor(NodeIDRepeater2, SHsensor::none);
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    
        //
        // 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));
    
    
        // Bad oben:
        SHactuator *sBathUp400 = new SHactuator(NodeIDBadOben, 400, SHactuator::SensorType::Switch, "http://192.168.178.24");
        HeatControl * controlBathUp400  = new HeatControl(t_wrom, sBathUp400,  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:
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        SHactuator *sHW_60_2500 = new SHactuator(NodeIdHW_60, 2550, SHactuator::SensorType::Switch, "http://192.168.178.122");
        SHactuator *sHW_40_2500 = new SHactuator(NodeIdHW_40, 2550, 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);
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    
        // solar controllers
        m_solarControl =
                new SolarControl(
    
                        central, /* solar, */
                        /* {wash, ecar, airc}, */
    
                        {controlHW_40_2500, controlHW_60_2500, controlKamn1000, controlWall900, controlWall450, controlBathUp400, controlBath300, controlFlor150} // absteigende Sortierung bzw. Priorisierung
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
                    );
    
    
        // Ping: TODO
    
        // einlesen:
    
    
        HeatControl::readCSV();
    
        // Timer starten:
        startTimers();
    
        // WebSocketServer
        startWebSocketServer();
    
    
    
        // SHsensor::displayMem("Server::Server ENDE");
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    }
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    /*
     *
     */
    Server::Server() // private: nur zum Testen
    {
        SHsensor::initStatics(nullptr);
    }
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    /*
     *
     */
    void Server::startTimers()
    {
    
        // Timer zum regelmäßigen Abfragen der Werte von den Clients inkl. Status-Anzeige:
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        QTimer *submitTimer = new QTimer;
        connect(submitTimer, &QTimer::timeout, this, &Server::showSensorValues);
    
        submitTimer->start(SHsensor::POLLING_INTERVALL * 1000); // ms
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    
        // Kontrolle der Zeitstempel: wenn man länger nichts hört wird auf NOVALUE gesetz
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        QTimer *timeStampTimer = new QTimer(this);
        connect(timeStampTimer, &QTimer::timeout, this, &SHsensor::checkTimeStampALL);
        timeStampTimer->start(SHsensor::CONTROL_INTERVALL * 1000); // ms
    
    
    
        // heatControl Timer: für Geräte, die ON/OFF (oder veraltet im Timer-Modus) gesteuert werden
        // TODO: unklar, ob das noch gebraucht wird
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        QTimer *heatControlTimer = new QTimer(this);
        connect(heatControlTimer, &QTimer::timeout, this, &HeatControl::processALL);
    
        heatControlTimer->start(23 * 1000); // ms
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    
        // SolarControl Timer: Energie-Management
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        QTimer *SolarControlTimer = new QTimer(this);
        connect(SolarControlTimer, &QTimer::timeout, m_solarControl, &SolarControl::process);
    
        SolarControlTimer->start(23 * 1000); // ms
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    
        // SHsensor::displayMem("Server::startTimers ENDE");
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    }
    
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    /*
     *
     */
    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);
        }
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        m_webSocketServer = new QWebSocketServer(QStringLiteral("SH Server"), QWebSocketServer::NonSecureMode, this);
    
    
        qDebug() << "webSocketServer Interface started";
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        const quint16 port = 1235;
    
        if (m_webSocketServer->listen(QHostAddress::Any, port)) {
    
            qDebug() << "webSocketServer listening on port " + QString::number(port);
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
            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");
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    }
    
    
    
    
    
    
    /*
     *
     */
    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;
    
    /*
     *
     */
    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");
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    /*
     *
     */
    void Server::showSensorValues()
    {
    
        SHsensor::printALL();
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        SHsensor::pollTasmota();
    
    
        // SHsensor::displayMem("Server::showSensorValues ENDE");
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    /*
     *
     */
    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();
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        if (retValue == "")
            return false;
    
        return true;
    
    }
    
    /*
     *
     */
    void Server::processTasmotaMsg(QString jsonMSG)
    {
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        // 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();
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        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:
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
            int nid = SHsensor::nid(devName);
    
    
            // qDebug() << "# message received for " << devName << "NID" << nid;
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
            if (nid != -1) {
                SHsensor::setValue(nid, SID_RELAY, state);
    
                SHsensor::setValue(nid, SID_POWER, value);
    
                // qDebug() << "# message values" << devName << state << value;
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
                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;
           }
       }
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        if (isError)
            qDebug() << "# invalid HTTP reply message: ignored";
    
    }
    
    
    /*
     *
     */
    void Server::tasmotaReply(QNetworkReply *reply)
    {
    
        // SHsensor::displayMem("Server::tasmotaReply");
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        if (reply->error()) {
            qDebug() << reply->errorString();
            return;
        }
    
        QString jsonMSG = reply->readAll();
    
    
        reply->deleteLater(); // sonst Memory Leak
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        processTasmotaMsg(jsonMSG);
    
    
        // SHsensor::displayMem("Server::tasmotaReply ENDE");
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
    /*
     *
     */
    
    void Server::onReadyReadSensorValueTCP()
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    {
    
        // SHsensor::displayMem("Server::onReadyReadSensorValueTCP");
    
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
        QByteArray data = m_gatewayTCPsocket.readAll();
    
        qDebug() << "TCPMsg=" << data;
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    
        SHmessage msg(data);
    
        if (msg.command() == C_SET)
            SHsensor::setValue(msg.nodeId(), msg.sensorId(), msg.payload());
    
    
        // SHsensor::displayMem("Server::onReadyReadSensorValueTCP ENDE");
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    }
    
    
    /*
     *
     */
    void Server::onReadyReadSensorValueSerial()
    {
    
        // SHsensor::displayMem("Server::onReadyReadSensorValueSerial");
    
    
        QByteArray collected;
    
        const QByteArray data = m_serial->readAll();
    
        // 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());
    
                collected.clear();
    
    
                // SHsensor::displayMem("Server::onReadyReadSensorValueSerial CLEAR");
    
    
        // SHsensor::displayMem("Server::onReadyReadSensorValueSerial ENDE");
    
    Peter Altenbernd's avatar
    Peter Altenbernd committed
    /*
     *
     */
    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();
    
    
    }