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 }