00a5be261cc29022c2b88c6cebc0a2148c77156f
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b61f3e0560 |
fix(galaxy): AVEVA priority 1 is the most urgent: Galaxy alarm Severity follows AVEVA's priority bands, live and history alike (#887)
v2-ci / unit-tests (push) Has been cancelled
GalaxyAlarmPriority, the one table live Galaxy alarms (MxAccessSeverityMapper) and AVEVA A&E history (EventMapper, #886) map the AVEVA priority through, ranked a higher number as more severe. AVEVA ranks priority 1 as the most urgent, so a priority-1 alarm read 250 and a priority-999 system event 1000. It now uses AVEVA's default priority ranges, most urgent first: AVEVA priority AVEVA severity bucket OPC UA Severity 1-250 1 Critical Critical 1000 251-500 2 High High 750 501-750 3 Medium Medium 500 751-999 4 Low Low 250 (history only) 0 not an alarm - 1 Out-of-range priorities clamp (<= 0 Critical, > 999 Low). Evidence, vd03 Runtime.dbo.Events read-only: priority 1/100 alarms carry AVEVA severity 1, 400/500 severity 2, 600/700 severity 3, and every priority-999 System.* / User.* row severity 0 with IsAlarm 0. - Live and history now use the same number. GalaxyDriver passes the table's exact value as AlarmEventArgs.OpcUaSeverity. Before, it passed only the bucket, which NativeAlarmProjector re-mapped through its generic 200/500/700/900 ladder, so live and history disagreed even before the direction fix. - History: a row whose AVEVA severity is 0 (System.Start/Stop/Deploy, User.Write...) is not an alarm and reads Severity 1, below every alarm band. For alarms AVEVA's 1-4 severity is still never the value: the live feed carries only the priority, so it cannot use it, and history must agree. - Docs: docs/Historian.md gains "Galaxy alarm priority -> Severity (lmxopcua#887)"; docs/AlarmTracking.md updated. - Live re-run of the #801/#886 harness against vd03 HistorianGateway ea9f21c (read-only, network-only): 224 of 233 cases identical to the #886 run, the 9 others differ only in Severity (priority-700 alarm rows 750 -> 500, 19 system/user events 1000 -> 1). Record: aveva-history-801.md "#887 re-run", report aveva-gate-sdk2-887.json.gz. Client-visible: every OPC UA client that ranks or filters Galaxy alarms by Severity sees the order reversed (live and HistoryReadEvents). ScadaBridge has no code that depends on the old values; its own mixed severity directions are ScadaBridge#129. Tests updated because they pinned the inverted table: - Driver.Galaxy.Tests MxAccessSeverityMapperTests (rewritten: edges 1/250/251/ 500/501/750/751/999, 0/-100/int.MinValue/1000/int.MaxValue clamps, monotonic-decreasing sweep, equals GalaxyAlarmPriority for -2..1001) - Driver.Galaxy.Tests GatewayGalaxyAlarmFeedTests (750 and 600 -> Medium) - Driver.Historian.Gateway.Tests EventMapperTests (band table, priority 999 -> 250, 1 -> 1000; system events -> 1) - OpcUaServer.IntegrationTests GalaxyEquipmentEventHistoryWireTests (700 -> 500, 100 -> 1000, System.Stop -> 1; the fake now stores AVEVA's real 0-4 severity) New tests: - Runtime.Tests NativeAlarmRestatementEndToEndTests .Galaxy_live_condition_severity_is_the_AVEVA_priority_band (8 rows: the real feed -> real GalaxyDriver -> host -> projector; the same rows EventMapperTests pins on the history path) - EventMapperTests.Aveva_severity_zero_marks_an_event_that_is_not_an_alarm (5 rows, incl. the priority-999 alarm positive control) - GalaxyDriverAlarmSourceTests: OpcUaSeverity passes through Mutation check, 8 mutants, all killed: the pre-#887 inverted table; the driver dropping OpcUaSeverity; no not-an-alarm rule; each of the three band edges off by one; history ignoring priority; a missing AVEVA severity read as 0. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi |
||
|
|
3a312ffdd1 |
fix(history): a folder's event history reads the Galaxy objects behind its native conditions (#886)
v2-ci / unit-tests (push) Has been cancelled
A notifier folder read one event source, its NodeId identifier (a RawPath or an EquipmentId). AVEVA stores a Galaxy object's A&E history under the object's tag_name (Source_Object), so AVEVA's own alarm and event history reached no production folder. Its lower-case property keys were also missed, so every row read Severity 1 with no ConditionName. Sources. A folder's event history now reads its own source plus the Galaxy object of every native condition that fires on it live: those parented under it and those WireAlarmNotifiers wired to it. Live and history use the same membership. The object comes from the condition's Galaxy alarm reference, its raw tag's attributeRef, up to the first '.': - The composer reads it with the driver's own parser (GalaxyAttributeRef, shared with GalaxyDriver), only for a GalaxyMxGateway tag with an alarm (RawTagPlan.NativeAlarmReference, part of plan equality). - The applier binds it after materialising the condition (IOpcUaAddressSpaceSink.BindNativeAlarmHistory, forwarded by the deferred sink). A rebuild or the condition's removal forgets it. Each object is read with the #882 rule: its own events and <Object>.<Attribute> rows, never a prefix sibling. Fan-out. historiangw's ReadEvents filter is a single predicate, so there is one request per source; the wire contract is unchanged. EventSourceFanOut merges the reads as one source: - order is (time, source position, arrival), reversed for a reverse read; ties are never re-sorted by EventId, so the boundary skip stays stable; - duplicates by EventId are dropped and the merge is cut to the cap; - a truncated source bounds the page at the time it stopped. Paging, continuation points (bound to the whole source set), the walked-back reverse read and the tie-cluster over-fetch are unchanged. A folder with one source is read exactly as before. ACL. Every source is read inside the folder's own HistoryRead, after AuthorizeHistoryRead (#826). A denied folder reads nothing and fails closed. Mapping (EventMapper, property names now case-insensitive): - Severity: an AVEVA row's priority goes through GalaxyAlarmPriority, the table live Galaxy alarms use (MxAccessSeverityMapper now delegates to it), so history matches live. Otherwise severity (OtOpcUa's own rows, which AVEVA stores lower-cased), clamped. AVEVA's own 1-4 severity is ignored. The table ranks a higher priority as more severe, which inverts AVEVA's convention: filed as #887. - An AVEVA row whose source_conditionvariable names a native condition that fires on the folder takes that condition's SourceNode and live ConditionName. Otherwise ConditionName is alarmname or the condition variable's attribute. - alarm_id/alarmid map to AlarmId. Tests: - GalaxyEquipmentEventHistoryWireTests (4, real SDK session, production Gateway backend, AVEVA-shaped fake gateway): equipment EqA backed by objects Oven_007 + OvenDoor_07. Covers object and attribute events from both objects, the prefix sibling excluded, cross-object and in-object ties, forward paging at 1-4 a page, reverse whole and paged, SourceNode/ConditionName/Severity, the raw device folder, ACL deny with no backend call plus a granted positive control, and a rebuild forgetting the bindings. - EventSourceFanOutTests (8), EventMapper AVEVA cases (11), GalaxyAttributeRefTests (16), a composer test and an applier ordering test. - Mutation-checked, 17 mutants, all killed. Covered: parent-only membership, no scope, a case-sensitive condition match, no condition rewrite, no truncation bound, EventId tie order, no sort, no cap, no stop at the bound, unreversed source order, no dedupe, no '.' boundary, priority ignored, case-sensitive keys, no applier bind, no deferred forward, and a composer that ignores the driver type. The forget-on-rematerialise guard survives as belt-and-braces: the rebuild clear covers it. - Live, read-only, no WinRM, vd03 HistorianGateway ea9f21c: equipment folder EQ-886-ZipsenLeft (objects DevWinPlatform + Left_001). It read 0 events before and 92 now, equal to the unfiltered read's rows for both objects. 1/7-event pages and reverse whole/5-event pages agree, and 73 rows resolve to their live condition. diff_runs vs the #882 run: 221/226 identical. The 5 that differ are expected: DevWinPlatform Severity 1 -> 750/1000, and #881 capabilities. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi |
||
|
|
29665c1872 |
feat(opcua): HistoryReadEvents projects EventType, ConditionName, SourceNode; honours the where-clause (lmxopcua#746)
HistoryReadEvents projected only EventId / SourceName / Time / ReceiveTime / Message / Severity: a select clause asking for EventType, ConditionName or SourceNode (what most generic alarm viewers ask for) got nulls, and the event filter's where-clause (an OfType) was ignored. - HistoricalEvent gains optional EventTypeName, ConditionName and AlarmId. SqlServer, QuestDB and Gateway event reads fill them from what their alarm writers already store (AlarmTypeName / AlarmName / AlarmId; the gateway event Type and its AlarmName / AlarmId properties). - The node manager maps the type name to its Part 9 event type (unknown or non-event => AlarmConditionType), projects ConditionName, and resolves SourceNode to the materialised condition the alarm id names (the live events' SourceNode), else null. - The where-clause is evaluated per event with the SDK content filter through an IFilterTarget (OfType via the type tree; other operators against the projected fields), after the page's cursor is taken, so paging is unchanged. Tested in the node manager, over a real client session (the Client.CLI has no `historyread --events` mode yet; filed as a follow-up), and by alarm writer -> event read round trips on SqlServer and QuestDB. Rebased from the unpushed remed/lane-H2 branch (originally authored 2026-10-22 in session_01Ub9hGmYLQvn9XETzw7CMsT) onto current master. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV |
||
|
|
a54c7a9366 |
feat(historian-gateway): HistorianEvent->HistoricalEvent mapper (+ clamped severity)
Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii |