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 }