# Final Solution: Action Mapping Access via SDK Database Queries ## Discovery Summary After thorough SDK documentation research, I found the correct approach: ### How Action Mappings Work in GeViSoft 1. **Alarms** = Action mapping containers 2. **Actions** = Stored in separate Action table, linked to Alarms 3. **Action Relations**: - `AlarmRelation = 1` → Start Action (INPUT - the trigger) - `AlarmRelation = 2` → Follow Action (OUTPUT - what to execute) - `AlarmRelation = 0` → Standalone action (no alarm relation) ### SDK Functions to Use **Query Alarms**: ```csharp // Get first alarm var query = new GeViSQ_GetFirstAlarm(activeOnly: false, enabledOnly: false); await database.SendMessageAsync(query); // Receive: GeViSA_AlarmInfo (contains alarm ID, name, description) // Get next alarm var nextQuery = new GeViSQ_GetNextAlarm(); await database.SendMessageAsync(nextQuery); ``` **Query Actions for an Alarm**: ```csharp // Query all actions for a specific alarm var actionQuery = new GeViDBQ_CreateActionQuery(alarmPK); await database.SendMessageAsync(actionQuery); // Navigate results var getFirst = new GeViDBQ_GetFirst(); await database.SendMessageAsync(getFirst); // Receive: GeViDBA_ActionEntry var getNext = new GeViDBQ_GetNext(); await database.SendMessageAsync(getNext); ``` **GeViDBA_ActionEntry Fields**: ```csharp class GeViDBA_ActionEntry { Int64 sPK; // Action primary key GeViActionData sActionData; // Action data (contains action string) Int32 sAlarmRelation; // 0=none, 1=input(start), 2=output(follow) Int64 sAlarmReference; // FK to alarm String sActionText; // Optional text DateTime sLocalTime, sGMTTime; // Timestamps } struct GeViActionData { String _ActionData; // Actual action command: "VMD_Start(101038)" } ``` ## Implementation Plan ### 1. Create AlarmQueryService.cs ```csharp public class AlarmQueryService { private readonly GeViDatabaseWrapper _database; private readonly List _alarmCache = new(); private TaskCompletionSource _queryComplete; public async Task> GetAllActionMappingsAsync() { var alarms = await EnumerateAllAlarmsAsync(); var result = new List(); foreach (var alarm in alarms) { var actions = await GetActionsForAlarmAsync(alarm.GlobalID); var inputActions = actions .Where(a => a.AlarmRelation == 1) .Select(a => a.ActionData._ActionData) .ToList(); var outputActions = actions .Where(a => a.AlarmRelation == 2) .Select(a => a.ActionData._ActionData) .ToList(); if (inputActions.Any() && outputActions.Any()) { result.Add(new AlarmWithActions { AlarmID = alarm.GlobalID, Name = alarm.Name, Description = alarm.Description, InputAction = inputActions.FirstOrDefault(), OutputActions = outputActions }); } } return result; } private async Task> EnumerateAllAlarmsAsync() { _alarmCache.Clear(); _queryComplete = new TaskCompletionSource(); // Register callbacks _database.Database.OnNewGeViSA_FirstAlarm += HandleFirstAlarm; _database.Database.OnNewGeViSA_NextAlarm += HandleNextAlarm; // Send first query var query = new GeViSQ_GetFirstAlarm(false, false); await _database.SendMessageAsync(query); // Wait for completion (with timeout) await Task.WhenAny(_queryComplete.Task, Task.Delay(5000)); // Unregister callbacks _database.Database.OnNewGeViSA_FirstAlarm -= HandleFirstAlarm; _database.Database.OnNewGeViSA_NextAlarm -= HandleNextAlarm; return _alarmCache; } private void HandleFirstAlarm(object sender, GeViSA_FirstAlarmEventArgs e) { _alarmCache.Add(new AlarmInfo { GlobalID = e.StateAnswer.sGlobalID, Name = e.StateAnswer.sName, Description = e.StateAnswer.sDescription }); // Request next var nextQuery = new GeViSQ_GetNextAlarm(); _database.SendMessageAsync(nextQuery).Wait(); } private void HandleNextAlarm(object sender, GeViSA_NextAlarmEventArgs e) { if (e.StateAnswer != null && !string.IsNullOrEmpty(e.StateAnswer.sName)) { _alarmCache.Add(new AlarmInfo { GlobalID = e.StateAnswer.sGlobalID, Name = e.StateAnswer.sName, Description = e.StateAnswer.sDescription }); // Request next var nextQuery = new GeViSQ_GetNextAlarm(); _database.SendMessageAsync(nextQuery).Wait(); } else { // No more alarms _queryComplete.TrySetResult(true); } } private async Task> GetActionsForAlarmAsync(int alarmGlobalID) { var actions = new List(); var actionQueryComplete = new TaskCompletionSource(); // Register action answer callback EventHandler actionHandler = null; actionHandler = (sender, e) => { if (e.Answer != null && e.Answer.sActionData?._ActionData != null) { actions.Add(new ActionEntry { PK = e.Answer.sPK, ActionData = e.Answer.sActionData, AlarmRelation = e.Answer.sAlarmRelation, AlarmReference = e.Answer.sAlarmReference }); // Request next var nextQuery = new GeViDBQ_GetNext(); _database.SendMessageAsync(nextQuery).Wait(); } else { // No more actions actionQueryComplete.TrySetResult(true); } }; _database.Database.OnNewGeViDBA_ActionEntry += actionHandler; try { // Create query for this alarm's actions var query = new GeViDBQ_CreateActionQuery(alarmGlobalID); await _database.SendMessageAsync(query); // Get first result var getFirst = new GeViDBQ_GetFirst(); await _database.SendMessageAsync(getFirst); // Wait for completion await Task.WhenAny(actionQueryComplete.Task, Task.Delay(2000)); } finally { _database.Database.OnNewGeViDBA_ActionEntry -= actionHandler; } return actions; } } ``` ### 2. Update ActionMappingHandler.cs Replace in-memory storage with SDK queries: ```csharp public class ActionMappingHandler { private readonly AlarmQueryService _alarmQuery; public ActionMappingHandler(GeViDatabaseWrapper database) { _alarmQuery = new AlarmQueryService(database); } public async Task> GetAllActionMappingsAsync() { var alarmsWithActions = await _alarmQuery.GetAllActionMappingsAsync(); return alarmsWithActions.Select(a => new ActionMappingConfig { Id = $"alarm_{a.AlarmID}", Name = a.Name, Description = a.Description, InputAction = a.InputAction, OutputActions = a.OutputActions, Enabled = true // Alarms are enabled if they exist }).ToList(); } } ``` ### 3. Test Implementation ```csharp // In Program.cs or test file var mappings = await actionMappingHandler.GetAllActionMappingsAsync(); foreach (var mapping in mappings) { Console.WriteLine($"Mapping: {mapping.Name}"); Console.WriteLine($" Input: {mapping.InputAction}"); Console.WriteLine($" Outputs: {string.Join(", ", mapping.OutputActions)}"); } ``` ## Advantages ✅ No direct database access - uses SDK ✅ GeViServer queries its own database ✅ Respects database locking ✅ Gets LIVE data from running GeViSoft ✅ Same data that GeViSet sees ✅ Can receive real-time updates via events ## Next Steps 1. Implement AlarmQueryService.cs 2. Update ActionMappingHandler.cs 3. Test enumeration with live GeViServer 4. Implement create/update/delete if SDK supports it 5. Add error handling and timeout logic ## Note on Write Operations For creating/updating action mappings, we'll need to investigate if the SDK provides action creation commands or if we need to send raw SQL through a different SDK mechanism. The SDK documentation showed delete handles (`GeViDBQ_CreateDeleteActionsHandle`, `GeViDBQ_CreateDeleteAlarmsHandle`) so write operations may be supported.