Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
shsensor.cpp 13.68 KiB
#include "shsensor.h"
#include <QTimer>
#include <QtGlobal>

int SHsensor::m_sensors = 0;
bool SHsensor::m_testing = false;
QDateTime * SHsensor::m_testTime = nullptr;
SHsensor * SHsensor::S[SHNodeIdSize * SHSensorIdSize] = {nullptr};
QMap<QString,int> SHsensor::NID;
QNetworkAccessManager * SHsensor::m_tasmota = nullptr;

QString SHsensor::m_brutto = NOVALUE;
QString SHsensor::m_netto = NOVALUE;
QStringList SHsensor::m_info = {};

QString SHsensor::m_errcnt_total = NOVALUE;
QString SHsensor::m_errcnt_current = NOVALUE;
QString SHsensor::m_err_solar = NOVALUE;



/*
 * Konstruktor
 */
SHsensor::SHsensor(/* QString name,*/ int nid, SHsensor::SensorType type, QString url) :
    m_nodeID(nid),
    // m_name(name),
    m_url(url),
    m_type(type),
    m_value(NOVALUE),
    m_timestamp(SHsensor::currentTime())
{
    if (nid < 0 or nid >= SHNodeIdSize)
        throw std::invalid_argument("SHsensor::SHsensor: nid out of range");

    switch (m_type) {
    case SHsensor::none:
        m_unit = "?";
        m_sensorID = SID_DUMMY;
        break;

    case SHsensor::tempSensor:
        m_unit = "C";
        m_sensorID = SID_TEMP;
        break;

    case SHsensor::powerSensor:
    case SHsensor::lightSensor:
        m_unit = "W";
        m_sensorID = SID_POWER;
        break;

    case SHsensor::Switch:
        m_unit = "";
        m_sensorID = SID_RELAY;
        break;

    case SHsensor::Switch2:
        m_unit = "";
        m_sensorID = SID_RELAY2;
        break;

    default:
        throw std::invalid_argument("SHsensor::SHsensor: type out of range");
    }

    // Kontrolle der Zeitstempel
//    QTimer *timer = new QTimer(this);

//    connect(timer, &QTimer::timeout, this, &SHsensor::checkTimeStamp);
//    timer->start(CONTROL_INTERVALL * 1000); // ms

    // Zähler für Konsistenz
    m_sensors++;

    // Hinzufügen:
    m_pos = hash(m_nodeID, m_sensorID);

    // qDebug() << m_name << pos;

    if (S[m_pos] != nullptr)
        throw std::invalid_argument("SHsensor::SHsensor: nid=" + QString::number(m_nodeID).toStdString() + "/ sid=" + QString::number(m_sensorID).toStdString() + " not unique");

    S[m_pos] = this;

    m_error = noError;
}

/*
 *
 */
SHsensor::~SHsensor()
{
    S[m_pos] = nullptr;

    m_sensors--;
}


/*
 *
 */
int SHsensor::pos() const
{
    return m_pos;
}


/*
 *
 */
bool SHsensor::tasmota() const
{
    return (m_url != "");
}










/*
 *
 */
int SHsensor::sensorID() const
{
    return m_sensorID;
}






/*
 *
 */
int SHsensor::nodeID() const
{
    return m_nodeID;
}





/*
 *
 */
QDateTime SHsensor::timestamp() const
{
    return m_timestamp;
}




/*
 *
 */
QString SHsensor::value() const
{
    if (isError())
        return ("F" + QString::number((int) m_error));
    else
        return m_value;
}




/*
 *
 */
QString SHsensor::unit() const
{
    return m_unit;
}




/*
 *
 */
QString SHsensor::name() const
{
    return SHsensor::name(m_nodeID);
}





/*
 *
 */
void SHsensor::checkTimeStamp()
{
    // qDebug() << "SHsensorBase::checkTimeStamp()";

    QDateTime time = m_timestamp.addSecs(CONTROL_INTERVALL);

    if (SHsensor::currentTime() > time) {
        setValue(NOVALUE); // wird zu F1 error
    }

}






