# Service Pattern (Background Services) ## When to Use - Implementing hardware communication (SLICE, TDAS, Ribeye) - Long-running background operations - Real-time data acquisition - Download operations from hardware devices - Diagnostics and calibration services ## Architecture Overview ``` ┌──────────────────────────────────────────────────────────────────────┐ │ ServiceBase │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Callback Pattern: ServiceCallback(CallbackData) │ │ │ │ - Progress: Report progress percentage │ │ │ │ - NewData: Real-time sample data │ │ │ │ - Success/Failure: Operation completion │ │ │ │ - Canceled: User cancellation │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ State Machine: Stateless-based state transitions │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ │ │ │ Prepare │───>│Configure│───>│ Arm │───>│ Realtime │ │ │ └─────────┘ └─────────┘ └─────────┘ └──────────┘ │ │ │ │ │ │ │ │ v v v v │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ │ │ │ Diagnose│ │ Download│ │ Disarm │ │ Stop │ │ │ └─────────┘ └─────────┘ └─────────┘ └──────────┘ │ └──────────────────────────────────────────────────────────────────────┘ ``` ## Files to Create/Modify ### Structure ``` IService/ ├── Classes/ │ ├── ServiceBase.cs (Base class for all services) │ ├── GenericServices.cs (Service orchestration) │ ├── SLICEService/ │ │ ├── SLICE Service.cs │ │ ├── SLICE Service.Configuration.cs │ │ ├── SLICE Service.Realtime.cs │ │ └── SLICE Service.Download.cs │ └── TDAS Service/ ├── Interfaces/ │ ├── IConfigurationActions.cs │ ├── IRealTimeActions.cs │ ├── IDownloadActions.cs │ └── IDiagnosticsActions.cs └── StateMachine/ ├── States.cs ├── Triggers.cs └── States/ ├── Configure.cs ├── Realtime.cs └── Download.cs ``` ## Code Templates ### 1. Service Base Class Pattern ```csharp using System; using System.Threading; using DTS.Common.Utilities.Logging; namespace DTS.DASLib.Service { public abstract class ServiceBase : IDisposable { public bool AggregateProgress { get; set; } public class CallbackData { public enum CallbackStatus { Progress, NewData, AllFinished, Success, Failure, Canceled } public CallbackStatus Status { get; set; } public IDASCommunication Target { get; set; } public string ErrorMessage { get; set; } public Exception ErrorException { get; set; } public int ProgressValue { get; set; } public SampleData[] Data { get; set; } } public delegate void Callback(CallbackData data); public delegate void ServiceBaseEventHandler(object sender, CallbackData data); public event ServiceBaseEventHandler ServiceAvailable; public event ServiceCallbackErrorEventHandler ServiceCallbackError; public abstract string ServiceName(); public virtual void Cancel() { APILogger.LogString($"Entering {ServiceName()}.Cancel"); DASServiceLock.Cancel(this); } protected void RunService(Callback userCallback, object userObject) { var glue = new ServiceGlueClass(userCallback, userObject); // Service orchestration logic } protected void FireCallback(CallbackData data) { // Fire callback to user } } } ``` ### 2. Hardware Service Implementation ```csharp using System; using System.Threading; using DTS.Common.DASResource; using DTS.Common.Interface.DASFactory; namespace DTS.DASLib.Service { public class SLICEService : ServiceBase, IConfigurationActions, IRealTimeActions, IDownloadActions { public override string ServiceName() => "SLICE Service"; #region Configuration public void Configure( IDASCommunication[] targets, IServiceConfiguration config, Callback callback, object userObject) { APILogger.LogString($"{ServiceName()}.Configure starting"); try { foreach (var target in targets) { var slice = target as ISLICE; if (slice == null) continue; ApplyConfiguration(slice, config); } callback?.Invoke(new CallbackData { Status = CallbackData.CallbackStatus.Success, Target = null }); } catch (Exception ex) { callback?.Invoke(new CallbackData { Status = CallbackData.CallbackStatus.Failure, ErrorException = ex, ErrorMessage = ex.Message }); } } #endregion #region Realtime public void StartRealtime( IDASCommunication[] targets, Callback callback, object userObject) { APILogger.LogString($"{ServiceName()}.StartRealtime"); RunService(callback, userObject); foreach (var target in targets) { var slice = target as ISLICE; slice?.StartRealtime(OnRealtimeData, target); } } private void OnRealtimeData(SampleData[] data, object context) { FireCallback(new CallbackData { Status = CallbackData.CallbackStatus.NewData, Data = data, Target = context as IDASCommunication }); } public void StopRealtime(IDASCommunication[] targets) { foreach (var target in targets) { var slice = target as ISLICE; slice?.StopRealtime(); } } #endregion #region Download public void Download( IDASCommunication[] targets, IDownloadParameters parameters, Callback callback, object userObject) { APILogger.LogString($"{ServiceName()}.Download"); foreach (var target in targets) { var slice = target as ISLICE; slice?.Download( parameters, (progress, data, error) => { if (!string.IsNullOrEmpty(error)) { callback?.Invoke(new CallbackData { Status = CallbackData.CallbackStatus.Failure, ErrorMessage = error, Target = target }); } else if (data != null) { callback?.Invoke(new CallbackData { Status = CallbackData.CallbackStatus.NewData, Data = data, ProgressValue = progress, Target = target }); } }); } callback?.Invoke(new CallbackData { Status = CallbackData.CallbackStatus.AllFinished, Target = null }); } #endregion } } ``` ### 3. Service Interface ```csharp namespace DTS.DASLib.Service.Interfaces { public interface IRealTimeActions { void StartRealtime(IDASCommunication[] targets, ServiceBase.Callback callback, object userObject); void StopRealtime(IDASCommunication[] targets); } public interface IDownloadActions { void Download(IDASCommunication[] targets, IDownloadParameters parameters, ServiceBase.Callback callback, object userObject); } public interface IConfigurationActions { void Configure(IDASCommunication[] targets, IServiceConfiguration config, ServiceBase.Callback callback, object userObject); } } ``` ## Examples from Codebase ### Example 1: ServiceBase Callback Pattern **File:** `DataPRO/IService/Classes/GenericServices.cs:35-122` ```csharp public abstract class ServiceBase : IDisposable { public class CallbackData { public enum CallbackStatus { Progress, NewData, AllFinished, Success, Failure, Canceled } public CallbackStatus Status { get; set; } public IDASCommunication Target { get; set; } public string ErrorMessage { get; set; } public Exception ErrorException { get; set; } public int ProgressValue { get; set; } public SampleData[] Data { get; set; } } public delegate void Callback(CallbackData data); public event ServiceBaseEventHandler ServiceAvailable; public abstract string ServiceName(); public virtual void Cancel() { APILogger.LogString($"Entering {ServiceName()}.Cancel"); DASServiceLock.Cancel(this); } } ``` ### Example 2: Service with Progress Reporting **File:** `DataPRO/IService/Classes/GenericServices.cs:198` ```csharp protected void ServiceIsAvailable(object sender, CallbackData data) { ServiceAvailable -= ServiceIsAvailable; // Handle service completion } protected class ServiceGlueClass { public object UserObject { get; set; } public Callback UserCallback { get; set; } public ManualResetEvent AvailableEvent { get; set; } public ServiceGlueClass(Callback userCallback, object userObject) { UserObject = userObject; UserCallback = userCallback; AvailableEvent = new ManualResetEvent(false); } } ``` ### Example 3: DASModule Configuration **File:** `DataPRO/IService/Classes/DASModule.cs` ```csharp public class DASModule { public List AnalogInputChannels { get; set; } public List DigitalOutputChannels { get; set; } public void Configure(IServiceConfiguration config) { // Apply configuration to all channels } } ``` ## Common Mistakes to Avoid 1. **Not handling cancellation** - Always implement `Cancel()` to clean up resources 2. **Blocking the callback thread** - Use async patterns, don't block in callbacks 3. **Missing error handling in callbacks** - Always check for null callback before invoking 4. **Not disposing connections** - Implement `IDisposable` properly 5. **Fire-and-forget without error logging** - Always log errors even if no callback 6. **Cross-thread UI updates** - Use `Dispatcher.Invoke` for UI updates from service callbacks 7. **Not using ServiceAvailable event** - Must unsubscribe to prevent memory leaks 8. **Ignoring Target in CallbackData** - Needed to identify which unit responded 9. **Not checking AggregateProgress** - Affects how progress is reported for multiple units 10. **Missing ServiceName() override** - Required for logging identification