File indexing completed on 2024-04-28 16:50:02

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     d->minimum = (*result).min;
0226     return d->minimum.value();
0227 }
0228 
0229 qreal SensorDataModel::maximum() const
0230 {
0231     if (d->sensors.isEmpty()) {
0232         return 0;
0233     }
0234 
0235     if (d->maximum.has_value()) {
0236         return d->maximum.value();
0237     }
0238 
0239     auto result = std::max_element(d->sensorInfos.cbegin(), d->sensorInfos.cend(), [](const SensorInfo &first, const SensorInfo &second) {
0240         return first.max < second.max;
0241     });
0242     d->maximum = (*result).max;
0243     return d->maximum.value();
0244 }
0245 
0246 QStringList SensorDataModel::sensors() const
0247 {
0248     return d->requestedSensors;
0249 }
0250 
0251 void SensorDataModel::setSensors(const QStringList &sensorIds)
0252 {
0253     if (d->requestedSensors == sensorIds) {
0254         return;
0255     }
0256 
0257     d->requestedSensors = sensorIds;
0258 
0259     if (!d->usedByQml || d->componentComplete) {
0260         d->sensorsChanged();
0261     }
0262     Q_EMIT readyChanged();
0263     Q_EMIT sensorsChanged();
0264 }
0265 
0266 bool SensorDataModel::enabled() const
0267 {
0268     return d->enabled;
0269 }
0270 
0271 void SensorDataModel::setEnabled(bool newEnabled)
0272 {
0273     if (newEnabled == d->enabled) {
0274         return;
0275     }
0276 
0277     d->enabled = newEnabled;
0278     if (d->enabled) {
0279         SensorDaemonInterface::instance()->subscribe(d->sensorInfos.keys());
0280         SensorDaemonInterface::instance()->requestMetaData(d->sensorInfos.keys());
0281     } else {
0282         SensorDaemonInterface::instance()->unsubscribe(d->sensorInfos.keys());
0283     }
0284 
0285     Q_EMIT enabledChanged();
0286 }
0287 
0288 QVariantMap SensorDataModel::sensorColors() const
0289 {
0290     return d->sensorColors;
0291 }
0292 
0293 void SensorDataModel::setSensorColors(const QVariantMap &sensorColors)
0294 {
0295     if (sensorColors == d->sensorColors) {
0296         return;
0297     }
0298     d->sensorColors = sensorColors;
0299     Q_EMIT sensorColorsChanged();
0300     Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), {Color});
0301 }
0302 
0303 QVariantMap SensorDataModel::sensorLabels() const
0304 {
0305     return d->sensorLabels;
0306 }
0307 
0308 void SensorDataModel::setSensorLabels(const QVariantMap &sensorLabels)
0309 {
0310     if (sensorLabels == d->sensorLabels) {
0311         return;
0312     }
0313     d->sensorLabels = sensorLabels;
0314     Q_EMIT sensorLabelsChanged();
0315     Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), {Name, ShortName});
0316 }
0317 
0318 int SensorDataModel::updateRateLimit() const
0319 {
0320     return d->updateRateLimit.value_or(-1);
0321 }
0322 
0323 void SensorDataModel::setUpdateRateLimit(int newUpdateRateLimit)
0324 {
0325     // An update rate limit of 0 or less makes no sense, so treat it as clearing
0326     // the limit.
0327     if (newUpdateRateLimit <= 0) {
0328         if (!d->updateRateLimit) {
0329             return;
0330         }
0331 
0332         d->updateRateLimit.reset();
0333     } else {
0334         if (d->updateRateLimit && d->updateRateLimit.value() == newUpdateRateLimit) {
0335             return;
0336         }
0337 
0338         d->updateRateLimit = newUpdateRateLimit;
0339     }
0340     d->lastUpdateTimes.clear();
0341     Q_EMIT updateRateLimitChanged();
0342 }
0343 
0344 void KSysGuard::SensorDataModel::resetUpdateRateLimit()
0345 {
0346     setUpdateRateLimit(-1);
0347 }
0348 
0349 bool KSysGuard::SensorDataModel::isReady() const
0350 {
0351     return d->sensors.size() == d->sensorInfos.size();
0352 }
0353 
0354 void SensorDataModel::addSensor(const QString &sensorId)
0355 {
0356     d->addSensor(sensorId);
0357 }
0358 
0359 void SensorDataModel::removeSensor(const QString &sensorId)
0360 {
0361     d->removeSensor(sensorId);
0362 }
0363 
0364 int KSysGuard::SensorDataModel::column(const QString &sensorId) const
0365 {
0366     return d->sensors.indexOf(sensorId);
0367 }
0368 
0369 void KSysGuard::SensorDataModel::classBegin()
0370 {
0371     d->usedByQml = true;
0372 }
0373 
0374 void KSysGuard::SensorDataModel::componentComplete()
0375 {
0376     d->componentComplete = true;
0377 
0378     d->sensorsChanged();
0379 
0380     Q_EMIT sensorsChanged();
0381 }
0382 
0383 void SensorDataModel::Private::addSensor(const QString &id)
0384 {
0385     if (!requestedSensors.contains(id)) {
0386         return;
0387     }
0388 
0389     qCDebug(LIBKSYSGUARD_SENSORS) << "Add Sensor" << id;
0390 
0391     sensors.append(id);
0392     SensorDaemonInterface::instance()->subscribe(id);
0393     SensorDaemonInterface::instance()->requestMetaData(id);
0394 }
0395 
0396 void SensorDataModel::Private::removeSensor(const QString &id)
0397 {
0398     const int col = sensors.indexOf(id);
0399     if (col == -1) {
0400         return;
0401     }
0402 
0403     q->beginRemoveColumns(QModelIndex(), col, col);
0404 
0405     sensors.removeAt(col);
0406     sensorInfos.remove(id);
0407     sensorData.remove(id);
0408 
0409     q->endRemoveColumns();
0410 }
0411 
0412 void SensorDataModel::onSensorAdded(const QString &sensorId)
0413 {
0414     if (!d->enabled) {
0415         return;
0416     }
0417 
0418     d->addSensor(sensorId);
0419 }
0420 
0421 void SensorDataModel::onSensorRemoved(const QString &sensorId)
0422 {
0423     if (!d->enabled) {
0424         return;
0425     }
0426 
0427     d->removeSensor(sensorId);
0428 }
0429 
0430 void SensorDataModel::onMetaDataChanged(const QString &sensorId, const SensorInfo &info)
0431 {
0432     if (!d->enabled) {
0433         return;
0434     }
0435 
0436     auto column = d->sensors.indexOf(sensorId);
0437     if (column == -1) {
0438         return;
0439     }
0440 
0441     qCDebug(LIBKSYSGUARD_SENSORS) << "Received metadata change for" << sensorId;
0442 
0443     d->minimum.reset();
0444     d->maximum.reset();
0445 
0446     // Simple case: Just an update for a sensor's metadata
0447     if (d->sensorInfos.contains(sensorId)) {
0448         d->sensorInfos[sensorId] = info;
0449         Q_EMIT dataChanged(index(0, column), index(0, column), {Qt::DisplayRole, Name, ShortName, Description, Unit, Minimum, Maximum, Type, FormattedValue});
0450         Q_EMIT sensorMetaDataChanged();
0451         return;
0452     }
0453 
0454     // Otherwise, it's a new sensor that was added
0455 
0456     // Ensure we do not insert columns that are out of range.
0457     while (d->sensorInfos.count() + 1 <= column && column > 0) {
0458         column--;
0459     }
0460 
0461     beginInsertColumns(QModelIndex{}, column, column);
0462     d->sensorInfos[sensorId] = info;
0463     d->sensorData[sensorId] = QVariant{};
0464     endInsertColumns();
0465 
0466     SensorDaemonInterface::instance()->requestValue(sensorId);
0467     Q_EMIT sensorMetaDataChanged();
0468 
0469     if (isReady()) {
0470         Q_EMIT readyChanged();
0471     }
0472 }
0473 
0474 void SensorDataModel::onValueChanged(const QString &sensorId, const QVariant &value)
0475 {
0476     const auto column = d->sensors.indexOf(sensorId);
0477     if (column == -1 || !d->enabled) {
0478         return;
0479     }
0480 
0481     if (d->updateRateLimit && d->sensorData[sensorId].isValid()) {
0482         auto updateRateLimit = chrono::steady_clock::duration(chrono::milliseconds(d->updateRateLimit.value()));
0483         auto now = chrono::steady_clock::now();
0484         if (d->lastUpdateTimes.contains(column) && now - d->lastUpdateTimes.value(column) < updateRateLimit) {
0485             return;
0486         } else {
0487             d->lastUpdateTimes[column] = now;
0488         }
0489     }
0490 
0491     d->sensorData[sensorId] = value;
0492     Q_EMIT dataChanged(index(0, column), index(0, column), {Qt::DisplayRole, Value, FormattedValue});
0493 }
0494 
0495 void SensorDataModel::Private::sensorsChanged()
0496 {
0497     q->beginResetModel();
0498 
0499     SensorDaemonInterface::instance()->unsubscribe(sensors);
0500 
0501     sensors.clear();
0502     sensorData.clear();
0503     sensorInfos.clear();
0504     lastUpdateTimes.clear();
0505 
0506     sensors = requestedSensors;
0507 
0508     SensorDaemonInterface::instance()->subscribe(requestedSensors);
0509     SensorDaemonInterface::instance()->requestMetaData(requestedSensors);
0510 
0511     q->endResetModel();
0512 }