/*
 * Nur für Tasmota und Temp
 */
void SHsensor::setNervousTemp(const QString &payload)
{
    const int noAvg = 30;

    if (payload != NOVALUE) {
        float temp = payload.toFloat();

        if (T.size() >= noAvg) {
            Tsum -= T[0];
            T.pop_front();
        }

        T.push_back(temp);
        Tsum += temp;

        // Durchschnitt berechnen
        int Tint = Tsum/T.size() * 10; // auf eine Nachkommastelle beschränken

        m_value = QString::number(Tint / 10.0);
    } else {
        if (T.size() > 0) {
            Tsum -= T[0];
            T.pop_front();
        }

        m_value = NOVALUE;
    }

}




/*
 *
 */
int SHsensor::sensors()
{
    return m_sensors;
}





/*
 *
 */
static QString errorTxt(SHsensor::ErrorType error) {
    switch (error) {
    case SHsensor::ErrorType::noError:
        return "";
        break;

    case SHsensor::ErrorType::noValueError:
        return "Kein Wert";
        break;

    case SHsensor::ErrorType::stupidValueError:
        return "Fehlerhafter Wert";
        break;

    case SHsensor::ErrorType::switchError:
        return "Schalter reagiert nicht";
        break;
    }

    return "Unbekannter Fehler";
}





/*
 *
 */
QString SHsensor::errorTxt() const
{
    return ::errorTxt(m_error);
}




/*
 *
 */
QString SHsensor::prevErrorTxt() const
{
    return ::errorTxt(m_previousError);
}




/*
 *
 */
void SHsensor::poll() const
{
    if (not tasmota())
        return;

    QString cmd = "cm?cmnd=Status0";

    QString urlRequest = m_url + "/" + cmd;

    QNetworkRequest request;

    // qDebug() << "polling tasmota" << name();

    request.setUrl(QUrl(urlRequest));
    m_tasmota->get(request);

}




/*
 *
 */
void SHsensor::setValue(const QString &payload)
{
    // qDebug() << "setValue::setValue(nid=" << m_nodeID << "sid=" << m_sensorID << "payl=" << payload << ")";

    // qDebug() <<  "-> 1 isError" <<  isError();
    // qDebug() << m_errorIndicated << m_error << m_previousError;

    QString payl = payload;


    if (payl != NOVALUE) {
        m_timestamp = SHsensor::currentTime();

        resetError(noValueError);
    } else {
        setError(noValueError);
    }


    // Konsistenz-Check für bestimmte Sensoren:

    if (payl != NOVALUE) {
        float fvalue;

        switch (m_type) {
        case SHsensor::tempSensor:
            fvalue = payl.toFloat();

            if (fvalue < 0.0 or fvalue > 45.0) {
                setError(stupidValueError);

                payl = NOVALUE;
            } else {
                resetError(stupidValueError);
            }
            break;

        case SHsensor::powerSensor:
            fvalue = payl.toFloat();

            if (fvalue < 0.0) {
                setError(stupidValueError);

                payl = NOVALUE;
            } else {
                resetError(stupidValueError);
            }
            break;

        case SHsensor::lightSensor:
        case SHsensor::none:
        case SHsensor::Switch:
        case SHsensor::Switch2:
            break;

        default:
            throw std::invalid_argument("SHsensor::SHsensor: type out of range");
        }
    }

//    if (tasmota() and m_type == SHsensor::tempSensor) // wird nicht mehr gebraucht
//        setNervousTemp(payl);
//    else
        m_value = payl;

}




/*
 *
 */
bool SHsensor::isTesting()
{
    return m_testing;
}


/*
 *
 */
void SHsensor::setTesting()
{
    m_testTime = new QDateTime;
    m_testing = true;
}





/*
 *
 */
void SHsensor::setTestTime(const QDateTime &time)
{
    assert(m_testing);

    *m_testTime = time;
}





/*
 *
 */
QDateTime SHsensor::currentTime()
{
    if (m_testing) {
        assert(m_testTime != nullptr);

        return *m_testTime;
    } else {
        return QDateTime::currentDateTime();
    }
}






