File indexing completed on 2024-04-21 05:31:42

0001 /*
0002     SPDX-FileCopyrightText: 2019 Eike Hein <hein@kde.org>
0003     SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "SensorDataModel.h"
0009 
0010 #include <chrono>
0011 #include <optional>
0012 
0013 #include <QMetaEnum>
0014 
0015 #include "formatter/Formatter.h"
0016 #include "systemstats/SensorInfo.h"
0017 
0018 #include "SensorDaemonInterface_p.h"
0019 #include "sensors_logging.h"
0020 
0021 using namespace KSysGuard;
0022 
0023 namespace chrono = std::chrono;
0024 
0025 class Q_DECL_HIDDEN SensorDataModel::Private
0026 {
0027 public:
0028     Private(SensorDataModel *qq)
0029         : q(qq)
0030     {
0031     }
0032 
0033     void sensorsChanged();
0034     void addSensor(const QString &id);
0035     void removeSensor(const QString &id);
0036 
0037     QStringList requestedSensors;
0038 
0039     QStringList sensors;
0040     QStringList objects;
0041 
0042     QHash<QString, SensorInfo> sensorInfos;
0043     QHash<QString, QVariant> sensorData;
0044     QVariantMap sensorColors;
0045     QVariantMap sensorLabels;
0046 
0047     bool usedByQml = false;
0048     bool componentComplete = false;
0049     bool loaded = false;
0050     bool enabled = true;
0051 
0052     std::optional<qreal> minimum;
0053     std::optional<qreal> maximum;
0054 
0055     std::optional<int> updateRateLimit;
0056     QHash<int, std::chrono::steady_clock::time_point> lastUpdateTimes;
0057 
0058 private:
0059     SensorDataModel *q;
0060 };
0061 
0062 SensorDataModel::SensorDataModel(const QStringList &sensorIds, QObject *parent)
0063     : QAbstractTableModel(parent)
0064     , d(new Private(this))
0065 {
0066     connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorAdded, this, &SensorDataModel::onSensorAdded);
0067     connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorRemoved, this, &SensorDataModel::onSensorRemoved);
0068     connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::metaDataChanged, this, &SensorDataModel::onMetaDataChanged);
0069     connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::valueChanged, this, &SensorDataModel::onValueChanged);
0070 
0071     // Empty string is used for entries that do not specify a wildcard object
0072     d->objects << QStringLiteral("");
0073 
0074     setSensors(sensorIds);
0075 }
0076 
0077 SensorDataModel::~SensorDataModel()
0078 {
0079 }
0080 
0081 QHash<int, QByteArray> SensorDataModel::roleNames() const
0082 {
0083     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0084 
0085     QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles"));
0086 
0087     for (int i = 0; i < e.keyCount(); ++i) {
0088         roles.insert(e.value(i), e.key(i));
0089     }
0090 
0091     return roles;
0092 }
0093 
0094 QVariant SensorDataModel::data(const QModelIndex &index, int role) const
0095 {
0096     const bool check = checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent);
0097     if (!check) {
0098         return QVariant();
0099     }
0100 
0101     auto sensor = d->sensors.at(index.column());
0102     auto info = d->sensorInfos.value(sensor);
0103     auto data = d->sensorData.value(sensor);
0104 
0105     switch (role) {
0106     case Qt::DisplayRole:
0107     case FormattedValue:
0108         return Formatter::formatValue(data, info.unit);
0109     case Value:
0110         return data;
0111     case Unit:
0112         return info.unit;
0113     case Name:
0114         return d->sensorLabels.value(sensor, info.name);
0115     case ShortName: {
0116         auto it = d->sensorLabels.constFind(sensor);
0117         if (it != d->sensorLabels.constEnd()) {
0118             return *it;
0119         }
0120         if (info.shortName.isEmpty()) {
0121             return info.name;
0122         }
0123         return info.shortName;
0124     }
0125     case Description:
0126         return info.description;
0127     case Minimum:
0128         return info.min;
0129     case Maximum:
0130         return info.max;
0131     case Type:
0132         return info.variantType;
0133     case SensorId:
0134         return sensor;
0135     case Color:
0136         if (!d->sensorColors.empty()) {
0137             return d->sensorColors.value(sensor);
0138         }
0139         break;
0140     case UpdateInterval:
0141         // TODO: Make this dynamic once the backend supports it.
0142         return BackendUpdateInterval;
0143     default:
0144         break;
0145     }
0146 
0147     return QVariant();
0148 }
0149 
0150 QVariant SensorDataModel::headerData(int section, Qt::Orientation orientation, int role) const
0151 {
0152     if (orientation == Qt::Vertical) {
0153         return QVariant();
0154     }
0155 
0156     if (section < 0 || section >= d->sensors.size()) {
0157         return QVariant();
0158     }
0159 
0160     auto sensor = d->sensors.at(section);
0161     auto info = d->sensorInfos.value(sensor);
0162 
0163     switch (role) {
0164     case Qt::DisplayRole:
0165     case ShortName:
0166         if (info.shortName.isEmpty()) {
0167             return info.name;
0168         }
0169         return info.shortName;
0170     case Name:
0171         return info.name;
0172     case SensorId:
0173         return sensor;
0174     case Unit:
0175         return info.unit;
0176     case Description:
0177         return info.description;
0178     case Minimum:
0179         return info.min;
0180     case Maximum:
0181         return info.max;
0182     case Type:
0183         return info.variantType;
0184     case UpdateInterval:
0185         // TODO: Make this dynamic once the backend supports it.
0186         return BackendUpdateInterval;
0187     default:
0188         break;
0189     }
0190 
0191     return QVariant();
0192 }
0193 
0194 int SensorDataModel::rowCount(const QModelIndex &parent) const
0195 {
0196     if (parent.isValid()) {
0197         return 0;
0198     }
0199 
0200     return d->objects.count();
0201 }
0202 
0203 int SensorDataModel::columnCount(const QModelIndex &parent) const
0204 {
0205     if (parent.isValid()) {
0206         return 0;
0207     }
0208 
0209     return d->sensorInfos.count();
0210 }
0211 
0212 qreal SensorDataModel::minimum() const
0213 {
0214     if (d->sensors.isEmpty()) {
0215         return 0;
0216     }
0217 
0218     if (d->minimum.has_value()) {
0219         return d->minimum.value();
0220     }
0221 
0222     auto result = std::min_element(d->sensorInfos.cbegin(), d->sensorInfos.cend(), [](const SensorInfo &first, const SensorInfo &second) {
0223         return first.min < second.min;
0224     });
0225     if (result == d->sensorInfos.cend()) {
0226         d->minimum = 0.0;
0227     } else {
0228         d->minimum = (*result).min;
0229     }
0230     return d->minimum.value();
0231 }
0232 
0233 qreal SensorDataModel::maximum() const
0234 {
0235     if (d->sensors.isEmpty()) {
0236         return 0;
0237     }
0238 
0239     if (d->maximum.has_value()) {
0240         return d->maximum.value();
0241     }
0242 
0243     auto result = std::max_element(d->sensorInfos.cbegin(), d->sensorInfos.cend(), [](const SensorInfo &first, const SensorInfo &second) {
0244         return first.max < second.max;
0245     });
0246     if (result == d->sensorInfos.cend()) {
0247         d->maximum = 0.0;
0248     } else {
0249         d->maximum = (*result).max;
0250     }
0251     return d->maximum.value();
0252 }
0253 
0254 QStringList SensorDataModel::sensors() const
0255 {
0256     return d->requestedSensors;
0257 }
0258 
0259 void SensorDataModel::setSensors(const QStringList &sensorIds)
0260 {
0261     if (d->requestedSensors == sensorIds) {
0262         return;
0263     }
0264 
0265     d->requestedSensors = sensorIds;
0266 
0267     if (!d->usedByQml || d->componentComplete) {
0268         d->sensorsChanged();
0269     }
0270     Q_EMIT readyChanged();
0271     Q_EMIT sensorsChanged();
0272 }
0273 
0274 bool SensorDataModel::enabled() const
0275 {
0276     return d->enabled;
0277 }
0278 
0279 void SensorDataModel::setEnabled(bool newEnabled)
0280 {
0281     if (newEnabled == d->enabled) {
0282         return;
0283     }
0284 
0285     d->enabled = newEnabled;
0286     if (d->enabled) {
0287         SensorDaemonInterface::instance()->subscribe(d->sensorInfos.keys());
0288         SensorDaemonInterface::instance()->requestMetaData(d->sensorInfos.keys());
0289     } else {
0290         SensorDaemonInterface::instance()->unsubscribe(d->sensorInfos.keys());
0291     }
0292 
0293     Q_EMIT enabledChanged();
0294 }
0295 
0296 QVariantMap SensorDataModel::sensorColors() const
0297 {
0298     return d->sensorColors;
0299 }
0300 
0301 void SensorDataModel::setSensorColors(const QVariantMap &sensorColors)
0302 {
0303     if (sensorColors == d->sensorColors) {
0304         return;
0305     }
0306     d->sensorColors = sensorColors;
0307     Q_EMIT sensorColorsChanged();
0308     Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), {Color});
0309 }
0310 
0311 QVariantMap SensorDataModel::sensorLabels() const
0312 {
0313     return d->sensorLabels;
0314 }
0315 
0316 void SensorDataModel::setSensorLabels(const QVariantMap &sensorLabels)
0317 {
0318     if (sensorLabels == d->sensorLabels) {
0319         return;
0320     }
0321     d->sensorLabels = sensorLabels;
0322     Q_EMIT sensorLabelsChanged();
0323     Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), {Name, ShortName});
0324 }
0325 
0326 int SensorDataModel::updateRateLimit() const
0327 {
0328     return d->updateRateLimit.value_or(-1);
0329 }
0330 
0331 void SensorDataModel::setUpdateRateLimit(int newUpdateRateLimit)
0332 {
0333     // An update rate limit of 0 or less makes no sense, so treat it as clearing
0334     // the limit.
0335     if (newUpdateRateLimit <= 0) {
0336         if (!d->updateRateLimit) {
0337             return;
0338         }
0339 
0340         d->updateRateLimit.reset();
0341     } else {
0342         if (d->updateRateLimit && d->updateRateLimit.value() == newUpdateRateLimit) {
0343             return;
0344         }
0345 
0346         d->updateRateLimit = newUpdateRateLimit;
0347     }
0348     d->lastUpdateTimes.clear();
0349     Q_EMIT updateRateLimitChanged();
0350 }
0351 
0352 void KSysGuard::SensorDataModel::resetUpdateRateLimit()
0353 {
0354     setUpdateRateLimit(-1);
0355 }
0356 
0357 bool KSysGuard::SensorDataModel::isReady() const
0358 {
0359     return d->sensors.size() == d->sensorInfos.size();
0360 }
0361 
0362 void SensorDataModel::addSensor(const QString &sensorId)
0363 {
0364     d->addSensor(sensorId);
0365 }
0366 
0367 void SensorDataModel::removeSensor(const QString &sensorId)
0368 {
0369     d->removeSensor(sensorId);
0370 }
0371 
0372 int KSysGuard::SensorDataModel::column(const QString &sensorId) const
0373 {
0374     return d->sensors.indexOf(sensorId);
0375 }
0376 
0377 void KSysGuard::SensorDataModel::classBegin()
0378 {
0379     d->usedByQml = true;
0380 }
0381 
0382 void KSysGuard::SensorDataModel::componentComplete()
0383 {
0384     d->componentComplete = true;
0385 
0386     d->sensorsChanged();
0387 
0388     Q_EMIT sensorsChanged();
0389 }
0390 
0391 void SensorDataModel::Private::addSensor(const QString &id)
0392 {
0393     if (!requestedSensors.contains(id)) {
0394         return;
0395     }
0396 
0397     qCDebug(LIBKSYSGUARD_SENSORS) << "Add Sensor" << id;
0398 
0399     sensors.append(id);
0400     SensorDaemonInterface::instance()->subscribe(id);
0401     SensorDaemonInterface::instance()->requestMetaData(id);
0402 }
0403 
0404 void SensorDataModel::Private::removeSensor(const QString &id)
0405 {
0406     const int col = sensors.indexOf(id);
0407     if (col == -1) {
0408         return;
0409     }
0410 
0411     q->beginRemoveColumns(QModelIndex(), col, col);
0412 
0413     sensors.removeAt(col);
0414     sensorInfos.remove(id);
0415     sensorData.remove(id);
0416 
0417     q->endRemoveColumns();
0418 }
0419 
0420 void SensorDataModel::onSensorAdded(const QString &sensorId)
0421 {
0422     if (!d->enabled) {
0423         return;
0424     }
0425 
0426     d->addSensor(sensorId);
0427 }
0428 
0429 void SensorDataModel::onSensorRemoved(const QString &sensorId)
0430 {
0431     if (!d->enabled) {
0432         return;
0433     }
0434 
0435     d->removeSensor(sensorId);
0436 }
0437 
0438 void SensorDataModel::onMetaDataChanged(const QString &sensorId, const SensorInfo &info)
0439 {
0440     if (!d->enabled) {
0441         return;
0442     }
0443 
0444     auto column = d->sensors.indexOf(sensorId);
0445     if (column == -1) {
0446         return;
0447     }
0448 
0449     qCDebug(LIBKSYSGUARD_SENSORS) << "Received metadata change for" << sensorId;
0450 
0451     d->minimum.reset();
0452     d->maximum.reset();
0453 
0454     // Simple case: Just an update for a sensor's metadata
0455     if (d->sensorInfos.contains(sensorId)) {
0456         d->sensorInfos[sensorId] = info;
0457         Q_EMIT dataChanged(index(0, column), index(0, column), {Qt::DisplayRole, Name, ShortName, Description, Unit, Minimum, Maximum, Type, FormattedValue});
0458         Q_EMIT sensorMetaDataChanged();
0459         return;
0460     }
0461 
0462     // Otherwise, it's a new sensor that was added
0463 
0464     // Ensure we do not insert columns that are out of range.
0465     while (d->sensorInfos.count() + 1 <= column && column > 0) {
0466         column--;
0467     }
0468 
0469     beginInsertColumns(QModelIndex{}, column, column);
0470     d->sensorInfos[sensorId] = info;
0471     d->sensorData[sensorId] = QVariant{};
0472     endInsertColumns();
0473 
0474     SensorDaemonInterface::instance()->requestValue(sensorId);
0475     Q_EMIT sensorMetaDataChanged();
0476 
0477     if (isReady()) {
0478         Q_EMIT readyChanged();
0479     }
0480 }
0481 
0482 void SensorDataModel::onValueChanged(const QString &sensorId, const QVariant &value)
0483 {
0484     const auto column = d->sensors.indexOf(sensorId);
0485     if (column == -1 || !d->enabled) {
0486         return;
0487     }
0488 
0489     if (d->updateRateLimit && d->sensorData[sensorId].isValid()) {
0490         auto updateRateLimit = chrono::steady_clock::duration(chrono::milliseconds(d->updateRateLimit.value()));
0491         auto now = chrono::steady_clock::now();
0492         if (d->lastUpdateTimes.contains(column) && now - d->lastUpdateTimes.value(column) < updateRateLimit) {
0493             return;
0494         } else {
0495             d->lastUpdateTimes[column] = now;
0496         }
0497     }
0498 
0499     d->sensorData[sensorId] = value;
0500     Q_EMIT dataChanged(index(0, column), index(0, column), {Qt::DisplayRole, Value, FormattedValue});
0501 }
0502 
0503 void SensorDataModel::Private::sensorsChanged()
0504 {
0505     q->beginResetModel();
0506 
0507     SensorDaemonInterface::instance()->unsubscribe(sensors);
0508 
0509     sensors.clear();
0510     sensorData.clear();
0511     sensorInfos.clear();
0512     lastUpdateTimes.clear();
0513 
0514     sensors = requestedSensors;
0515 
0516     SensorDaemonInterface::instance()->subscribe(requestedSensors);
0517     SensorDaemonInterface::instance()->requestMetaData(requestedSensors);
0518 
0519     q->endResetModel();
0520 }