Wire Galaxy security_classification to OPC UA AccessLevel (ReadOnly for SecuredWrite/VerifiedWrite/ViewOnly). Use deployed package chain for attribute queries to exclude undeployed attributes. Group primitive attributes under their parent variable node (merged Variable+Object). Add is_historized and is_alarm detection via HistoryExtension/AlarmExtension primitives. Implement OPC UA HistoryRead backed by Wonderware Historian Runtime database. Implement AlarmConditionState nodes driven by InAlarm with condition refresh support. Add historyread and alarms CLI commands for testing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
124 lines
4.8 KiB
SQL
124 lines
4.8 KiB
SQL
-- Galaxy Object User-Defined Attributes/Tags for OPC UA Server
|
|
-- Returns user-defined (dynamic) attributes for deployed automation objects.
|
|
-- These are the attributes defined on templates and inherited by instances
|
|
-- via the deployed package derivation chain (e.g., MachineID, MoveInFlag).
|
|
--
|
|
-- Use full_tag_reference for read/write operations against the runtime.
|
|
-- Join with hierarchy.sql results on gobject_id to place attributes in the OPC UA browse tree.
|
|
--
|
|
-- For system/primitive attributes as well, see attributes_extended.sql.
|
|
--
|
|
-- Only attributes that existed at deploy time are included. The CTE walks
|
|
-- package.derived_from_package_id starting from each instance's deployed_package_id,
|
|
-- then joins dynamic_attribute on package_id to filter out post-deploy additions.
|
|
-- When the same attribute appears at multiple levels, only the shallowest
|
|
-- (most-derived) version is kept.
|
|
--
|
|
-- Historization detection: an attribute is historized when a primitive_instance
|
|
-- with a matching name exists in the deployed package chain and its primitive_definition
|
|
-- has primitive_name = 'HistoryExtension'.
|
|
--
|
|
-- Array dimensions are extracted from the mx_value hex string on the template's
|
|
-- dynamic_attribute row (bytes 5-6, little-endian uint16 at hex positions 13-16).
|
|
--
|
|
-- Data types (mx_data_type):
|
|
-- 1 = Boolean, 2 = Integer (Int32), 3 = Float (Single), 4 = Double,
|
|
-- 5 = String, 6 = Time (DateTime), 7 = ElapsedTime (TimeSpan),
|
|
-- 8 = (reference), 13 = (enumeration), 14 = (custom), 15 = InternationalizedString, 16 = (custom)
|
|
|
|
;WITH deployed_package_chain AS (
|
|
-- Start from each deployed instance's deployed package
|
|
SELECT
|
|
g.gobject_id,
|
|
p.package_id,
|
|
p.derived_from_package_id,
|
|
0 AS depth
|
|
FROM gobject g
|
|
INNER JOIN package p
|
|
ON p.package_id = g.deployed_package_id
|
|
WHERE g.is_template = 0
|
|
AND g.deployed_package_id <> 0
|
|
UNION ALL
|
|
-- Walk up the package derivation chain
|
|
SELECT
|
|
dpc.gobject_id,
|
|
p.package_id,
|
|
p.derived_from_package_id,
|
|
dpc.depth + 1
|
|
FROM deployed_package_chain dpc
|
|
INNER JOIN package p
|
|
ON p.package_id = dpc.derived_from_package_id
|
|
WHERE dpc.derived_from_package_id <> 0 AND dpc.depth < 10
|
|
)
|
|
SELECT
|
|
gobject_id,
|
|
tag_name,
|
|
attribute_name,
|
|
full_tag_reference,
|
|
mx_data_type,
|
|
data_type_name,
|
|
is_array,
|
|
array_dimension,
|
|
mx_attribute_category,
|
|
security_classification,
|
|
is_historized,
|
|
is_alarm
|
|
FROM (
|
|
SELECT
|
|
dpc.gobject_id,
|
|
g.tag_name,
|
|
da.attribute_name,
|
|
g.tag_name + '.' + da.attribute_name
|
|
+ CASE WHEN da.is_array = 1 THEN '[]' ELSE '' END
|
|
AS full_tag_reference,
|
|
da.mx_data_type,
|
|
dt.description AS data_type_name,
|
|
da.is_array,
|
|
CASE WHEN da.is_array = 1
|
|
THEN CONVERT(int, CONVERT(varbinary(2),
|
|
SUBSTRING(da.mx_value, 15, 2) + SUBSTRING(da.mx_value, 13, 2), 2))
|
|
ELSE NULL
|
|
END AS array_dimension,
|
|
da.mx_attribute_category,
|
|
da.security_classification,
|
|
CASE WHEN EXISTS (
|
|
SELECT 1 FROM deployed_package_chain dpc2
|
|
INNER JOIN primitive_instance pi
|
|
ON pi.package_id = dpc2.package_id
|
|
AND pi.primitive_name = da.attribute_name
|
|
INNER JOIN primitive_definition pd
|
|
ON pd.primitive_definition_id = pi.primitive_definition_id
|
|
AND pd.primitive_name = 'HistoryExtension'
|
|
WHERE dpc2.gobject_id = dpc.gobject_id
|
|
) THEN 1 ELSE 0 END AS is_historized,
|
|
CASE WHEN EXISTS (
|
|
SELECT 1 FROM deployed_package_chain dpc2
|
|
INNER JOIN primitive_instance pi
|
|
ON pi.package_id = dpc2.package_id
|
|
AND pi.primitive_name = da.attribute_name
|
|
INNER JOIN primitive_definition pd
|
|
ON pd.primitive_definition_id = pi.primitive_definition_id
|
|
AND pd.primitive_name = 'AlarmExtension'
|
|
WHERE dpc2.gobject_id = dpc.gobject_id
|
|
) THEN 1 ELSE 0 END AS is_alarm,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY dpc.gobject_id, da.attribute_name
|
|
ORDER BY dpc.depth
|
|
) AS rn
|
|
FROM deployed_package_chain dpc
|
|
INNER JOIN dynamic_attribute da
|
|
ON da.package_id = dpc.package_id
|
|
INNER JOIN gobject g
|
|
ON g.gobject_id = dpc.gobject_id
|
|
INNER JOIN template_definition td
|
|
ON td.template_definition_id = g.template_definition_id
|
|
LEFT JOIN data_type dt
|
|
ON dt.mx_data_type = da.mx_data_type
|
|
WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)
|
|
AND da.attribute_name NOT LIKE '[_]%'
|
|
AND da.attribute_name NOT LIKE '%.Description'
|
|
AND da.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)
|
|
) ranked
|
|
WHERE rn = 1
|
|
ORDER BY tag_name, attribute_name;
|