/*
 *
 */
void SHsensor::displayMem(QString comment)
{
    QString command = "top -b -o +%MEM | head -n 10 | grep SHserver";
//    QString command = "ssh pi@192.168.178.23 \"top -b -o +%MEM | head -n 10 | grep SHserver\"";
    FILE* fp;
    char* result = NULL;
    size_t len = 0;

    fflush(NULL);
    fp = popen(command.toStdString().c_str(), "r");
    if (fp == NULL) {
        qDebug() << "Cannot execute command:" << command;
        return;
    }

    QString qs;

    while(getline(&result, &len, fp) != -1) {
        // fputs(result, stdout);

        qs += result;
    }

    // qDebug() << "result=" << result;
    // qDebug() << "qs=" << qs;

    free(result);
    fflush(fp);
    if (pclose(fp) != 0) {
        perror("Cannot close stream.\n");
    }


    QStringList qsl = qs.split(' '/* QT6, Qt::SkipEmptyParts*/);
    // qDebug() << qsl;

    static int index = 0;
    static int cnt = 0;

    while (qsl[index] == "" or cnt < 5) {
        index++;

        if (qsl[index] != "")
            cnt++;
    }

    static int prevsize = 0;
    int memsize = qsl[index].toInt();

    if (memsize > prevsize)
        qDebug() << "#" << comment << "memsize" << memsize << ">" << prevsize;
    else
        qDebug() << "#" << comment << "memsize" << memsize;

    prevsize = memsize;

}


/*
 *
 */
void SHsensor::checkTimeStampALL()
{
    for (auto s : S)
        if (s != nullptr)
            s->checkTimeStamp();
}






/*
 * static
 */


int SHsensor::hash(int nid, int sid)
{
    if (nid < 0 or nid >= SHNodeIdSize or sid < 0 or sid >= SHSensorIdSize) {
        qDebug() << "hash out of range" << nid << sid;

        return -1;
    }

//    assert(nid >= 0);
//    assert(nid < SHNodeIdSize);

//    assert(sid >= 0);
//    assert(sid < SHSensorIdSize);

    return nid * SHSensorIdSize + sid;
}


/*
 *
 */
void SHsensor::printALL() {
    static int errcnt_total = 0;
    int errcnt = 0;
    bool errsolar = false;
    // SHsensor::displayMem("SHsensor::printALL");

    QString line = QDate::currentDate().toString("dd.MM.yyyy") + " " + QTime::currentTime().toString("hh:mm:ss");

    for (auto s : S)
        if (s != nullptr) {
            line = line + " " + s->name() + ":" + s->value() + s->unit();
//            qDebug() << s->name() << s->unit() << s->value() << s->timestamp();
            if (s->isError()) {
                errcnt++;

                if (s->type() == SHsensor::SensorType::lightSensor)
                    errsolar = true;
            }

        }

    qDebug() << line;
    // SHsensor::displayMem("SHsensor::printALL ENDE");

    errcnt_total += errcnt;

    SHsensor::setErrorInfo(errcnt_total, errcnt, errsolar);
}



/*
 *
 */
void SHsensor::pollTasmota() {
    // SHsensor::displayMem("SHsensor::pollTasmota");

    static QVector<SHsensor*> T; // nur Tasmota und keine doppelten Namen
    static bool firstRun = true;

    if (firstRun) { // T einmalig erstellen
        for (auto s : S)
            if (s != nullptr and s->tasmota()) {
                bool found = false;

                for (auto t : T)
                    if (t->name() == s->name())
                        found = true;

                if (not found)
                    T.push_back(s);
            }

        firstRun = false;
    }

    for (auto s : T)
        if (s != nullptr)
            s->poll();

    // SHsensor::displayMem("SHsensor::pollTasmota ENDE");

}


/*
 *
 */
void SHsensor::setValue(int nid, int sid, const QString &payl)
{
    // qDebug() << "static SHsensor::setValue(nid=" << nid << "sid=" << sid << "payl=" << payl << ")";

    int pos = hash(nid, sid);

    // qDebug() << "pos" << pos << "S[pos]" << S[pos];

    if (pos != -1 and S[pos] != nullptr) {
        // qDebug() << "call setValue" << nid << sid;
        S[pos]->setValue(payl);
    }

}


/*
 *
 */
QString SHsensor::getValue(int nid, int sid)
{
    int pos = hash(nid, sid);

    if (pos == -1 or S[pos] == nullptr)
        return "ERRnoValue";
    else
        return S[pos]->value();
}



/*
 *
 */
QString SHsensor::name(int nid)
{
    static QString N[SHNodeIdSize] = {
        "Gateway",
        "HW_40", // Heißwasser Stufe 1
        "Kamin", // große Heizung im Wohnzimmer, Stufe 2 gibt es nicht mehr
        "Sonne", // Lichtsensor
        "HW_60", // Heißwasser Stufe 2
        "Tür",  // Fußboden-Heizung im Wohnzimmer
        "Klima", // Klimaanlage
        "Bad oben",  // Heizung Bad oben
        "Wasch", // Waschmaschine
        "Wohn", // Thermometer im Wohnzimmer
        "Repeater2", // Repeater
        "eCar", // E-Auto
        "T Bad unten", // Thermometer Bad unten
        "T Bad oben", // Thermometer Bad oben
        "WandLR", // Wohnzimmer-Wand links+rechts
        "WandM", // Wohnzimmer-Wand mitte
        "Bad unten", // Heizung Bad unten
        "Strom", // Zentraler Strommesser
        "Dummy" // zum Testen neuer Knoten -- immer ganz hinten
    };

    if (nid < 0 or nid >= SHNodeIdSize)
        return "#ERR-NAME#";

    return N[nid];

}


/*
 *
 */
void SHsensor::initStatics(QNetworkAccessManager *tasmota)
{
    m_tasmota = tasmota;

    for (int i=0; i < SHNodeIdSize; i++) {
        if (NID.contains(name(i)))
            throw std::invalid_argument("ERROR double use of " + name(i).toStdString());

        NID[name(i)] = i;
    }

}



/*
 *
 */
int SHsensor::nid(QString name)
{
    if (NID.contains(name))
        return NID[name];
    else
        return -1;
}




/*
 *
 */
SHsensor::ErrorType SHsensor::error() const
{
    return m_error;
}



/*
 *
 */
void SHsensor::setError(ErrorType newError)
{
    if (not isError()) { // keine Aktion falls es bereits einen Fehler gibt
        m_error = newError;
        m_errorTime = m_timestamp;
    }
}




/*
 *
 */
void SHsensor::resetError(ErrorType oldError)
{
    if (m_error == oldError) {
        m_previousError = m_error;
        m_previousErrorTime = m_errorTime;
        m_error = noError;
    }
}




/*
 *
 */
bool SHsensor::isError() const
{
    return (m_error != noError);
}





/*
 *
 */
SHsensor::SensorType SHsensor::type() const
{
    return m_type;
}




/*
 *
 */
void SHsensor::setSolarInfo(int newBrutto, int newNetto, const QStringList &newInfo)
{
    SHsensor::m_brutto = QString::number(newBrutto);
    SHsensor::m_netto = QString::number(newNetto);
    SHsensor::m_info = newInfo;
}


/*
 *
 */
void SHsensor::setErrorInfo(int errcnt_total, int errcnt_current, bool err_solar)
{
    SHsensor::m_errcnt_total = QString::number(errcnt_total);
    SHsensor::m_errcnt_current = QString::number(errcnt_current);

    if (err_solar)
        SHsensor::m_err_solar = "ERROR";
    else
        SHsensor::m_err_solar = "OK";
}



/*
 *
 */
QStringList SHsensor::getInfo() {
    QStringList qsl = {
        SHsensor::m_brutto, SHsensor::m_netto, SHsensor::m_info.join(";"), // solar
        SHsensor::m_errcnt_total, SHsensor::m_errcnt_current, SHsensor::m_err_solar // errors
    };

    return qsl;
}

/*
 *
 */
const QString &SHsensor::url() const
{
    return m_url;
}