UNPKG

@etm-professional-control/winccoa-mcp-server

Version:

MCP Server for WinCC OA with field-specific configurations

331 lines (321 loc) 13 kB
/** * Alarm Set Tool * * MCP tool for creating and updating alarm configurations on datapoint elements. */ import { z } from 'zod'; import { createSuccessResponse, createErrorResponse } from '../../utils/helpers.js'; import { DpConfigType, DpeType, DpAlertRangeType } from '../../types/winccoa/constants.js'; /** * Alarm Direction */ var AlarmDirection; (function (AlarmDirection) { /** Ascending: Higher values trigger higher severity alarms */ AlarmDirection["ASC"] = "ASC"; /** Descending: Lower values trigger higher severity alarms */ AlarmDirection["DESC"] = "DESC"; })(AlarmDirection || (AlarmDirection = {})); /** * Default alarm classes */ const DEFAULT_ALARM_CLASSES = { ASC: ['information.', 'warning.', 'alert.'], DESC: ['alert.', 'warning.', 'information.'] }; /** * Get min/max values for a datapoint element type */ function getMinMaxForType(dpeType) { switch (dpeType) { case DpeType.DPEL_CHAR: return [-128, 127]; case DpeType.DPEL_INT: return [-32768, 32767]; case DpeType.DPEL_UINT: return [0, 65535]; case DpeType.DPEL_LONG: return [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]; // -9007199254740991 to 9007199254740991 case DpeType.DPEL_ULONG: return [0, Number.MAX_SAFE_INTEGER]; // 0 to 9007199254740991 case DpeType.DPEL_FLOAT: return [-3.4e38, 3.4e38]; default: return [-3.4e38, 3.4e38]; } } /** * Configure a binary alert (for BOOL datapoint elements) */ async function configureBinaryAlert(winccoa, dpe, direction, alarmClass = 'alert.') { console.log(`🔔 Configuring binary alert for ${dpe}, direction: ${direction}`); // ok_range: TRUE when DESC (alarm on FALSE), FALSE when ASC (alarm on TRUE) const okRange = direction === AlarmDirection.DESC; await winccoa.dpSetWait([ `${dpe}:_alert_hdl.._type`, `${dpe}:_alert_hdl.._class`, `${dpe}:_alert_hdl.._ok_range`, `${dpe}:_alert_hdl.._active` ], [ DpConfigType.DPCONFIG_ALERT_BINARYSIGNAL, alarmClass, okRange, true ]); console.log(`✓ Binary alert configured for ${dpe}`); } /** * Configure an analog alert (for numeric datapoint elements) */ async function configureAnalogAlert(winccoa, dpe, thresholds, dpeType, direction, alarmClasses) { console.log(`🔔 Configuring analog alert for ${dpe}, thresholds: ${thresholds.join(', ')}, direction: ${direction}`); // Validate thresholds if (!thresholds || thresholds.length === 0) { throw new Error(`No thresholds defined for ${dpe}`); } if (thresholds.length > 3) { throw new Error(`Maximum 3 thresholds allowed, got ${thresholds.length}`); } // Sort thresholds in ascending order const sortedThresholds = [...thresholds].sort((a, b) => a - b); // Use custom or default alarm classes const classes = alarmClasses || DEFAULT_ALARM_CLASSES[direction]; // Get min/max values for the datapoint type const [minValue, maxValue] = getMinMaxForType(dpeType); // Create the alert configuration await winccoa.dpSetWait([ `${dpe}:_alert_hdl.._type`, `${dpe}:_alert_hdl.._orig_hdl` ], [ DpConfigType.DPCONFIG_ALERT_NONBINARYSIGNAL, false ]); // Create ranges (n thresholds = n+1 ranges) const dpes = []; const values = []; for (let i = 1; i <= sortedThresholds.length + 1; i++) { // Range Type dpes.push(`${dpe}:_alert_hdl.${i}._type`); values.push(DpAlertRangeType.DPDETAIL_RANGETYPE_MINMAX); // Lower Limit dpes.push(`${dpe}:_alert_hdl.${i}._l_limit`); values.push(i === 1 ? minValue : sortedThresholds[i - 2]); // Upper Limit dpes.push(`${dpe}:_alert_hdl.${i}._u_limit`); values.push(i > sortedThresholds.length ? maxValue : sortedThresholds[i - 1]); // ASC Direction if (direction === AlarmDirection.ASC) { dpes.push(`${dpe}:_alert_hdl.${i}._l_incl`); values.push(true); dpes.push(`${dpe}:_alert_hdl.${i}._u_incl`); values.push(i <= sortedThresholds.length ? false : true); // Alarm class (not for first range in ASC) if (i > 1) { dpes.push(`${dpe}:_alert_hdl.${i}._class`); // Select the appropriate alarm class based on number of thresholds let classIndex; switch (sortedThresholds.length) { case 3: classIndex = i - 2; // 0, 1, 2 break; case 2: classIndex = i - 1; // 1, 2 break; case 1: classIndex = 2; // alert break; default: classIndex = 2; } values.push(classes[classIndex] || classes[classes.length - 1]); } } // DESC Direction else { dpes.push(`${dpe}:_alert_hdl.${i}._l_incl`); values.push(i === 1 ? true : false); dpes.push(`${dpe}:_alert_hdl.${i}._u_incl`); values.push(true); // Alarm class (not for last range in DESC) if (i <= sortedThresholds.length) { dpes.push(`${dpe}:_alert_hdl.${i}._class`); // Select the appropriate alarm class let classIndex; switch (sortedThresholds.length) { case 1: classIndex = 0; // alert break; case 2: case 3: classIndex = i - 1; // 0, 1, 2 break; default: classIndex = 0; } values.push(classes[classIndex] || classes[0]); } } } // Set all parameters await winccoa.dpSetWait(dpes, values); // Activate the alert configuration await winccoa.dpSetWait(`${dpe}:_alert_hdl.._active`, true); console.log(`✓ Analog alert configured for ${dpe}`); } /** * Check if an alert configuration exists */ async function hasAlertConfig(winccoa, dpe) { try { const alertType = await winccoa.dpGet(`${dpe}:_alert_hdl.._type`); return alertType !== DpConfigType.DPCONFIG_NONE && alertType !== null && alertType !== undefined; } catch (error) { return false; } } /** * Deactivate an existing alert configuration */ async function deactivateAlert(winccoa, dpe) { try { await winccoa.dpSetWait(`${dpe}:_alert_hdl.._active`, false); } catch (error) { console.error(`Error deactivating alert for ${dpe}:`, error); } } /** * Get the datapoint element type using the official dpElementType() API */ function getDpeType(winccoa, dpe) { try { // Use dpElementType() instead of dpGet() - this returns the correct WinccoaElementType return winccoa.dpElementType(dpe); } catch (error) { throw new Error(`Cannot determine type of DPE ${dpe}: ${error}`); } } /** * Register alarm set tools * @param server - MCP server instance * @param context - Server context with winccoa, configs, etc. * @returns Number of tools registered */ export function registerTools(server, context) { const { winccoa } = context; server.tool("alarm-set", `Set or update alarm configuration for a datapoint element in WinCC OA. Supports both binary alarms (for BOOL datapoints) and analog alarms (for numeric datapoints). Binary alarm: Triggers when the value is TRUE (ASC) or FALSE (DESC) Analog alarm: Triggers based on threshold values (1-3 thresholds) Default alarm classes: - ASC (ascending): ['information.', 'warning.', 'alert.'] - DESC (descending): ['alert.', 'warning.', 'information.'] Custom alarm classes can be provided via the alarmClasses parameter. Examples: Binary alarm (ASC): { "dpe": "System1:MyBoolTag.", "direction": "ASC" } Analog alarm with 1 threshold: { "dpe": "System1:Temperature.", "direction": "ASC", "thresholds": [80] } Analog alarm with 3 thresholds: { "dpe": "System1:Pressure.", "direction": "ASC", "thresholds": [50, 75, 90] } With custom alarm classes: { "dpe": "System1:Level.", "direction": "DESC", "thresholds": [20, 10], "alarmClasses": ["low.", "critical.", "emergency."] } CAUTION: Alarm configurations directly affect production monitoring. Use with care. `, { config: z.union([ z.object({ dpe: z.string().describe('Datapoint element name (e.g., System1:MyTag.)'), direction: z.enum(['ASC', 'DESC']).describe('Alarm direction: ASC (ascending) or DESC (descending)'), thresholds: z.array(z.number()).optional().describe('Thresholds for analog alarms (1-3 values)'), alarmClasses: z.array(z.string()).optional().describe('Custom alarm classes (optional)'), force: z.boolean().optional().describe('Force update even if alert exists') }), z.string() ]) }, async ({ config }) => { try { // Parse string if needed let parsedConfig = typeof config === 'string' ? JSON.parse(config) : config; console.log('========================================'); console.log('Setting Alarm Configuration'); console.log('========================================'); console.log(`DPE: ${parsedConfig.dpe}`); // Check if DPE exists if (!winccoa.dpExists(parsedConfig.dpe)) { throw new Error(`DPE ${parsedConfig.dpe} does not exist in the system`); } // Get DPE type const dpeType = getDpeType(winccoa, parsedConfig.dpe); console.log(`DPE Type: ${dpeType}`); // Check if alert configuration already exists const hasConfig = await hasAlertConfig(winccoa, parsedConfig.dpe); if (hasConfig && !parsedConfig.force) { return createErrorResponse(`Alert configuration already exists for ${parsedConfig.dpe}. Use force: true to overwrite.`); } // Deactivate existing configuration if exists if (hasConfig) { await deactivateAlert(winccoa, parsedConfig.dpe); } // Binary alarm (BOOL) if (dpeType === DpeType.DPEL_BOOL) { const alarmClass = parsedConfig.alarmClasses ? parsedConfig.alarmClasses[0] : 'alert.'; await configureBinaryAlert(winccoa, parsedConfig.dpe, parsedConfig.direction, alarmClass); } // Analog alarm (numeric) else if ([ DpeType.DPEL_CHAR, DpeType.DPEL_INT, DpeType.DPEL_UINT, DpeType.DPEL_LONG, DpeType.DPEL_ULONG, DpeType.DPEL_FLOAT ].includes(dpeType)) { if (!parsedConfig.thresholds || parsedConfig.thresholds.length === 0) { throw new Error('Thresholds are required for analog alarms'); } await configureAnalogAlert(winccoa, parsedConfig.dpe, parsedConfig.thresholds, dpeType, parsedConfig.direction, parsedConfig.alarmClasses); } else { throw new Error(`Unsupported DPE type: ${dpeType}. Supported types are: BOOL (23), CHAR (19), INT (21), UINT (20), LONG (54), ULONG (58), FLOAT (22)`); } console.log('========================================'); console.log('✓ Alarm Configuration Complete'); console.log('========================================'); return createSuccessResponse({ dpe: parsedConfig.dpe, message: 'Alarm configuration set successfully', direction: parsedConfig.direction, thresholds: parsedConfig.thresholds || null, alarmClasses: parsedConfig.alarmClasses || DEFAULT_ALARM_CLASSES[parsedConfig.direction] }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error('========================================'); console.error('✗ Alarm Configuration Failed'); console.error('========================================'); console.error(`Error: ${errorMessage}`); return createErrorResponse(`Failed to set alarm configuration: ${errorMessage}`); } }); return 1; // Number of tools registered } //# sourceMappingURL=alarm_set.js.map