Repository navigation
M2D-1: typed IEC Quality / UTC TimeQuality inspector for native Workbench #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ Rectangle { | |
| property int expandedMemberIndex: -1 | ||
| property var expandedMemberChildren: [] | ||
| property int valueRevision: 0 | ||
| property bool showSelectedEvidence: false | ||
| readonly property int maximumExpandedChildren: 64 | ||
|
|
||
| function memberParts(raw) { | ||
|
|
@@ -55,6 +56,7 @@ Rectangle { | |
| function onSelectionChanged() { | ||
| root.expandedMemberIndex = -1 | ||
| root.expandedMemberChildren = [] | ||
| root.showSelectedEvidence = false | ||
| } | ||
| } | ||
| Connections { | ||
|
|
@@ -256,8 +258,11 @@ Rectangle { | |
| Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: theme.lineSoft } | ||
| MouseArea { | ||
| anchors.fill: parent | ||
| onClicked: root.modelProvider.treeModel.selectMmsItem( | ||
| attributeRow.modelData.mmsDomain, attributeRow.modelData.mmsItem) | ||
| onClicked: { | ||
| root.modelProvider.treeModel.selectMmsItem( | ||
| attributeRow.modelData.mmsDomain, attributeRow.modelData.mmsItem) | ||
| root.showSelectedEvidence = true | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -278,6 +283,12 @@ Rectangle { | |
| } | ||
| } | ||
| } | ||
| BrowserEngineeringEvidence { | ||
| theme: root.theme | ||
| engineeringEvidence: root.showSelectedEvidence | ||
| ? (root.modelProvider.treeModel.selectedNode.engineeringEvidence || ({})) | ||
| : ({}) | ||
|
Comment on lines
+286
to
+290
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a DataSet is selected, this component is the third direct child of the Useful? React with 👍 / 👎.
Comment on lines
+288
to
+290
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a DataSet member resolves directly to a Useful? React with 👍 / 👎. |
||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
| import QtQuick | ||
| import QtQuick.Controls | ||
| import QtQuick.Layouts | ||
|
|
||
| // Pure presentation over canonical typed evidence. No hex parsing or MMS reads. | ||
| ColumnLayout { | ||
| id: root | ||
| required property var theme | ||
| property var engineeringEvidence: ({}) | ||
| readonly property bool hasEvidence: Boolean(engineeringEvidence) | ||
| && (engineeringEvidence.kind === "quality" | ||
| || engineeringEvidence.kind === "timestamp") | ||
| visible: hasEvidence | ||
| spacing: 0 | ||
| Layout.fillWidth: true | ||
|
|
||
| Rectangle { | ||
| Layout.fillWidth: true | ||
| implicitHeight: 27 | ||
| color: theme.surfaceRaised | ||
| RowLayout { | ||
| anchors.fill: parent | ||
| anchors.leftMargin: 8 | ||
| anchors.rightMargin: 8 | ||
| Label { | ||
| Layout.fillWidth: true | ||
| text: root.engineeringEvidence.kind === "quality" | ||
| ? "Quality Details · MMS BIT STRING" | ||
| : "Timestamp & TimeQuality · MMS UTC" | ||
| color: theme.text | ||
| font.pixelSize: 10 | ||
| font.weight: Font.DemiBold | ||
| } | ||
| Label { | ||
| text: "READ EVIDENCE" | ||
| color: theme.muted | ||
| font.pixelSize: 9 | ||
| } | ||
| } | ||
| } | ||
| Repeater { | ||
| model: root.hasEvidence && root.engineeringEvidence.rows | ||
| ? root.engineeringEvidence.rows : [] | ||
| delegate: Rectangle { | ||
| id: evidenceRow | ||
| required property int index | ||
| required property var modelData | ||
| Layout.fillWidth: true | ||
| implicitHeight: 23 | ||
| color: index % 2 ? theme.surfaceSoft : theme.surface | ||
| RowLayout { | ||
| anchors.fill: parent | ||
| anchors.leftMargin: 9 | ||
| anchors.rightMargin: 9 | ||
| spacing: 6 | ||
| Label { | ||
| Layout.preferredWidth: Math.max(154, root.width * 0.4) | ||
| text: evidenceRow.modelData.label || "" | ||
| color: theme.textSoft | ||
| font.pixelSize: 10 | ||
| elide: Text.ElideRight | ||
| } | ||
| Label { | ||
| Layout.fillWidth: true | ||
| text: evidenceRow.modelData.value === undefined | ||
| || evidenceRow.modelData.value === null | ||
| ? "Unknown" : String(evidenceRow.modelData.value) | ||
| color: theme.text | ||
| font.pixelSize: 10 | ||
| elide: Text.ElideMiddle | ||
| } | ||
| } | ||
| Rectangle { | ||
| anchors.bottom: parent.bottom | ||
| height: 1 | ||
| width: parent.width | ||
| color: theme.lineSoft | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
| #pragma once | ||
|
|
||
| #include "ariec61850/mms/data_value.hpp" | ||
|
|
||
| #include <QDateTime> | ||
| #include <QString> | ||
| #include <QStringList> | ||
| #include <QVariant> | ||
| #include <QVariantList> | ||
| #include <QVariantMap> | ||
|
|
||
| #include <chrono> | ||
| #include <cstdint> | ||
| #include <variant> | ||
|
|
||
| // One typed, fail-closed engineering projection for native Browser q/t values. | ||
| // The underlying MMS data remains authoritative; GUI never guesses hex semantics. | ||
| namespace arstack::iedsim { | ||
| namespace detail { | ||
| inline void addEvidenceRow(QVariantList& rows, const QString& label, const QVariant& value) { | ||
| rows.push_back(QVariantMap{{QStringLiteral("label"), label}, | ||
| {QStringLiteral("value"), value}}); | ||
| } | ||
| } // namespace detail | ||
|
|
||
| [[nodiscard]] inline QVariantMap decodeEngineeringEvidence( | ||
| const QString& mmsItem, const ar::iec61850::mms::MmsDataValue& value) { | ||
| using namespace ar::iec61850::mms; | ||
| const QString leaf = mmsItem.mid(mmsItem.lastIndexOf(QLatin1Char('$')) + 1); | ||
| QVariantList rows; | ||
|
|
||
| if (leaf == QStringLiteral("q") && value.kind() == MmsDataKind::bit_string) { | ||
| const auto& bits = value.raw_value(); // [unused bits, octet 1, octet 2] | ||
| // IEC 61850 Quality is 13 bits. A malformed/short wire value is NOT | ||
| // translated to false flags (unknown != false). | ||
| if (bits.size() != 3U || bits[0] != 3U || (bits[2] & 0x07U) != 0U) | ||
| return {}; | ||
| const auto at = [&bits](const unsigned index) { | ||
| return ((bits[1U + index / 8U] >> (7U - index % 8U)) & 1U) != 0U; | ||
| }; | ||
| const unsigned validity = (at(0) ? 2U : 0U) | (at(1) ? 1U : 0U); | ||
| const QStringList meanings{ | ||
| QStringLiteral("good"), QStringLiteral("invalid"), | ||
| QStringLiteral("reserved"), QStringLiteral("questionable")}; | ||
| detail::addEvidenceRow(rows, QStringLiteral("Validity"), meanings.at(int(validity))); | ||
| static constexpr const char* flags[]{ | ||
| "Overflow", "OutOfRange", "BadReference", "Oscillatory", | ||
| "Failure", "OldData", "Inconsistent", "Inaccurate"}; | ||
| for (unsigned i = 0; i < 8U; ++i) | ||
| detail::addEvidenceRow(rows, QString::fromLatin1(flags[i]), at(i + 2U)); | ||
| const QString source = at(10U) ? QStringLiteral("substituted") : QStringLiteral("process"); | ||
| detail::addEvidenceRow(rows, QStringLiteral("Source"), source); | ||
| detail::addEvidenceRow(rows, QStringLiteral("Test"), at(11U)); | ||
| detail::addEvidenceRow(rows, QStringLiteral("OperatorBlocked"), at(12U)); | ||
| return {{QStringLiteral("kind"), QStringLiteral("quality")}, | ||
| {QStringLiteral("summary"), meanings.at(int(validity))}, | ||
| {QStringLiteral("rows"), rows}}; | ||
| } | ||
|
|
||
| if (leaf == QStringLiteral("t") && value.kind() == MmsDataKind::utc_time) { | ||
| const auto& utc = std::get<Iec61850UtcTime>(value.value()); | ||
| const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>( | ||
| utc.value.time_since_epoch()).count(); | ||
| const QString timestamp = QDateTime::fromMSecsSinceEpoch( | ||
| static_cast<qint64>(milliseconds), Qt::UTC) | ||
| .toString(QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz 'UTC'")); | ||
| const std::uint8_t timeQuality = utc.quality; | ||
| const unsigned accuracy = timeQuality & 0x1FU; | ||
| detail::addEvidenceRow(rows, QStringLiteral("UTC time"), timestamp); | ||
| detail::addEvidenceRow(rows, QStringLiteral("LeapSecondsKnown"), | ||
| (timeQuality & 0x80U) != 0U); | ||
| detail::addEvidenceRow(rows, QStringLiteral("ClockFailure"), | ||
| (timeQuality & 0x40U) != 0U); | ||
| detail::addEvidenceRow(rows, QStringLiteral("ClockNotSynchronized"), | ||
| (timeQuality & 0x20U) != 0U); | ||
| detail::addEvidenceRow(rows, QStringLiteral("TimeAccuracy"), | ||
| accuracy == 31U ? QVariant{QStringLiteral("unspecified")} | ||
| : QVariant{int(accuracy)}); | ||
| return {{QStringLiteral("kind"), QStringLiteral("timestamp")}, | ||
| {QStringLiteral("summary"), timestamp}, | ||
| {QStringLiteral("rows"), rows}}; | ||
| } | ||
| return {}; | ||
| } | ||
| } // namespace arstack::iedsim |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This evidence-focused commit also changes Chocolatey retry behavior and NSIS compiler discovery in the Windows packaging workflow, which addresses an independent CI failure mode and expands the review and rollback scope without contributing to typed Quality/UTC evidence. Move this workflow hardening into a separate focused change with its own validation.
AGENTS.md reference: AGENTS.md:L238-L240
Useful? React with 👍 / 👎.