using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.PMR; using Tango.PMR.Diagnostics; using Tango.Transport; using Tango.Transport.Transporters; using System.Reactive.Linq; using System.Reactive.Concurrency; using System.Reactive.Threading; using Tango.PMR.Common; using Tango.PMR.Printing; using System.Reactive.Subjects; using Tango.PMR.Debugging; using Tango.Logging; using Tango.Settings; using System.IO; using Tango.BL.Entities; using Tango.PMR.Hardware; using Google.Protobuf; using Tango.PMR.Connection; using Tango.BL.Enumerations; using Tango.PMR.Stubs; using System.Threading; using Tango.Integration.Storage; using Ionic.Zip; using Tango.Core.Threading; using Tango.PMR.IO; using Tango.Integration.Upgrade; using Tango.PMR.FirmwareUpgrade; using Tango.Integration.Logging; using Tango.Integration.JobRuns; using Tango.FirmwareUpdateLib.WPF; using Tango.FirmwareUpdateLib; using Tango.Core.ExtensionMethods; using Tango.ColorConversion; using Tango.Integration.Emergency; using Tango.PMR.MachineStatus; using Newtonsoft.Json; using Tango.PMR.Integration; using System.Globalization; using Tango.PMR.Power; using Tango.PMR.ThreadLoading; using Tango.BL.DTO; using Tango.PMR.IFS; using System.Runtime.CompilerServices; using Tango.BL.Interfaces; namespace Tango.Integration.Operation { /// /// Represents the Tango machine operator default implementation. /// /// /// public class MachineOperator : BasicTransporter, IMachineOperator { public const String FIRMWARE_UPGRADE_FOLDER_NAME = "UpgradePackage"; public const String FIRMWARE_UPGRADE_CONFIG_FILE_NAME = "package.cfg"; public const String JOB_DESCRIPTION_FILE_NAME = "job_segments.jdf"; private String[] EUREKA_FIRMWARE_UPGRADE_DRIVE_LABELS = { "NOD_H743ZI2", "NOD_H753ZI2", "NOD_H753ZI", "STLINK_V3P" }; public const int MAX_DISPENSER_NANOLITER = 130000000; public const double MAX_MIDTANK_LITERS = 1.8; public const double EMPTY_MIDTANK_LITERS = 0.2; public const double LOW_MIDTANK_LITERS = 0.3; public const double OVERALL_TEMPERATURE_OK = 35; public const double OVERALL_TEMPERATURE_WARNING = 35; public const double OVERALL_TEMPERATURE_ERROR = 40; private bool _diagnosticsSent; private bool _eventsSent; private bool _debugSent; private bool _machineStatusSent; private bool _inkFillingStatusSent; private bool _telemetryWireSent; private bool _notificationsSent; private bool _threadLoadingSent; private bool _isPowerDownRequestInProgress; private bool _isHeadCleaningInProgress; private DateTime _diagnosticsTime; private Configuration _machineConfiguration; private MachineStatus _lastMachineStatus; protected class CurrentJobContext { private List _liquidQuantities; private Func> _getLiquidQuantities; public MachineStatus MachineStatusOnCreation { get; private set; } public DateTime StartDate { get; set; } public DateTime? UploadingStartDate { get; set; } public DateTime? HeatingStartDate { get; set; } public DateTime? ActualStartDate { get; set; } public Job Job { get; set; } public Job OriginalJob { get; set; } public Job ClonedJob { get; set; } public Job JobForJobRun { get; set; } public JobTicket JobTicket { get; set; } public JobHandler JobHandler { get; set; } public bool RequestSent { get; set; } public JobRequest JobRequest { get; set; } public bool JobCompleted { get; set; } public bool ResponseLogged { get; set; } public bool InitialUseKeepAlive { get; set; } public bool IsResumingJob { get; set; } public List GetLiquidQuantities() { if (_liquidQuantities == null) { _liquidQuantities = _getLiquidQuantities(this).ToList(); } return _liquidQuantities; } public CurrentJobContext(MachineStatus currentMachineStatus, Func> getLiquidQuantities) { MachineStatusOnCreation = currentMachineStatus.Clone(); _getLiquidQuantities = getLiquidQuantities; JobRequest = new JobRequest(); StartDate = DateTime.UtcNow; } } private List _emulatedEvents; private List _bitResults; private JobSpoolType _currentSpoolType; private String _lastWasteReplaceRequestToken; public static String EmbeddedLogsFolder { get; private set; } public static String EmbeddedLogsTag { get; private set; } public static SessionFileLogger SessionLogger { get; set; } public static String CachedJobOperationFile { get; set; } #region Classes private class RequiredLiquid { public IdsPack IdsPack { get; set; } public long Quantity { get; set; } } #endregion #region Constructors /// /// Initializes the class. /// static MachineOperator() { if (EmbeddedLogManager == null) { EmbeddedLogManager = new LogManager(); EmbeddedLogsTag = "Embedded"; EmbeddedLogsFolder = Path.Combine(Path.GetDirectoryName(SettingsManager.Default.Folder), "Logs", Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName), "Embedded"); Directory.CreateDirectory(EmbeddedLogsFolder); FileLogger fileLogger = new FileLogger(EmbeddedLogsFolder, EmbeddedLogsTag) { Enabled = true }; EmbeddedLogManager.RegisterLogger(fileLogger); } if (SessionLogger == null) { SessionLogger = new SessionFileLogger(); LogManager.Default.RegisterLogger(SessionLogger); } CachedJobOperationFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "Job Resume", Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName), "CachedJobOperation.cache"); } /// /// Initializes a new instance of the class. /// public MachineOperator() : base() { _bitResults = new List(); _emulatedEvents = new List(); ComponentName = $"Machine Operator {_component_counter++}"; DeviceInformation = new DeviceInformation(); MachineEventsStateProvider = new DefaultMachineEventsStateProvider(); JobRunsLogger = new BasicJobRunsLogger(this); JobRunsLogger.Start(); EnableEventsNotification = true; EnableMachineStatusUpdates = true; EnableInkFillingStatus = true; EnableJobResume = true; LogEmbeddedDebuggingToFile = true; FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; GradientGenerationConfiguration = new DefaultGradientGenerationConfiguration(); EmergencyNotificationProvider = new UsbEmergencyNotificationProvider("COM1"); EnableJobLiquidQuantityValidation = true; FailsWithAdapter = true; IsSpoolReplaced = true; ContinuousRequestTimeout = TimeSpan.FromSeconds(2); ResetInkFllingStatus(); } /// /// Initializes a new instance of the class. /// /// The transport adapter. public MachineOperator(ITransportAdapter adapter) : this() { Adapter = adapter; } #endregion #region Events /// /// Occurs when the machine has changed. /// public event EventHandler StatusChanged; /// /// Occurs when there is new diagnostics data available. /// public event EventHandler DiagnosticsDataAvailable; /// /// Occurs when an events notification has been received from the embedded device. /// public event EventHandler EventsNotification; /// /// Occurs when a new debug log is available. /// public event EventHandler DebugLogAvailable; /// /// Occurs when machine embedded device status has changed. /// public event EventHandler MachineStatusChanged; /// /// Occurs when a new cartridge validation request has been received. /// public event EventHandler CartridgeValidationRequestReceived; /// /// Reports about the job printing preparation progress. /// public event EventHandler PreparingJobProgress; /// /// Occurs when a printing process has started. /// public event EventHandler PrintingStarted; /// /// Occurs when a printing process has completed. /// public event EventHandler PrintingCompleted; /// /// Occurs when a printing process has failed. /// public event EventHandler PrintingFailed; /// /// Occurs when a printing process has been aborted. /// public event EventHandler PrintingAborted; /// /// Occurs when a printing process has ended. /// public event EventHandler PrintingEnded; /// /// Occurs when the machine operator has detected that a job is in progress after connecting to the machine. /// public event EventHandler ResumingJob; /// /// Occurs when the machine was connected and device has reported IsAfterReset. /// public event EventHandler FirmwareStarted; /// /// Occurs when power down has started. /// public event EventHandler PowerDownStarted; /// /// Occurs when the thread loading status has changed. /// public event EventHandler ThreadLoadingStatusChanged; /// /// Occurs when a thread loading confirmation is required. /// public event EventHandler ThreadLoadingConfirmationRequired; /// /// Occurs when thread loading has completed. /// public event EventHandler ThreadLoadingCompleted; /// /// Occurs when thread loading has failed. /// public event EventHandler ThreadLoadingFailed; /// /// Occurs when the power up sequence has started. /// public event EventHandler PowerUpStarted; /// /// Occurs when the power up sequence progress has changed. /// public event EventHandler PowerUpProgress; /// /// Occurs when power up sequence has completed successfully. /// public event EventHandler PowerUpCompleted; /// /// Occurs when power up sequence has failed. /// public event EventHandler PowerUpFailed; /// /// Occurs when power up sequence has ended. Could be due to no response to the request! /// public event EventHandler PowerUpEnded; /// /// Occurs when a head cleaning job has ended. /// public event EventHandler HeadCleaningEnded; /// /// Occurs when the ink filling status has changed. /// public event EventHandler InkFillingStatusChanged; /// /// Occurs when waste replacement is required. /// public event EventHandler WasteReplacementRequired; public event EventHandler TelemetryWireAvailable; public event EventHandler NotificationAvailable; #endregion #region Properties /// /// Gets or sets a value indicating whether to create a new designated session log file each successful connection. /// This log file will contain standard logs that have occurred between the last connection and disconnection states. /// public static bool EnableSessionLogFile { get { return SessionLogger.Enabled; } set { SessionLogger.Enabled = value; } } /// /// Gets or sets the job handling mode. /// public JobHandlerModes JobHandlingMode { get; set; } /// /// Gets or sets the job upload strategy. /// public JobUploadStrategy JobUploadStrategy { get; set; } /// /// Gets or sets the job number of units duplication method. /// public JobUnitsMethods JobUnitsMethod { get; set; } /// /// Gets or sets the way of calculating how much liquid was spent during the job. /// public JobLiquidQuantityCalculationMode JobLiquidQuantityCalculationMode { get; set; } private MachineStatuses _status; /// /// Gets the current machine status. /// public MachineStatuses Status { get { return _status; } protected set { if (_status != value) { _status = value; RaisePropertyChangedAuto(); OnStatusChanged(value); RaisePropertyChanged(nameof(IsPrinting)); RaisePropertyChanged(nameof(CanPrint)); RaisePropertyChanged(nameof(IsConnected)); } } } /// /// Gets a value indicating whether the machine is connected and status is not disconnected. /// public bool IsConnected { get { return State == TransportComponentState.Connected && Status != MachineStatuses.Disconnected; } } private MachineStatus _machineStatus; /// /// Gets the machine embedded device status. /// public MachineStatus MachineStatus { get { return _machineStatus; } private set { _machineStatus = value; RaisePropertyChangedAuto(); } } private InkFillingStatus _inkFillingStatus; /// /// Gets or sets the ink filling status. /// public InkFillingStatus InkFillingStatus { get { return _inkFillingStatus; } private set { _inkFillingStatus = value; RaisePropertyChangedAuto(); } } private StartThreadLoadingResponse _threadLoadingStatus; /// /// Gets the current thread loading status. /// public StartThreadLoadingResponse ThreadLoadingStatus { get { return _threadLoadingStatus; } private set { _threadLoadingStatus = value; RaisePropertyChangedAuto(); } } /// /// Gets or sets a value indicating whether to enable liquid quantity validation before starting the job. /// The validation is done using the reported . /// public bool EnableJobLiquidQuantityValidation { get; set; } /// /// Gets or sets the firmware upgrade mode. /// public FirmwareUpgradeModes FirmwareUpgradeMode { get; set; } /// /// Gets a value indicating whether this instance is printing. /// public bool IsPrinting { get { return Status == MachineStatuses.Printing || Status == MachineStatuses.GettingReady; } } /// /// Gets a value indicating whether this instance can print. /// public bool CanPrint { get { return Status == MachineStatuses.ReadyToDye || Status == MachineStatuses.PowerUp || Status == MachineStatuses.Standby; } } private Job _runningJob; /// /// Gets the running job. /// public Job RunningJob { get { return _runningJob; } set { _runningJob = value; RaisePropertyChangedAuto(); } } private RunningJobStatus _runningJobStatus; /// /// Gets the running job status. /// public RunningJobStatus RunningJobStatus { get { return _runningJobStatus; } set { _runningJobStatus = value; RaisePropertyChangedAuto(); } } /// /// Gets the embedded device log manager. /// public static LogManager EmbeddedLogManager { get; private set; } private bool _enableDiagnostics; /// /// Gets or sets a value indicating whether direct the embedded device to send diagnostics messages. /// public bool EnableDiagnostics { get { return _enableDiagnostics; } set { if (_enableDiagnostics != value) { _enableDiagnostics = value; RaisePropertyChangedAuto(); OnEnableDiagnosticsChanged(value); } } } private bool _enableEventsNotification; /// /// Gets or sets a value indicating whether direct the embedded device to send events notification messages. /// public bool EnableEventsNotification { get { return _enableEventsNotification; } set { if (_enableEventsNotification != value) { _enableEventsNotification = value; RaisePropertyChangedAuto(); OnEnableEventsNotification(value); } } } private bool _enableEmbeddedDebugging; /// /// Gets or sets a value indicating whether to allow incoming debugging messages. /// /// /// public bool EnableEmbeddedDebugging { get { return _enableEmbeddedDebugging; } set { if (_enableEmbeddedDebugging != value) { _enableEmbeddedDebugging = value; RaisePropertyChangedAuto(); OnEnableEmbeddedDebuggingChanged(value); } } } private bool _enableMachineStatusUpdates; /// /// Gets or sets a value indicating whether to direct the embedded device to update about status changes. /// public bool EnableMachineStatusUpdates { get { return _enableMachineStatusUpdates; } set { if (_enableMachineStatusUpdates != value) { _enableMachineStatusUpdates = value; RaisePropertyChangedAuto(); OnEnableMachineStatusUpdatesChanged(value); } } } private bool _enableInkFillingStatus; public bool EnableInkFillingStatus { get { return _enableInkFillingStatus; } set { if (_enableInkFillingStatus != value) { _enableInkFillingStatus = value; RaisePropertyChangedAuto(); OnEnableInkFillingStatus(value); } } } private bool _enableAutomaticThreadLoading; /// /// Gets or sets a value indicating whether to enable automatic thread loading support. /// public bool EnableAutomaticThreadLoading { get { return _enableAutomaticThreadLoading; } set { _enableAutomaticThreadLoading = value; RaisePropertyChangedAuto(); OnEnableAutomaticThreadLoadingChanged(value); } } private bool _enableJobResume; /// /// Gets or sets a value indicating whether to check whether a job is in progress after connection was successful. /// public bool EnableJobResume { get { return _enableJobResume; } set { _enableJobResume = value; RaisePropertyChangedAuto(); } } private bool _logEmbeddedDebuggingToFile; /// /// Gets or sets a value indicating whether to automatically save incoming log data from the embedded device. /// public bool LogEmbeddedDebuggingToFile { get { return _logEmbeddedDebuggingToFile; } set { _logEmbeddedDebuggingToFile = value; RaisePropertyChangedAuto(); } } private bool _enablePowerUpSequence; /// /// Gets or sets a value indicating whether to enable the power sequence tracking. /// public bool EnablePowerUpSequence { get { return _enablePowerUpSequence; } set { _enablePowerUpSequence = value; RaisePropertyChangedAuto(); } } private bool _enableTelemetryWire; public bool EnableTelemetryWire { get { return _enableTelemetryWire; } set { if (_enableTelemetryWire != value) { _enableTelemetryWire = value; RaisePropertyChangedAuto(); OnEnableTelemetryWireChanged(value); } } } private bool _enableNotifications; public bool EnableNotifications { get { return _enableNotifications; } set { if (_enableNotifications != value) { _enableNotifications = value; RaisePropertyChangedAuto(); OnEnableNotificationsChanged(value); } } } /// /// Gets or sets the machine events state provider used to get notifications about current machine events and errors. /// public IMachineEventsStateProvider MachineEventsStateProvider { get; set; } /// /// Gets or sets the job runs logger. /// public IJobRunsLogger JobRunsLogger { get; set; } /// /// Gets the last process parameters table sent to the embedded device. /// public ProcessParametersTable CurrentProcessParameters { get; private set; } /// /// Gets the last hardware configuration sent to the embedded device. /// public HardwareConfiguration CurrentHardwareConfiguration { get; private set; } private DeviceInformation _deviceInformation; /// /// Gets or sets the embedded device information. /// public DeviceInformation DeviceInformation { get { return _deviceInformation; } set { _deviceInformation = value; RaisePropertyChangedAuto(); } } private IGradientGenerationConfiguration _gradientGenerationConfiguration; /// /// Gets or sets the gradients generation configuration. /// public IGradientGenerationConfiguration GradientGenerationConfiguration { get { return _gradientGenerationConfiguration; } set { _gradientGenerationConfiguration = value; RaisePropertyChangedAuto(); } } /// /// Gets or sets the emergency notification provider. /// public IEmergencyNotificationProvider EmergencyNotificationProvider { get; set; } /// /// Gets or sets the general continuous request timeout. /// public TimeSpan ContinuousRequestTimeout { get; set; } /// /// Gets a value indicating whether the spool was replaced after the last job. /// public bool IsSpoolReplaced { get; private set; } /// /// Gets or sets the type of the machine. /// public MachineTypes MachineType { get; set; } #endregion #region Virtual Methods /// /// Called when the enable diagnostics property has been changed /// /// if set to true [value]. protected virtual async void OnEnableDiagnosticsChanged(bool value) { if (value && State == TransportComponentState.Connected && !_diagnosticsSent) { var request = new StartDiagnosticsRequest(); bool responseLogged = false; _diagnosticsSent = true; LogManager.Log($"Sending '{nameof(StartDiagnosticsRequest)}'..."); SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = false }).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { if (!responseLogged) { _diagnosticsTime = DateTime.Now; responseLogged = true; } else { _diagnosticsTime = _diagnosticsTime.Add(TimeSpan.FromMilliseconds(response.Message.ElapsedMilli)); } response.Message.DateTime = _diagnosticsTime.ToString("MM/dd/yyyy HH:mm:ss.fff"); OnDiagnosticsDataAvailable(response); }, (ex) => { _diagnosticsSent = false; }, () => { _diagnosticsSent = false; LogManager.Log("Diagnostics response completed!?", LogCategory.Warning); }); } else if (_diagnosticsSent) { _diagnosticsSent = false; if (State == TransportComponentState.Connected) { var req = new StopDiagnosticsRequest(); try { var res = await SendRequest(req, new TransportRequestConfig() { ShouldLog = true }); } catch { } } } } private void OnEnableTelemetryWireChanged(bool value) { if (value && State == TransportComponentState.Connected && !_telemetryWireSent) { var request = new StartTelemetryWireRequest(); _telemetryWireSent = true; LogManager.Log($"Sending '{nameof(StartTelemetryWireRequest)}'..."); SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = false }).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { OnTelemetryWireAvailable(response); }, (ex) => { _telemetryWireSent = false; }, () => { _telemetryWireSent = false; LogManager.Log("Telemetry Wire response completed!?", LogCategory.Warning); }); } } private void OnEnableNotificationsChanged(bool value) { if (value && State == TransportComponentState.Connected && !_notificationsSent) { var request = new StartNotificationRequest(); _notificationsSent = true; LogManager.Log($"Sending '{nameof(StartNotificationRequest)}'..."); SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = false }).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { OnNotificationAvailable(response); }, (ex) => { _notificationsSent = false; }, () => { _notificationsSent = false; LogManager.Log("Notifications response completed!?", LogCategory.Warning); }); } } /// /// Called when the enable events property has been changed. /// /// if set to true [value]. protected virtual async void OnEnableEventsNotification(bool value) { if (value && State == TransportComponentState.Connected && !_eventsSent) { var request = new StartEventsNotificationRequest(); bool responseLogged = false; _eventsSent = true; SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = true }).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { OnEventsNotification(response); if (!responseLogged) { responseLogged = true; } }, (ex) => { _eventsSent = false; }, () => { _eventsSent = false; LogManager.Log("Events Notification response completed!?", LogCategory.Warning); }); } else if (_eventsSent) { _eventsSent = false; if (State == TransportComponentState.Connected) { var req = new StopEventsNotificationRequest(); try { var res = await SendRequest(req, new TransportRequestConfig() { ShouldLog = true }); } catch { } } } } /// /// Called when the enable embedded debugging has been changed /// /// if set to true [value]. protected virtual async void OnEnableEmbeddedDebuggingChanged(bool value) { if (value && State == TransportComponentState.Connected && !_debugSent) { var request = new StartDebugLogRequest(); bool responseLogged = false; _debugSent = true; SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = true }).ObserveOn(new NewThreadScheduler()) .Subscribe ( (response) => { if (!responseLogged) { responseLogged = true; } OnDebugLogAvailable(response); }, (ex) => { _debugSent = false; }, () => { _debugSent = false; }); } else if (_debugSent) { _debugSent = false; if (State == TransportComponentState.Connected) { var req = new StopDebugLogRequest(); try { var res = await SendRequest(req, new TransportRequestConfig() { ShouldLog = true }); } catch { } } } } /// /// Called when the enable machine status updates has been changed. /// /// if set to true [value]. protected virtual async void OnEnableMachineStatusUpdatesChanged(bool value) { if (value && State == TransportComponentState.Connected && !_machineStatusSent) { var request = new StartMachineStatusUpdateRequest(); bool responseLogged = false; _machineStatusSent = true; SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = true }).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { _lastMachineStatus = response.Message.Status?.Clone(); OnMachineStatusChanged(response); if (!responseLogged) { responseLogged = true; } }, (ex) => { _machineStatusSent = false; }, () => { _machineStatusSent = false; LogManager.Log("Machine status update response completed!?", LogCategory.Warning); }); } else if (_machineStatusSent) { _machineStatusSent = false; if (State == TransportComponentState.Connected) { var req = new StopMachineStatusUpdateRequest(); try { var res = await SendRequest(req, new TransportRequestConfig() { ShouldLog = true }); } catch { } } } } /// /// Called when the enable ink filling status has been changed. /// /// if set to true [value]. protected virtual void OnEnableInkFillingStatus(bool value) { if (value && State == TransportComponentState.Connected && !_inkFillingStatusSent) { var request = new StartInkFillingStatusRequest(); bool responseLogged = false; _inkFillingStatusSent = true; SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = true }).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { OnInkFillingStatusChanged(response); if (!responseLogged) { responseLogged = true; } }, (ex) => { _inkFillingStatusSent = false; }, () => { _inkFillingStatusSent = false; LogManager.Log("Ink filling status response completed!?", LogCategory.Warning); }); } else if (_inkFillingStatusSent) { _inkFillingStatusSent = false; } } /// /// Called when the enable automatic thread loading has been changed /// /// if set to true [value]. protected virtual async void OnEnableAutomaticThreadLoadingChanged(bool value) { if (value && State == TransportComponentState.Connected && !_threadLoadingSent) { var request = new StartThreadLoadingRequest(); bool responseLogged = false; _threadLoadingSent = true; SendContinuousRequest(request, new TransportContinuousRequestConfig() { ShouldLog = true }).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { OnThreadLoadingStatusChanged(response); if (!responseLogged) { responseLogged = true; } }, (ex) => { _threadLoadingSent = false; }, () => { _threadLoadingSent = false; LogManager.Log("Thread loading response completed!?", LogCategory.Warning); }); } else if (_threadLoadingSent) { _threadLoadingSent = false; if (State == TransportComponentState.Connected) { var req = new StopThreadLoadingRequest(); try { var res = await SendRequest(req, new TransportRequestConfig() { ShouldLog = true }); } catch { } } } } /// /// Invokes the event. /// /// The sensors data. protected virtual void OnDiagnosticsDataAvailable(StartDiagnosticsResponse data) { DiagnosticsDataAvailable?.Invoke(this, data); } private void OnTelemetryWireAvailable(StartTelemetryWireResponse response) { TelemetryWireAvailable?.Invoke(this, response); } private void OnNotificationAvailable(StartNotificationResponse response) { NotificationAvailable?.Invoke(this, response); } /// /// Called when events notification message has been received. /// /// The response. protected virtual void OnEventsNotification(StartEventsNotificationResponse response) { if (MachineEventsStateProvider != null) { var events = response.Events; foreach (var emulated in _emulatedEvents) { if (!events.Any(x => x.Type == emulated.Type)) { events.Add(emulated); } } MachineEventsStateProvider.ApplyEvents(events); } EventsNotification?.Invoke(this, response); } /// /// Invokes the event. /// /// The sensors data. protected virtual void OnDebugLogAvailable(StartDebugLogResponse data) { if (LogEmbeddedDebuggingToFile && EmbeddedLogManager != null) { EmbeddedLogManager.Log(new EmbeddedLogItem(data)); } DebugLogAvailable?.Invoke(this, data); } /// /// Called when the machine status has been updated. /// /// The response. protected virtual void OnMachineStatusChanged(StartMachineStatusUpdateResponse response) { if (response.Status == null) return; bool changed = (MachineStatus == null || response.Status.State != MachineStatus.State); foreach (var idsPack in response.Status.IDSPacksLevels) { if (idsPack.DispenserLevel > 0 && idsPack.DispenserLevel64 == 0) { idsPack.DispenserLevel64 = idsPack.DispenserLevel; } } MachineStatus = response.Status; MachineStatusChanged?.Invoke(this, MachineStatus); if (changed) { OnMachineStateChanged(MachineStatus.State); } if (MachineStatus.SpoolState == SpoolState.Absent) { IsSpoolReplaced = true; } } /// /// Called when ink filling status has been changed. /// /// The response. protected virtual void OnInkFillingStatusChanged(StartInkFillingStatusResponse response) { if (response.Status == null || response.Status.CartridgesStatuses == null || response.Status.CartridgesStatuses.Count == 0) return; int index = -1; bool raiseChange = false; foreach (var remoteCartridge in response.Status.CartridgesStatuses) { index++; if (remoteCartridge.Cartridge == null) { LogManager.Log($"Remote cartridge arrived with null cartridge at position [{index}] and will be ignored.", LogCategory.Error); continue; } var localCartridge = InkFillingStatus.CartridgesStatuses.SingleOrDefault(x => x.Cartridge.Index == remoteCartridge.Cartridge.Index && x.Cartridge.Slot == remoteCartridge.Cartridge.Slot); if (localCartridge != null) { if (localCartridge.State != remoteCartridge.State) { localCartridge.State = remoteCartridge.State; LogManager.Log($"{localCartridge.Cartridge.Slot} Cartridge '{localCartridge.Cartridge.Index}' state changed: '{localCartridge.State}' => '{remoteCartridge.State}'."); } if (remoteCartridge.Cartridge.Tag != null) { LogManager.Log($"{localCartridge.Cartridge.Slot} Cartridge '{localCartridge.Cartridge.Index}' Tag arrived:\n{remoteCartridge.Cartridge.Tag.ToJsonString()}"); } localCartridge.Message = remoteCartridge.Message; localCartridge.ProgressPercentage = remoteCartridge.ProgressPercentage; raiseChange = true; } else { LogManager.Log($"Could not locate local cartridge with slot '{remoteCartridge.Cartridge.Slot}' and index '{remoteCartridge.Cartridge.Index}'.", LogCategory.Error); } } if (raiseChange) { RaisePropertyChanged(nameof(InkFillingStatus)); InkFillingStatusChanged?.Invoke(this, new InkFillingStatusChangedEventArgs() { Status = InkFillingStatus }); } } /// /// Called when the machine state has been changed. /// /// The state. protected async virtual void OnMachineStateChanged(MachineState state) { LogManager.Log($"Machine State Changed: {state}."); if (IsPrinting) { LogManager.Log($"Machine state change will not affect the machine operator status as it is now in a '{Status}' status.", LogCategory.Warning); return; } switch (state) { case MachineState.PowerUp: UpdateStatus(MachineStatuses.PowerUp); break; //case MachineState.PreparingJob: // Status = MachineStatuses.GettingReady; // break; case MachineState.Ready: UpdateStatus(MachineStatuses.ReadyToDye); break; case MachineState.Sleep: UpdateStatus(MachineStatuses.Standby); break; case MachineState.PowerDown: UpdateStatus(MachineStatuses.PowerDown); break; case MachineState.ShuttingDown: UpdateStatus(MachineStatuses.ShuttingDown); if (!_isPowerDownRequestInProgress) { try { await PowerDown(); } catch { } } break; case MachineState.Error: //Status = MachineStatuses.Error; break; } } /// /// Called when the thread loading status has been changed. /// /// The response. protected virtual void OnThreadLoadingStatusChanged(StartThreadLoadingResponse response) { bool changed = (ThreadLoadingStatus == null || response.State != ThreadLoadingStatus.State || response.ErrorReason != ThreadLoadingStatus.ErrorReason); if (changed) { ThreadLoadingStatus = response; ThreadLoadingStatusChanged?.Invoke(this, response); LogManager.Log($"Thread Loading Status Changed: {ThreadLoadingStatus.State}."); switch (ThreadLoadingStatus.State) { case ThreadLoadingState.ReadyForLoading: LogManager.Log("Thread loading is ready for loading. Invoking confirmation event..."); ThreadLoadingConfirmationRequired?.Invoke(this, new ThreadLoadingConfirmationRequiredEventArgs((processTable) => { //Confirm Action try { var process = processTable.ToProcessParametersPMR(); LogManager.Log($"Thread loading confirmation received with process parameters:\n{process.ToJsonString()}"); LogManager.Log("Sending continue thread loading request..."); var r = SendRequest(new ContinueThreadLoadingRequest() { ProcessParameters = process, }, new TransportRequestConfig() { ShouldLog = true }).Result; } catch (Exception ex) { LogManager.Log(ex, "Error confirming thread loading sequence."); } }) { Status = ThreadLoadingStatus, }); break; case ThreadLoadingState.Completed: ThreadLoadingCompleted?.Invoke(this, ThreadLoadingStatus); break; case ThreadLoadingState.FinalizationError: case ThreadLoadingState.PreparationError: ThreadLoadingFailed?.Invoke(this, ThreadLoadingStatus); break; } } } /// /// Called when a new request has been received. /// /// The request. protected override void OnRequestReceived(RequestReceivedEventArgs e) { base.OnRequestReceived(e); if (e.Handled) return; var container = e.Container; if (container.Type == MessageType.CartridgeValidationRequest) { e.Handled = true; OnCartridgeValidationRequestReceived(container.Token, MessageFactory.ExtractMessageFromContainer(container)); } else if (container.Type == MessageType.UpdateStatusRequest) { e.Handled = true; OnUpdateStatusRequestReceived(container.Token, MessageFactory.ExtractMessageFromContainer(container)); } else if (container.Type == MessageType.WasteReplaceRequest) { e.Handled = true; OnWasteReplacementRequired(container.Token, MessageFactory.ExtractMessageFromContainer(container)); } } /// /// Called when the machine status has been changed /// /// The status. protected virtual void OnStatusChanged(MachineStatuses status) { StatusChanged?.Invoke(this, status); } /// /// Called when the cartridge validation request has been received. /// /// The request. protected virtual void OnCartridgeValidationRequestReceived(String token, CartridgeValidationRequest request) { if (request.Action == CartridgeAction.Inserted) { CartridgeValidationEventArgs e = new CartridgeValidationEventArgs(request, (index) => { //Approve SendResponse(new CartridgeValidationResponse() { IsValid = true, Index = index, }, token).Wait(); }, () => { //Decline SendResponse(new CartridgeValidationResponse() { }, token).Wait(); }); CartridgeValidationRequestReceived?.Invoke(this, e); } } /// /// Called when the update status request has been received. /// /// The token. /// The update status request. protected virtual void OnUpdateStatusRequestReceived(string token, UpdateStatusRequest request) { try { UpdateStatus((MachineStatuses)request.Status); } catch (Exception ex) { LogManager.Log(ex); } try { SendResponse(new UpdateStatusResponse(), token); } catch (Exception ex) { LogManager.Log(ex, "Error sending UpdateStatus response."); } } /// /// Called when the printing has been started. /// /// The handler. /// The job. protected virtual void OnPrintingStarted(CurrentJobContext context) { LogManager.Log("Raising printing started event..."); PrintingStarted?.Invoke(this, new PrintingEventArgs(context.JobHandler, context.JobForJobRun) { StartDate = context.StartDate, IsResumingJob = context.IsResumingJob }); } /// /// Called when the printing has been completed. /// /// The handler. /// The job. protected virtual void OnPrintingCompleted(CurrentJobContext context) { LogManager.Log("Raising printing completed event..."); PrintingCompleted?.Invoke(this, new PrintingEventArgs(context.JobHandler, context.JobForJobRun) { LiquidQuantities = context.GetLiquidQuantities(), StartDate = context.StartDate, UploadingStartTime = context.UploadingStartDate, HeatingStartTime = context.HeatingStartDate, ActualStartTime = context.ActualStartDate, IsResumingJob = context.IsResumingJob }); OnPrintingEnded(context); } /// /// Called when the printing has been failed. /// /// The handler. /// The job. /// The exception. protected virtual void OnPrintingFailed(CurrentJobContext context, Exception exception) { LogManager.Log("Raising printing failed event..."); PrintingFailed?.Invoke(this, new PrintingFailedEventArgs(context.JobHandler, context.JobForJobRun, exception) { LiquidQuantities = context.GetLiquidQuantities(), StartDate = context.StartDate, UploadingStartTime = context.UploadingStartDate, HeatingStartTime = context.HeatingStartDate, ActualStartTime = context.ActualStartDate, IsResumingJob = context.IsResumingJob }); OnPrintingEnded(context); } /// /// Called when the printing has been aborted. /// /// The handler. /// The job. protected virtual void OnPrintingAborted(CurrentJobContext context) { LogManager.Log("Raising printing aborted event..."); PrintingAborted?.Invoke(this, new PrintingEventArgs(context.JobHandler, context.JobForJobRun) { LiquidQuantities = context.GetLiquidQuantities(), StartDate = context.StartDate, UploadingStartTime = context.UploadingStartDate, HeatingStartTime = context.HeatingStartDate, ActualStartTime = context.ActualStartDate, IsResumingJob = context.IsResumingJob }); OnPrintingEnded(context); } /// /// Called when the printing has been ended. /// /// The handler. /// The job. protected virtual void OnPrintingEnded(CurrentJobContext context) { IsSpoolReplaced = false; LogManager.Log("Raising printing ended event..."); PrintingEnded?.Invoke(this, new PrintingEventArgs(context.JobHandler, context.JobForJobRun) { LiquidQuantities = context.GetLiquidQuantities(), StartDate = context.StartDate, UploadingStartTime = context.UploadingStartDate, HeatingStartTime = context.HeatingStartDate, ActualStartTime = context.ActualStartDate, IsResumingJob = context.IsResumingJob }); } protected virtual void OnHeadCleaningEnded(CurrentJobContext context, HeadCleaningHandler handler, JobRunStatus status) { HeadCleaningEnded?.Invoke(this, new HeadCleaningEndedEventArgs() { StartDate = context.StartDate, Length = handler.Status.Total, EndPosition = handler.Status.Progress, Status = status, LiquidQuantities = context.GetLiquidQuantities(), }); } protected virtual void OnWasteReplacementRequired(string token, WasteReplaceRequest wasteReplaceRequest) { if (_lastWasteReplaceRequestToken == null) { _lastWasteReplaceRequestToken = token; WasteReplacementRequired?.Invoke(this, new EventArgs()); } } #endregion #region Override Methods /// /// Called when the component state has changed. /// /// The state. protected override void OnStateChanged(TransportComponentState state) { base.OnStateChanged(state); if (state != TransportComponentState.Connected) { _diagnosticsSent = false; _debugSent = false; _eventsSent = false; _machineStatusSent = false; _telemetryWireSent = false; _notificationsSent = false; //MachineStatus = null; if (Status != MachineStatuses.Disconnected) { UpdateStatus(MachineStatuses.Disconnected); ResetEvents(); ResetInkFllingStatus(); } } } private void ResetEvents() { if (MachineEventsStateProvider != null) { LogManager.Log("Resetting active events..."); _emulatedEvents.Clear(); MachineEventsStateProvider.Reset(); } } /// /// Disconnects the machine operator and the underlying transporter. /// /// public async override Task Disconnect() { if (Status == MachineStatuses.Upgrading) return; UpdateStatus(MachineStatuses.Disconnected); if (MachineStatus != null) { MachineStatus.State = MachineState.Ready; } SessionLogger.EndSession(); if (State == TransportComponentState.Connected) { DisconnectRequest request = new DisconnectRequest(); try { var response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); UpdateStatus(MachineStatuses.Disconnected); } catch { } } ResetEvents(); ResetInkFllingStatus(); await base.Disconnect(); } /// /// Connects the transport component. /// /// public async override Task Connect() { var keep_alive = UseKeepAlive; UseKeepAlive = false; if (Status != MachineStatuses.Upgrading) { await base.Connect(); } if (State == TransportComponentState.Connected) { ConnectRequest request = new ConnectRequest() { Password = "1234", UnixTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), SpoolType = _currentSpoolType, MachineType = (PMR.Common.MachineType)MachineType, Supports64BitDispenserLevel = true }; try { var response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); SessionLogger.CreateSession(); _isPowerDownRequestInProgress = false; DeviceInformation = response.Message.DeviceInformation; if (Status != MachineStatuses.Upgrading) { UpdateStatus(MachineStatuses.ReadyToDye); } _diagnosticsSent = false; _eventsSent = false; _debugSent = false; _machineStatusSent = false; _telemetryWireSent = false; _notificationsSent = false; _bitResults = null; OnEnableDiagnosticsChanged(EnableDiagnostics); OnEnableEmbeddedDebuggingChanged(EnableEmbeddedDebugging); OnEnableEventsNotification(EnableEventsNotification); OnEnableMachineStatusUpdatesChanged(EnableMachineStatusUpdates); OnEnableAutomaticThreadLoadingChanged(EnableAutomaticThreadLoading); OnEnableInkFillingStatus(EnableInkFillingStatus); OnEnableTelemetryWireChanged(EnableTelemetryWire); OnEnableNotificationsChanged(EnableNotifications); if (EnablePowerUpSequence) { TrackPowerUpSequence(); } if (EnableJobResume) { ResumeJob(); } if (response.Message.IsAfterReset) { FirmwareStarted?.Invoke(this, new EventArgs()); } } catch (Exception ex) { SessionLogger.EndSession(); await base.Disconnect(); throw ex; } finally { UseKeepAlive = keep_alive; } } } #endregion #region Private Methods private void UpdateStatus(MachineStatuses status, [CallerMemberName] string caller = null, [CallerFilePath] string file = null, [CallerLineNumber] int lineNumber = 0) { if (Status != status) { Status = status; LogManager.Log($"Machine operator status changed: {status}", caller, file, lineNumber); } } private void ResetInkFllingStatus() { if (InkFillingStatus == null) { var status = new InkFillingStatus(); for (int i = 0; i < 8; i++) { status.CartridgesStatuses.Add(new CartridgeStatus() { Cartridge = new Cartridge() { Index = i, Slot = CartridgeSlot.Ink, }, State = CartridgeState.Absent }); } status.CartridgesStatuses.Add(new CartridgeStatus() { Cartridge = new Cartridge() { Index = 0, Slot = CartridgeSlot.WasteMiddle }, State = CartridgeState.Absent }); status.CartridgesStatuses.Add(new CartridgeStatus() { Cartridge = new Cartridge() { Index = 1, Slot = CartridgeSlot.WasteLower }, State = CartridgeState.Absent }); InkFillingStatus = status; } else { foreach (var cartridge in InkFillingStatus.CartridgesStatuses) { cartridge.ProgressPercentage = 0; cartridge.Message = String.Empty; cartridge.State = CartridgeState.Absent; } } InkFillingStatusChanged?.Invoke(this, new InkFillingStatusChangedEventArgs() { Status = InkFillingStatus }); } private void SaveCachedJobOperation(Job job, AdditionalJobConfiguration config) { try { LogManager.Log("Caching current job operation..."); CachedJobOperation cache = new CachedJobOperation(); cache.JobDTO = JobDTO.FromObservable(job); cache.MachineStatus = MachineStatus; cache.ProcessParametersDTO = ProcessParametersTableDTO.FromObservable(CurrentProcessParameters); cache.MachineConfigurationDTO = ConfigurationDTO.FromObservable(job.Machine.Configuration); cache.Config = config; var json = JsonConvert.SerializeObject(cache); Directory.CreateDirectory(Path.GetDirectoryName(CachedJobOperationFile)); File.WriteAllText(CachedJobOperationFile, json); } catch (Exception ex) { LogManager.Log(ex, "Error caching job operation for job resume."); } } private CachedJobOperation LoadCachedJobOperation() { try { LogManager.Log("Loading last cached job operation..."); String json = File.ReadAllText(CachedJobOperationFile); CachedJobOperation cache = JsonConvert.DeserializeObject(json); return cache; } catch (Exception ex) { LogManager.Log(ex, "Error loading cache of last job operation for job resume."); return null; } } private void TrackPowerUpSequence() { LogManager.Log("Starting power up sequence tracking..."); bool started = false; bool completed = false; PowerUpState lastState = PowerUpState.None; SendContinuousRequest(new StartPowerUpRequest(), new TransportContinuousRequestConfig() { ShouldLog = true, Timeout = TimeSpan.FromSeconds(5) }).Subscribe((response) => { if (!started) { started = true; PowerUpStarted?.Invoke(this, response); } PowerUpProgress?.Invoke(this, response); var state = response.Message.State; if (state != lastState) { LogManager.Log($"Power up sequence state changed to '{state}'..."); switch (state) { case PowerUpState.Error: completed = true; LogManager.Log($"Power up sequence failed with state '{state}'. ({response.Message.Message})"); PowerUpFailed?.Invoke(this, response); PowerUpEnded?.Invoke(this, new EventArgs()); break; case PowerUpState.Cancelled: completed = true; LogManager.Log($"Power up sequence canceled with state '{state}'. ({response.Message.Message})"); PowerUpEnded?.Invoke(this, new EventArgs()); break; case PowerUpState.MachineReadyToDye: completed = true; LogManager.Log($"Power up sequence completed successfully with state '{state}'. ({response.Message.Message})"); PowerUpCompleted?.Invoke(this, response); PowerUpEnded?.Invoke(this, new EventArgs()); break; } lastState = state; } }, (ex) => { if (!completed) { completed = true; LogManager.Log(ex, "Power up sequence tracking failed."); PowerUpEnded?.Invoke(this, new EventArgs()); } }, () => { if (!completed) { completed = true; PowerUpEnded?.Invoke(this, new EventArgs()); } }); } //private async void ResumeJob() //{ // LogManager.Log("Checking if a job is in progress..."); // try // { // var res = await SendRequest(new CurrentJobRequest(), new TransportRequestConfig() { ShouldLog = true }); // if (res.Message.IsJobInProgress) // { // LogManager.Log("Job is in progress. Trying to resume job..."); // CachedJobOperation cache = LoadCachedJobOperation(); // if (cache == null) // { // LogManager.Log("Cannot resume current job with no cached operation.", LogCategory.Error); // return; // } // Job job = null; // Configuration configuration = null; // ProcessParametersTable processParameters = null; // try // { // processParameters = cache.ProcessParametersDTO.ToObservable(); // job = cache.JobDTO.ToObservable(); // configuration = cache.MachineConfigurationDTO.ToObservable(); // CurrentProcessParameters = processParameters; // } // catch (Exception ex) // { // LogManager.Log(ex, "Error deserializing cache job operation. Aborting resume."); // return; // } // JobTicket jobTicket = res.Message.JobTicket; // ResumingJobEventArgs args = new ResumingJobEventArgs(() => // { // RunningJob = null; // RunningJobStatus = null; // var request = new ResumeCurrentJobRequest(); // JobHandler handler = null; // handler = new JobHandler(async () => // { // try // { // if (handler.CanCancel) // { // handler.CanCancel = false; // handler.IsCanceled = true; // LogManager.Log("Aborting current job..."); // var result = await SendRequest(new AbortJobRequest(), new TransportRequestConfig() { ShouldLog = true }); // SaveLastJobLiquidQuantities(job, configuration, processParameters, handler); // OnPrintingAborted(handler, job); // handler.RaiseCanceled(); // if (Status != MachineStatuses.Disconnected) // { // UpdateStatus(MachineStatuses.ReadyToDye); // } // } // } // catch (Exception ex) // { // handler.CanCancel = true; // LogManager.Log(ex, "Failed to cancel job."); // } // }, job, jobTicket, processParameters, JobHandlingMode); // handler.StatusChanged += (x, s) => // { // RunningJobStatus = s; // }; // if (MachineStatus != null) // { // _machineStatusBeforeJobStart = MachineStatus.Clone(); // } // else // { // _machineStatusBeforeJobStart = cache.MachineStatus.Clone(); // } // _jobStartDate = DateTime.UtcNow; // _jobUploadingStartDate = _jobStartDate; // _jobHeatingStartDate = _jobStartDate; // _jobActualStartDate = null; // bool responseLogged = false; // bool completed = false; // Thread.Sleep(500); //Just wait maybe Shlomo is getting this message to fast after restart ? // SendContinuousRequest(request, new TransportContinuousRequestConfig() { ContinuousTimeout = TimeSpan.FromSeconds(10), ShouldLog = true }).Subscribe((response) => // { // if (!completed) // { // handler.RaiseStatusReceived(response.Message.Status); // _last_job_status = handler.Status; // if (response.Message.Status.Progress > 0) // { // if (_jobActualStartDate == null) // { // _jobActualStartDate = DateTime.UtcNow; // } // } // if (!responseLogged) // { // UpdateStatus(MachineStatuses.GettingReady); // responseLogged = true; // RunningJob = job; // OnPrintingStarted(handler, job, true); // } // if (JobHandlingMode == JobHandlerModes.SettingUp) // { // if (response.Message.Status.Progress > CurrentProcessParameters.DryerBufferLengthMeters) // { // if (!completed) // { // UpdateStatus(MachineStatuses.Printing); // } // } // } // else // { // if (response.Message.Status.Progress > 0) // { // if (!completed) // { // UpdateStatus(MachineStatuses.Printing); // } // } // } // } // }, (ex) => // { // if (!completed) // { // completed = true; // if (Status != MachineStatuses.Disconnected) // { // UpdateStatus(MachineStatuses.ReadyToDye); // } // if (!handler.IsCanceled) // { // SaveLastJobLiquidQuantities(job, configuration, processParameters, handler); // Exception finalException = ex; // if (ex is ContinuousResponseAbortedException continuousException) // { // finalException = new ContinuousResponseAbortedException($"Job aborted by the embedded device ({continuousException.Container.ErrorMessage})."); // } // OnPrintingFailed(handler, job, finalException); // handler.RaiseFailed(finalException); // } // } // }, () => // { // if (!completed) // { // completed = true; // UpdateStatus(MachineStatuses.ReadyToDye); // SaveLastJobLiquidQuantities(job, configuration, processParameters, handler); // OnPrintingCompleted(handler, job); // handler.RaiseCompleted(); // } // }); // return handler; // }); // args.JobGuid = jobTicket.Guid; // ResumingJob?.Invoke(this, args); // } // } // catch (Exception ex) // { // LogManager.Log(ex); // } //} /// /// Creates a PMR job segment. /// /// The segment. /// private JobSegment CreatePMRJobSegment(Segment segment, Job job, ProcessParametersTable processParameters) { LogManager.Log($"Converting segment {segment.SegmentIndex} to PMR segment..."); JobSegment jobSegment = new JobSegment(); jobSegment.Length = segment.LengthWithFactor; jobSegment.Name = segment.Name; var stops = segment.BrushStops.ToList(); if (GradientGenerationConfiguration != null && GradientGenerationConfiguration.IsEnabled && segment.BrushStops.Count > 1) { LogManager.Log($"Generate segment {segment.SegmentIndex} gradient..."); try { stops = GradientGenerationConfiguration.Generate(segment, job, processParameters, (e) => { PreparingJobProgress?.Invoke(this, e); }); } catch (Exception ex) { throw new InvalidOperationException($"Error occurred while trying to generate a gradient.\n{ex.Message}"); } LogManager.Log($"Gradient generated."); PreparingJobProgress?.Invoke(this, new PreparingJobProgressEventArgs() { Job = job, Total = job.Segments.Sum(x => x.Length), Progress = job.Segments.Sum(x => x.Length), }); } foreach (var stop in stops) { JobBrushStop jobStop = new JobBrushStop(); jobStop.Index = stop.StopIndex; jobStop.OffsetPercent = stop.OffsetPercent; jobStop.OffsetMeters = stop.OffsetMeters; if (stop.LiquidVolumes == null) { stop.SetLiquidVolumes(job.Machine.Configuration, job.Rml, processParameters); } foreach (var liquidVolume in stop.LiquidVolumes) { JobDispenser dispenser = new JobDispenser(); dispenser.Index = liquidVolume.IdsPack.PackIndex; dispenser.Volume = liquidVolume.Volume; dispenser.DispenserLiquidType = (DispenserLiquidType)liquidVolume.IdsPack.LiquidType.Code; dispenser.DispenserStepDivision = (DispenserStepDivision)liquidVolume.DispenserStepDivision; if (liquidVolume.DispenserStepDivision != BL.Dispensing.DispenserStepDivisions.Auto) { dispenser.NanoliterPerPulse = liquidVolume.NanoliterPerStep; } else { dispenser.NanoliterPerPulse = liquidVolume.IdsPack.Dispenser.NlPerPulse; } dispenser.LiquidMaxNanoliterPerCentimeter = liquidVolume.LiquidMaxNanoliterPerCentimeter; dispenser.NanoliterPerCentimeter = liquidVolume.NanoliterPerCentimeter; dispenser.NanolitterPerSecond = liquidVolume.NanoliterPerSecond; dispenser.PulsePerSecond = liquidVolume.PulsePerSecond; jobStop.Dispensers.Add(dispenser); } jobSegment.BrushStops.Add(jobStop); } return jobSegment; } //private void ContinueSingleSpoolJob(Segment segment, Job job, ProcessParametersTable processParameters, JobHandler handler) //{ // JobRequest request = new JobRequest(); // JobTicket ticket = new JobTicket(); // ticket.Guid = handler.Job.Guid; // ticket.EnableInterSegment = job.EnableInterSegment; // ticket.InterSegmentLength = job.InterSegmentLength; // ticket.Length = segment.Length; // ticket.WindingMethod = (JobWindingMethod)job.WindingMethod.Code; // ticket.Spool = new JobSpool(); // job.SpoolType.MapPrimitivesTo(ticket.Spool); // ticket.Spool.JobSpoolType = (JobSpoolType)job.SpoolType.Code; // ProcessParameters process = new ProcessParameters(); // processParameters.MapPrimitivesTo(process); // ticket.ProcessParameters = process; // ticket.Segments.Add(CreatePMRJobSegment(segment, job, processParameters)); // request.JobTicket = ticket; // bool responseLogged = false; // var previous_segments_length = job.Segments.Where(x => x.SegmentIndex < segment.SegmentIndex).Sum(x => x.Length); // SendContinuousRequest(request, new TransportContinuousRequestConfig() { ContinuousTimeout = ContinuousRequestTimeout, ShouldLog = true }).Subscribe((response) => // { // response.Message.Status.Progress += previous_segments_length; // handler.RaiseStatusReceived(response.Message.Status); // if (!responseLogged && segment == job.OrderedSegments.First()) // { // responseLogged = true; // UpdateStatus(MachineStatuses.Printing); // RunningJob = handler.Job; // OnPrintingStarted(handler, handler.Job); // } // }, (ex) => // { // if (!(ex is ContinuousResponseAbortedException)) // { // UpdateStatus(MachineStatuses.ReadyToDye); // if (!handler.IsCanceled) // { // OnPrintingFailed(handler, handler.Job, ex); // handler.RaiseFailed(ex); // } // } // else // { // UpdateStatus(MachineStatuses.ReadyToDye); // } // }, () => // { // if (segment == job.OrderedSegments.Last()) // { // UpdateStatus(MachineStatuses.ReadyToDye); // OnPrintingCompleted(handler, handler.Job); // handler.RaiseCompleted(); // } // else // { // handler.RaiseSpoolChangeRequired(() => // { // ContinueSingleSpoolJob(segment.GetNextSegment(), job, processParameters, handler); // }, () => // { // OnPrintingAborted(handler, handler.Job); // UpdateStatus(MachineStatuses.ReadyToDye); // handler.RaiseCanceled(); // }); // } // }); //} private List ValidateJobLiquidQuantity(Job job, ProcessParametersTable processParameters, Configuration configuration) { LogManager.Log("Validating job liquid quantities using integral..."); Dictionary liquidQuantities = new Dictionary(); List requiredLiquids = new List(); foreach (var pack in configuration.NoneEmptyIdsPacks.OrderBy(x => x.PackIndex)) { liquidQuantities.Add(pack.PackIndex, 0); } int resolution = GradientGenerationConfiguration.ResolutionCM; for (int i = 0; i < Math.Max(job.NumberOfUnits, 1); i++) { for (int segmentIndex = 0; segmentIndex < job.Segments.Count; segmentIndex++) { var segment = job.Segments[segmentIndex]; var segment_length_cm = segment.Length * 100d; List orderedBrushCollection = segment.BrushStops.OrderBy(x => x.OffsetMeters).ToList(); int solid_gradient_oeff = orderedBrushCollection.Count == 1 ? 1 : 2; double prev_offset_cm = 0; for (int brushIndex = 0; brushIndex < orderedBrushCollection.Count; brushIndex++) { var brush = orderedBrushCollection[brushIndex]; double brush_length_centimeters = 0d; double brush_offset_cm = 0; if ((brushIndex + 1) < orderedBrushCollection.Count) { brush_offset_cm = (brush.OffsetMeters * 100d); double next_brush_offset_cm = (orderedBrushCollection[brushIndex + 1].OffsetMeters * 100d); brush_length_centimeters = ((next_brush_offset_cm - brush_offset_cm) + (brush_offset_cm - prev_offset_cm)); if (brushIndex == 0) { // add a resolution step for first brush brush_length_centimeters += resolution; } } else//last brush or solid brush { brush_length_centimeters = (segment_length_cm - prev_offset_cm); if (orderedBrushCollection.Count > 1) { // add a resolution for last brush , not solid brush brush_length_centimeters -= resolution; } } prev_offset_cm = brush_offset_cm; foreach (var liquidVolumes in brush.LiquidVolumes) { liquidQuantities[liquidVolumes.IdsPack.PackIndex] += liquidVolumes.NanoliterPerCentimeter * (brush_length_centimeters / solid_gradient_oeff); } } } } if (MachineStatus != null) { var exception = new InsufficientLiquidQuantityException($"Insufficient liquids level."); bool shouldThrow = false; foreach (var liquidQuantity in liquidQuantities) { int index = liquidQuantity.Key; var packLevel = MachineStatus.IDSPacksLevels.SingleOrDefault(x => x.Index == index); var idsPack = configuration.NoneEmptyIdsPacks.SingleOrDefault(x => x.PackIndex == index); if (packLevel != null) { var idsLevel = new InsufficientLiquidQuantityException.IDSPackLevel() { IdsPack = idsPack, Current = packLevel.DispenserLevel64, Required = (int)liquidQuantities[index], Maximum = MAX_DISPENSER_NANOLITER, }; requiredLiquids.Add(new RequiredLiquid() { IdsPack = idsPack, Quantity = (int)liquidQuantities[index] }); LogManager.Log($"Required {idsLevel.IdsPack.LiquidType.Type}: {idsLevel.Required}, Current: {idsLevel.Current}"); if (idsLevel.Required > idsLevel.Current) { shouldThrow = true; string display_value = (((double)(idsLevel.Required - idsLevel.Current) / 1000000)).ToString("N2", CultureInfo.InvariantCulture); idsLevel.Message = $"Missing {display_value} CC to complete the job."; if (idsLevel.Required > idsLevel.Maximum) { display_value = (((double)(idsLevel.Required - idsLevel.Maximum)) / 1000000).ToString("N2", CultureInfo.InvariantCulture); idsLevel.Message = $"Required ink exceeds the maximum capacity of the dispenser by {display_value} CC. Please reduce the segment length."; } } exception.IdsPackLevels.Add(idsLevel); } else { LogManager.Log($"Could not validate required liquid quantity for job. Missing IDS Pack level at index {index}.", LogCategory.Warning); } } if (shouldThrow) { LogManager.Log("Liquid quantity validation failed due to insufficient quantity. Throwing exception..."); exception.IdsPackLevels = exception.IdsPackLevels.OrderBy(x => x.IdsPack.PackIndex).ToList(); throw LogManager.Log(exception, JsonConvert.SerializeObject(exception.IdsPackLevels.Select(x => new { Liquid = x.IdsPack.LiquidType.Name, x.Required, x.Current }).ToList())); } } else { LogManager.Log("Could not validate required liquid quantity for job. No machine status received", LogCategory.Warning); } return requiredLiquids; } private List ValidateJobLiquidQuantity(JobTicket ticket, ProcessParametersTable processParameters, Configuration configuration) { LogManager.Log("Validating job liquid quantity using job ticket..."); Dictionary liquidQuantities = new Dictionary(); List requiredLiquids = new List(); foreach (var pack in configuration.NoneEmptyIdsPacks.OrderBy(x => x.PackIndex)) { liquidQuantities.Add(pack.PackIndex, 0); } for (int segmentIndex = 0; segmentIndex < ticket.Segments.Count; segmentIndex++) { var segment = ticket.Segments[segmentIndex]; var segment_length_cm = segment.Length * 100d; var stop_count = segment.BrushStops.Count - (segment.BrushStops.Count == 1 ? 0 : 1); var stop_length_centimeters = segment_length_cm / stop_count; for (int stopIndex = 0; stopIndex < stop_count; stopIndex++) { var stop = segment.BrushStops[stopIndex]; foreach (var dispenser in stop.Dispensers) { liquidQuantities[dispenser.Index] += dispenser.NanoliterPerCentimeter * stop_length_centimeters; } } } foreach (var key in liquidQuantities.Select(x => x.Key).ToList()) { liquidQuantities[key] *= Math.Max(ticket.NumberOfUnits, 1); } if (MachineStatus != null) { var exception = new InsufficientLiquidQuantityException($"Insufficient liquids level."); bool shouldThrow = false; foreach (var liquidQuantity in liquidQuantities) { int index = liquidQuantity.Key; var packLevel = MachineStatus.IDSPacksLevels.SingleOrDefault(x => x.Index == index); var idsPack = configuration.NoneEmptyIdsPacks.SingleOrDefault(x => x.PackIndex == index); if (packLevel != null) { var idsLevel = new InsufficientLiquidQuantityException.IDSPackLevel() { IdsPack = idsPack, Current = packLevel.DispenserLevel64, Required = (int)liquidQuantities[index], Maximum = MAX_DISPENSER_NANOLITER, }; requiredLiquids.Add(new RequiredLiquid() { IdsPack = idsPack, Quantity = (int)liquidQuantities[index] }); LogManager.Log($"Required {idsLevel.IdsPack.LiquidType.Type}: {idsLevel.Required}, Current: {idsLevel.Current}"); if (idsLevel.Required > idsLevel.Current) { shouldThrow = true; string display_value = (((double)(idsLevel.Required - idsLevel.Current) / 1000000)).ToString("N2", CultureInfo.InvariantCulture); idsLevel.Message = $"Missing {display_value} CC to complete the job."; if (idsLevel.Required > idsLevel.Maximum) { display_value = (((double)(idsLevel.Required - idsLevel.Maximum)) / 1000000).ToString("N2", CultureInfo.InvariantCulture); idsLevel.Message = $"Required ink exceeds the maximum capacity of the dispenser by {display_value} CC. Please reduce the segment length."; } } exception.IdsPackLevels.Add(idsLevel); } else { LogManager.Log($"Could not validate required liquid quantity for job. Missing IDS Pack level at index {index}.", LogCategory.Warning); } } if (shouldThrow) { LogManager.Log("Liquid quantity validation failed due to insufficient quantity. Throwing exception..."); exception.IdsPackLevels = exception.IdsPackLevels.OrderBy(x => x.IdsPack.PackIndex).ToList(); throw LogManager.Log(exception, JsonConvert.SerializeObject(exception.IdsPackLevels.Select(x => new { Liquid = x.IdsPack.LiquidType.Name, x.Required, x.Current }).ToList())); } } else { LogManager.Log("Could not validate required liquid quantity for job. No machine status received", LogCategory.Warning); } return requiredLiquids; } /// /// Assign the liquid quantities spent by the last job using the job and the handler last status. /// /// The job. /// The configuration. /// The handler. private List GetJobRunLiquidQuantities(CurrentJobContext context) { LogManager.Log($"Calculating job run liquid quantities using '{JobLiquidQuantityCalculationMode}' method..."); var liquidQuantities = new List(); try { //if (JobLiquidQuantityCalculationMode == JobLiquidQuantityCalculationMode.MachineStatus) //{ foreach (var pack in _machineConfiguration.NoneEmptyIdsPacks.ToList()) { try { var packLevelAfter = MachineStatus.IDSPacksLevels.SingleOrDefault(x => x.Index == pack.PackIndex); var packLevelBefore = context.MachineStatusOnCreation.IDSPacksLevels.SingleOrDefault(x => x.Index == pack.PackIndex); if (packLevelAfter != null && packLevelBefore != null) { if (packLevelAfter.DispenserLevel64 > packLevelBefore.DispenserLevel64) { LogManager.Log($"Invalid '{pack.LiquidType.Name}' dispenser level calculated: {packLevelBefore.DispenserLevel64} - {packLevelAfter.DispenserLevel64} = {packLevelBefore.DispenserLevel64 - packLevelAfter.DispenserLevel64}. Ignoring..."); continue; } liquidQuantities.Add(new BL.ValueObjects.JobRunLiquidQuantity() { LiquidType = pack.LiquidType.Type, Quantity = packLevelBefore.DispenserLevel64 - packLevelAfter.DispenserLevel64, }); } } catch (Exception ex) { LogManager.Log(ex, $"Error calculating liquid quantity for {pack.LiquidType.Name}"); } } //} //else //{ // liquidQuantities = CreateJobRunLiquidQuantitiesUsingIntegral(job, configuration, processParameters, jobProgress, jobTotalProgress); //} LogManager.Log($"Job run liquid quantities calculation completed:\n{liquidQuantities.ToJsonString()}"); } catch (Exception ex) { LogManager.Log(ex, LogCategory.Critical, "Error calculating and saving last job run liquid quantities."); } return liquidQuantities; } #endregion #region Public Static Methods /// /// Creates the job run liquid quantities. /// /// The job. /// The configuration. /// The process parameters. /// The position. /// The length. /// The gradient resolution. /// public static List CreateJobRunLiquidQuantitiesUsingIntegral(Job job, Configuration configuration, ProcessParametersTable processParameters, double position, double length) { var units = Math.Max(job.NumberOfUnits, 1); var effectiveSegments = new List(); for (int i = 0; i < units; i++) { if (i > 0 && job.EnableInterSegment) { effectiveSegments.Add(Job.CreateInterSegment(job.InterSegmentLength)); } foreach (var segment in job.EffectiveSegments) { effectiveSegments.Add(segment.Clone(job)); } } effectiveSegments.Add(Job.CreateInterSegment(processParameters.DryerBufferLengthMeters)); double total = length; double position_cm = position * 100d; double total_length = 0; Dictionary liquidQuantities = new Dictionary(); foreach (var pack in configuration.NoneEmptyIdsPacks.OrderBy(x => x.PackIndex)) { liquidQuantities.Add(pack.PackIndex, 0); } bool stop_calc = false; for (int segmentIndex = 0; segmentIndex < effectiveSegments.Count && !stop_calc; segmentIndex++) { var segment = effectiveSegments[segmentIndex]; var segment_length_cm = segment.Length * 100d; List orderedBrushCollection = segment.BrushStops.OrderBy(x => x.OffsetMeters).ToList(); int solid_gradient_oeff = orderedBrushCollection.Count == 1 ? 1 : 2; double prev_offset_cm = 0; double delta_brushLenghtToStopPosition = 0d; double position_interval_centimeters = 0d;//interval for calculation where the stop occurred for (int brushIndex = 0; brushIndex < orderedBrushCollection.Count && !stop_calc; brushIndex++) { var brush = orderedBrushCollection[brushIndex]; double brush_length_centimeters = 0d; double brush_offset_cm = 0; if ((brushIndex + 1) < orderedBrushCollection.Count) { brush_offset_cm = (brush.OffsetMeters * 100d); double next_brush_offset_cm = (orderedBrushCollection[brushIndex + 1].OffsetMeters * 100d); brush_length_centimeters = (next_brush_offset_cm - prev_offset_cm); double brush_length_centimeters_before_calc = brush_length_centimeters; if (delta_brushLenghtToStopPosition > 0)//calculate second brush { brush_length_centimeters = ((position_interval_centimeters - delta_brushLenghtToStopPosition) * (position_interval_centimeters - delta_brushLenghtToStopPosition)) / position_interval_centimeters; stop_calc = true; } else if (total_length + prev_offset_cm + brush_length_centimeters > position_cm)//calculate first brush { position_interval_centimeters = brush_length_centimeters; delta_brushLenghtToStopPosition = (total_length + prev_offset_cm + brush_length_centimeters) - position_cm; brush_length_centimeters = brush_length_centimeters - (delta_brushLenghtToStopPosition * delta_brushLenghtToStopPosition / brush_length_centimeters); } } else//last brush or solid brush { brush_length_centimeters = (segment_length_cm - prev_offset_cm); if (delta_brushLenghtToStopPosition > 0)//second brush { brush_length_centimeters = ((position_interval_centimeters - delta_brushLenghtToStopPosition) * (position_interval_centimeters - delta_brushLenghtToStopPosition)) / position_interval_centimeters; stop_calc = true; } else if (orderedBrushCollection.Count == 1 && (total_length + segment_length_cm) > position_cm)// solid brush { brush_length_centimeters = position_cm - total_length; stop_calc = true; } } prev_offset_cm = brush_offset_cm; if (brush.LiquidVolumes != null) { foreach (var liquidVolumes in brush.LiquidVolumes) { liquidQuantities[liquidVolumes.IdsPack.PackIndex] += liquidVolumes.NanoliterPerCentimeter * (brush_length_centimeters / solid_gradient_oeff); } } } total_length += segment_length_cm; } List quantities = new List(); foreach (var liquidQuantity in liquidQuantities) { int index = liquidQuantity.Key; var idsPack = configuration.NoneEmptyIdsPacks.SingleOrDefault(x => x.PackIndex == index); if (idsPack != null) { quantities.Add(new BL.ValueObjects.JobRunLiquidQuantity() { LiquidType = idsPack.LiquidType.Type, Quantity = (int)liquidQuantities[index], }); } } return quantities; } #endregion #region Public Methods /// /// Uploads the specified process parameters to the embedded device. /// /// The process parameters. /// public async Task UploadProcessParameters(ProcessParametersTable processParameters) { UploadProcessParametersRequest request = new UploadProcessParametersRequest(); request.ProcessParameters = new ProcessParameters(); processParameters.MapPrimitivesTo(request.ProcessParameters); UploadProcessParametersResponse response = null; try { CurrentProcessParameters = processParameters; response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } catch (Exception ex) { throw ex; } return response; } /// /// Uploads the specified hardware configuration to the embedded device. /// /// Machine version. /// Machine configuration. /// public async Task UploadHardwareConfiguration(HardwareVersion hardwareVersion, Configuration configuration) { _machineConfiguration = configuration; LogManager.Log("Uploading hardware configuration..."); try { hardwareVersion = configuration.GetHardwareConfiguration().Merge(hardwareVersion); } catch (Exception ex) { LogManager.Log(ex, "Error merging hardware configuration to hardware version."); } HardwareConfiguration hardwareConfiguration = new HardwareConfiguration(); foreach (var dancer in hardwareVersion.HardwareDancers.Where(x => x.Active)) { PMR.Hardware.HardwareDancer item = new PMR.Hardware.HardwareDancer(); dancer.MapPrimitivesTo(item); item.HardwareDancerType = (PMR.Hardware.HardwareDancerType)dancer.HardwareDancerType.Code; hardwareConfiguration.Dancers.Add(item); } foreach (var motor in hardwareVersion.HardwareMotors.Where(x => x.Active)) { PMR.Hardware.HardwareMotor item = new PMR.Hardware.HardwareMotor(); motor.MapPrimitivesTo(item); item.HardwareMotorType = (PMR.Hardware.HardwareMotorType)motor.HardwareMotorType.Code; hardwareConfiguration.Motors.Add(item); } foreach (var pid in hardwareVersion.HardwarePidControls.Where(x => x.Active)) { PMR.Hardware.HardwarePidControl item = new PMR.Hardware.HardwarePidControl(); pid.MapPrimitivesTo(item); item.HardwarePidControlType = (PMR.Hardware.HardwarePidControlType)pid.HardwarePidControlType.Code; hardwareConfiguration.PidControls.Add(item); } foreach (var winder in hardwareVersion.HardwareWinders.Where(x => x.Active)) { PMR.Hardware.HardwareWinder item = new PMR.Hardware.HardwareWinder(); winder.MapPrimitivesTo(item); item.HardwareWinderType = (PMR.Hardware.HardwareWinderType)winder.HardwareWinderType.Code; hardwareConfiguration.Winders.Add(item); } foreach (var sensor in hardwareVersion.HardwareSpeedSensors.Where(x => x.Active)) { PMR.Hardware.HardwareSpeedSensor item = new PMR.Hardware.HardwareSpeedSensor(); sensor.MapPrimitivesTo(item); item.HardwareSpeedSensorType = (PMR.Hardware.HardwareSpeedSensorType)sensor.HardwareSpeedSensorType.Code; hardwareConfiguration.SpeedSensors.Add(item); } foreach (var blower in hardwareVersion.HardwareBlowers.Where(x => x.Active)) { PMR.Hardware.HardwareBlower item = new PMR.Hardware.HardwareBlower(); blower.MapPrimitivesTo(item); item.HardwareBlowerType = (PMR.Hardware.HardwareBlowerType)blower.HardwareBlowerType.Code; hardwareConfiguration.Blowers.Add(item); } foreach (var breakSensor in hardwareVersion.HardwareBreakSensors.Where(x => x.Active)) { PMR.Hardware.HardwareBreakSensor item = new PMR.Hardware.HardwareBreakSensor(); breakSensor.MapPrimitivesTo(item); item.HardwareBreakSensorType = (PMR.Hardware.HardwareBreakSensorType)breakSensor.HardwareBreakSensorType.Code; hardwareConfiguration.BreakSensors.Add(item); } foreach (var idsPack in configuration.NoneEmptyIdsPacks.OrderBy(x => x.PackIndex)) { PMR.Hardware.HardwareDispenser item = new PMR.Hardware.HardwareDispenser(); item.Capacity = idsPack.Dispenser.DispenserType.Capacity; item.HardwareDispenserType = (PMR.Hardware.HardwareDispenserType)idsPack.Dispenser.DispenserType.Code; item.Index = idsPack.PackIndex; item.NlPerPulse = idsPack.Dispenser.NlPerPulse; hardwareConfiguration.Dispensers.Add(item); } UploadHardwareConfigurationRequest request = new UploadHardwareConfigurationRequest(); request.HardwareConfiguration = hardwareConfiguration; UploadHardwareConfigurationResponse response = null; try { LogManager.Log("Checking if firmware version < 1.6..."); if (DeviceInformation != null && DeviceInformation.Version != null) { Version firmwareVersion = Version.Parse(DeviceInformation.Version); if (firmwareVersion < new Version("1.6.0.0") && firmwareVersion > new Version("1.0.0.0")) { LogManager.Log("Firmware version is lower than '1.6'. Skipping hardware configuration upload..."); return new UploadHardwareConfigurationResponse(); } } else { LogManager.Log("No device information found. Continuing...", LogCategory.Warning); } } catch (Exception ex) { LogManager.Log(ex, "Error occurred while checking for firmware version. Continuing..."); } try { LogManager.Log($"Sending '{nameof(UploadHardwareConfigurationRequest)}'..."); LogManager.Log($"{nameof(UploadHardwareConfigurationRequest)} request:\n{request.HardwareConfiguration.ToJsonString()}", LogCategory.Debug); CurrentHardwareConfiguration = hardwareConfiguration; response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = false }); } catch (Exception ex) { throw ex; } return response; } /// /// Starts jogging the specified motor. /// /// The request. /// public async Task StartMotorJogging(MotorJoggingRequest request) { MotorJoggingResponse response = null; try { response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } catch (Exception ex) { throw ex; } return response; } /// /// Stops jogging the specified motor. /// /// The request. /// public async Task StopMotorJogging(MotorAbortJoggingRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Starts homing the specified motor. /// /// The request. /// public IObservable StartMotorHoming(MotorHomingRequest request) { return SendContinuousRequest(request, new TransportContinuousRequestConfig() { ContinuousTimeout = ContinuousRequestTimeout, ShouldLog = true }).Select(x => x.Message); } /// /// Stops homing the specified motor. /// /// The request. /// public async Task StopMotorHoming(MotorAbortHomingRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Starts jogging the specified dispenser. /// /// The request. /// public async Task StartDispenserJogging(DispenserJoggingRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Stops jogging the specified dispenser. /// /// The request. /// public async Task StopDispenserJogging(DispenserAbortJoggingRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Starts homing the specified dispenser. /// /// The request. /// public IObservable StartDispenserHoming(DispenserHomingRequest request) { return SendContinuousRequest(request, new TransportContinuousRequestConfig() { ContinuousTimeout = ContinuousRequestTimeout, ShouldLog = true }).Select(x => x.Message); } /// /// Stops homing the specified dispenser. /// /// The request. /// public async Task StopDispenserHoming(DispenserAbortHomingRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Turn on/off the specified digital output pin. /// /// The request. /// public async Task SetDigitalOut(SetDigitalOutRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Starts jogging the thread motion system. /// /// The request. /// public async Task StartThreadJogging(ThreadJoggingRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Stops jogging the thread motion system. /// /// The request. /// public async Task StopThreadJogging(ThreadAbortJoggingRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Sets the specified component value. /// /// The request. /// public async Task SetComponentValue(SetComponentValueRequest request) { return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Sets the state of the specified heater type. /// /// The heater. /// Set point temperature. /// public async Task SetHeaterState(HeaterType heater, double setPoint) { SetHeaterStateResponse response = null; SetHeaterStateRequest request = new SetHeaterStateRequest() { HeaterType = heater, SetPoint = setPoint, IsActive = true, }; try { response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } catch (Exception ex) { throw ex; } return response; } /// /// Sets the state of the specified blower. /// /// The blower. /// Blower on/off. /// The voltage in millivolts. /// public async Task SetBlowerState(PMR.Hardware.HardwareBlowerType blower, bool isActive, double voltage) { SetBlowerStateResponse response = null; SetBlowerStateRequest request = new SetBlowerStateRequest() { BlowerType = blower, Voltage = voltage, IsActive = isActive, }; try { response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } catch (Exception ex) { throw ex; } return response; } /// /// Sets the state of the specified valve type. /// /// The valve. /// Valve state. /// public async Task SetValveState(ValveType valve, ValveStateCode state) { SetValveStateResponse response = null; SetValveStateRequest request = new SetValveStateRequest() { ValveType = valve, State = state, }; try { response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } catch (Exception ex) { throw ex; } return response; } /// /// Resolves the specified event type. /// /// Type of the event. /// public async Task ResolveEvent(PMR.Diagnostics.EventType eventType) { ResolveEventRequest request = new ResolveEventRequest() { Type = eventType }; return await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } /// /// Resets the embedded device. /// /// public async Task Reset() { StubFpgaWriteRegResponse response = null; StubFpgaWriteRegRequest request = null; try { request = new StubFpgaWriteRegRequest() { Address = 0x60000800 | 0x3D0, Value = 0x0 }; response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } catch (Exception ex) { throw ex; } Thread.Sleep(1000); try { request = new StubFpgaWriteRegRequest() { Address = 0x60000800 | 0x3D0, Value = 0x1 }; response = await SendRequest(request, new TransportRequestConfig() { ShouldLog = true }); } catch (Exception ex) { throw ex; } return response; } /// /// Directs the embedded device to switch to stand-by mode. /// /// public async Task StandBy() { LogManager.Log("Switching to stand-by mode..."); await SendRequest(new StandByRequest(), new TransportRequestConfig() { ShouldLog = true }); } /// /// Resets the device through the DFU channel. /// /// public Task ResetDFU() { return Task.Factory.StartNew(() => { LogManager.Log("Performing device reset through DFU..."); //LogManager.Log("Disconnecting Operator..."); //Disconnect().Wait(); //LogManager.Log("Operator disconnected."); FirmwareUpdateManager updateManager = new FirmwareUpdateManager(); LogManager.Log("Initializing DFU API..."); updateManager.Initialize(); LogManager.Log("Enumerating DFU devices..."); var device = updateManager.GetAvailableDevices(false).Where(x => !x.DeviceName.Contains("In-Circuit Debug Interface")).FirstOrDefault(); if (device != null) { LogManager.Log($"DFU device found: '{device.DeviceName}'."); LogManager.Log("Switching to DFU mode..."); device.SwitchToDFUMode(); Thread.Sleep(6000); LogManager.Log("Reattaching to DFU device..."); device = updateManager.GetAvailableDevices(false).Where(x => !x.DeviceName.Contains("In-Circuit Debug Interface")).FirstOrDefault(); if (device != null) { LogManager.Log("Resetting device..."); device.Reset(); Thread.Sleep(1000); LogManager.Log("Reset completed."); } else { throw LogManager.Log(new Exception("DFU device not found.")); } } else { throw LogManager.Log(new Exception("DFU device not found.")); } }); } /// /// Creates a storage manager for managing the machine file system. /// /// public StorageManager CreateStorageManager() { return new StorageManager(this); } /// /// Upgrades the firmware. /// /// The TFP stream (Tango Firmware Package File). /// Specify whether the connected machine is emulated and to skip the actual DFU interface. /// /// public virtual async Task UpgradeFirmware(Stream tfpStream, bool isEmulated = false) { bool cancel = false; ZipFile zip = null; Action abortAction = null; var upgradeHandler = new FirmwareUpgradeHandler(() => { cancel = true; abortAction?.Invoke(); }); try { LogManager.Log("Starting firmware upgrade..."); LogManager.Log($"Firmware upgrade flags: {String.Join(", ", FirmwareUpgradeMode.GetFlags().Select(x => x.ToString()))}"); if (!CanPrint) { throw LogManager.Log(new InvalidOperationException($"Could not perform firmware upgrade while operator status is '{Status}'.")); } LogManager.Log("Extracting tfp package..."); var package_info = await GetFirmwarePackageInfo(tfpStream); tfpStream.Position = 0; LogManager.Log("Validating TFP package..."); package_info.Validate(); LogManager.Log("Reading zip stream..."); zip = ZipFile.Read(tfpStream); double packageUploadTotal = 0; try { packageUploadTotal = zip.Entries.Sum(x => x.UncompressedSize); } catch (Exception ex) { LogManager.Log(ex, "Error calculating total package upload bytes."); } LogManager.Log("Creating storage manager..."); var storage = CreateStorageManager(); LogManager.Log("Getting storage drive information..."); var drive = await storage.GetStorageDrive(); LogManager.Log($"Storage drive info:\n{drive.ToJsonString()}"); LogManager.Log("Getting root folder..."); var root = await storage.GetRootFolder(); LogManager.Log($"Root folder: '{root.Path}'"); var existing_folder = root.Items.SingleOrDefault(x => x.Name == FIRMWARE_UPGRADE_FOLDER_NAME); if (existing_folder != null) { LogManager.Log("Root folder exists. Deleting..."); await storage.DeleteItem(existing_folder); } String package_folder = Path.Combine(drive.Root, FIRMWARE_UPGRADE_FOLDER_NAME); LogManager.Log($"Creating new folder: '{package_folder}'."); await storage.CreateFolder(package_folder); List handlers = new List(); List entries = zip.Entries.ToList(); List streams = new List(); LogManager.Log("Disabling keep alive..."); var keepAlive = UseKeepAlive; UseKeepAlive = false; Action upgradeDFU = null; Action uploadNext = null; Action validate = null; Action activate = null; Action postActivation = null; Action upgradeEureka = null; UpdateStatus(MachineStatuses.Upgrading); abortAction = new Action(() => { UpdateStatus(MachineStatuses.ReadyToDye); }); upgradeDFU = new Action(() => { try { if (FirmwareUpgradeMode.HasFlag(FirmwareUpgradeModes.DFU)) { if (package_info.ContainsMcu()) { LogManager.Log("DFU enabled. Starting upgrade via DFU..."); LogManager.Log("Extracting MCU file..."); ZipEntry mcuEntry = null; try { mcuEntry = entries.Single(x => x.FileName == package_info.FileDescriptors.Single(y => y.Destination == VersionFileDestination.Mcu).FileName); entries.Remove(mcuEntry); } catch (Exception ex) { LogManager.Log(ex, "Error extracting MCU file from package."); upgradeHandler.RaiseFailed(new IOException("Error retrieving MCU file from package.", ex)); return; } MemoryStream ms = new MemoryStream(); mcuEntry.Extract(ms); ms.Position = 0; byte[] data = ms.ToArray(); ms.Dispose(); FirmwareUpgradeManager upgradeManager = new FirmwareUpgradeManager(); upgradeManager.UpgradeProgress += (sender, e) => { upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, e.State.ToDescription(), false, e.Total, e.Progress); }; LogManager.Log("Disconnecting adapter..."); Adapter.Disconnect().Wait(); ResetEvents(); ResetInkFllingStatus(); try { if (!isEmulated) { LogManager.Log("Upgrading..."); upgradeManager.PerformUpgrade(data).Wait(); } else { LogManager.Log("Upgrading (emulated)..."); Thread.Sleep(3000); } } catch (Exception ex) { LogManager.Log("Firmware upgrade failed while doing DFU upload. We need to destroy the whole transport layer.", LogCategory.Error); UpdateStatus(MachineStatuses.Disconnected); upgradeHandler.RaiseFailed(ex); OnFailed(ex); return; } LogManager.Log("Waiting for the device..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, "Waiting for the device..."); Thread.Sleep(5000); LogManager.Log("Reconnecting adapter..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, "Connecting..."); Adapter.Connect().Wait(); Connect().Wait(); LogManager.Log("Connected..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, "Connected."); Thread.Sleep(2000); LogManager.Log("Waiting..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, "Waiting..."); Thread.Sleep(2000); UpdateStatus(MachineStatuses.Upgrading); } else { LogManager.Log("DFU is enabled but no MCU file was found on the package. Skipping..."); } } //Upload tfp package only if specified in flag && package info contains more files other than the mcu bin file. if (FirmwareUpgradeMode.HasFlag(FirmwareUpgradeModes.TFP_PACKAGE)) { if (package_info.ContainsNoneMcu()) { LogManager.Log("TFP package is enabled. Starting upload..."); uploadNext(); } else { LogManager.Log("TFP package is enabled but no other files other than the MCU file were found on the package. Skipping..."); postActivation(); } } else { postActivation(); } } catch (Exception ex) { UpdateStatus(MachineStatuses.ReadyToDye); upgradeHandler.RaiseFailed(ex); return; } }); upgradeEureka = new Action(() => { try { if (FirmwareUpgradeMode.HasFlag(FirmwareUpgradeModes.DFU)) { if (package_info.ContainsMcu()) { LogManager.Log("Firmware upgrade enabled. Starting upgrade via file system..."); var upgradeDrive = DriveInfo.GetDrives().FirstOrDefault(x => EUREKA_FIRMWARE_UPGRADE_DRIVE_LABELS.Contains(x.VolumeLabel)); if (upgradeDrive == null) { throw LogManager.Log(new IOException($"Could not locate any of the firmware upgrade volume labels ({EUREKA_FIRMWARE_UPGRADE_DRIVE_LABELS.Join(", ")})")); } String upgradeFolder = upgradeDrive.RootDirectory.FullName; LogManager.Log("Extracting MCU file..."); ZipEntry mcuEntry = null; try { mcuEntry = entries.Single(x => x.FileName == package_info.FileDescriptors.Single(y => y.Destination == VersionFileDestination.Mcu).FileName); entries.Remove(mcuEntry); } catch (Exception ex) { LogManager.Log(ex, "Error extracting MCU file from package."); upgradeHandler.RaiseFailed(new IOException("Error retrieving MCU file from package.", ex)); return; } LogManager.Log("Disconnecting adapter..."); Adapter.Disconnect().Wait(); ResetEvents(); ResetInkFllingStatus(); try { if (!isEmulated) { LogManager.Log("Upgrading..."); mcuEntry.Extract(upgradeFolder, ExtractExistingFileAction.OverwriteSilently); } else { LogManager.Log("Upgrading (emulated)..."); Thread.Sleep(3000); } } catch (Exception ex) { LogManager.Log(ex, "Firmware upgrade failed while extracting the MCU file to the upgrade location."); UpdateStatus(MachineStatuses.Disconnected); upgradeHandler.RaiseFailed(ex); OnFailed(ex); return; } LogManager.Log("Waiting for the device..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, "Waiting for the device..."); Thread.Sleep(5000); LogManager.Log("Reconnecting adapter..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, "Connecting..."); Adapter.Connect().Wait(); Connect().Wait(); LogManager.Log("Connected..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, "Connected."); Thread.Sleep(2000); LogManager.Log("Waiting..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Upgrading, "Waiting..."); Thread.Sleep(2000); UpdateStatus(MachineStatuses.Upgrading); } else { LogManager.Log("DFU is enabled but no MCU file was found on the package. Skipping..."); } } //Upload tfp package only if specified in flag && package info contains more files other than the mcu bin file. if (FirmwareUpgradeMode.HasFlag(FirmwareUpgradeModes.TFP_PACKAGE)) { if (package_info.ContainsNoneMcu()) { LogManager.Log("TFP package is enabled. Starting upload..."); uploadNext(); } else { LogManager.Log("TFP package is enabled but no other files other than the MCU file were found on the package. Skipping..."); postActivation(); } } else { postActivation(); } } catch (Exception ex) { UpdateStatus(MachineStatuses.ReadyToDye); upgradeHandler.RaiseFailed(ex); return; } }); uploadNext = new Action(() => { if (entries.Count > 0) { try { var entry = entries.First(); entries.Remove(entry); LogManager.Log($"Uploading file '{entry.FileName}'..."); var reader = entry.OpenReader(); streams.Add(reader); var handler = storage.UploadFile(Path.Combine(package_folder, entry.FileName), reader).Result; handlers.Add(handler); handler.Canceled += (_, __) => { upgradeHandler.RaiseCanceled(); cancel = true; abortAction(); }; handler.Completed += (_, __) => uploadNext(); handler.Failed += (_, failedEx) => { upgradeHandler.RaiseFailed(failedEx); cancel = true; abortAction(); }; handler.Progress += (_, e) => { if (cancel) { handler.Cancel(); return; } upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Uploading, $"Uploading '{entry.FileName}'...", false, packageUploadTotal, upgradeHandler.Current + e.Delta); }; } catch (Exception ex) { abortAction(); upgradeHandler.RaiseFailed(ex); } } else { validate(); } }); validate = new Action(() => { try { LogManager.Log("Validating version..."); streams.ForEach(x => x.Dispose()); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Validating, "Validating version..."); var validateRequest = new ValidateVersionRequest(); validateRequest.Path = package_folder; var validateResponse = SendRequest(validateRequest, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(10), ShouldLog = true }).Result; activate(); } catch (Exception ex) { upgradeHandler.RaiseFailed(ex); } }); activate = new Action(() => { try { TaskCompletionSource activationCompletion = new TaskCompletionSource(); bool completed = false; LogManager.Log("Activating version..."); upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Activating, "Activating version..."); var activateRequest = new ActivateVersionRequest(); activateRequest.Path = package_folder; SendContinuousRequest(activateRequest, new TransportContinuousRequestConfig() { Timeout = TimeSpan.FromSeconds(10), ContinuousTimeout = TimeSpan.FromSeconds(10), ShouldLog = true }) .Subscribe((response) => { if (!completed && response.Message.Progress > 0) { upgradeHandler.RaiseProgress(FirmwareUpgradeStatus.Activating, "Activating version...", false, response.Message.Total, response.Message.Progress); } }, (ex) => { if (!completed) { completed = true; activationCompletion.SetException(ex); } }, () => { if (!completed) { completed = true; activationCompletion.SetResult(true); } }); var result = activationCompletion.Task.GetAwaiter().GetResult(); postActivation(); } catch (Exception ex) { upgradeHandler.RaiseFailed(ex); } }); postActivation = new Action(() => { LogManager.Log("Firmware upgrade completed."); upgradeHandler.RaiseCompleted(); UpdateStatus(MachineStatuses.ReadyToDye); LogManager.Log("Enabling keep alive..."); UseKeepAlive = keepAlive; }); ThreadFactory.StartNew(() => { if (MachineType == MachineTypes.TS1800) { upgradeDFU(); } else { upgradeEureka(); } }); return upgradeHandler; } catch (Exception) { if (zip != null) { zip.Dispose(); } throw; } } /// /// Validates the firmware package integrity. /// /// The TFP (Tango Firmware Package File) stream. /// public Task GetFirmwarePackageInfo(Stream tfpStream) { return Task.Factory.StartNew(() => { using (ZipFile zip = ZipFile.Read(tfpStream)) { var reader = zip.Entries.SingleOrDefault(x => x.FileName == FIRMWARE_UPGRADE_CONFIG_FILE_NAME).OpenReader(); var info = VersionPackageDescriptor.Parser.ParseFrom(reader); reader.Close(); reader.Dispose(); return info; } }); } /// /// Directs the embedded device to validate the last uploaded firmware package. /// /// public async Task ValidateFirmwareVersion(String path) { var validateRequest = new ValidateVersionRequest(); validateRequest.Path = path; await SendRequest(validateRequest, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(10), ShouldLog = true }); } /// /// Directs the embedded device to validate the last uploaded firmware package. /// /// public async Task ActivateFirmwareVersion(String path) { var activateRequest = new ActivateVersionRequest(); activateRequest.Path = path; await SendRequest(activateRequest, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(10), ShouldLog = true }); } /// /// Turns off the machine. /// /// public Task PowerDown() { if (_isPowerDownRequestInProgress) { throw new InvalidOperationException("Machine power down is already in progress."); } _isPowerDownRequestInProgress = true; PowerDownHandler handler = new PowerDownHandler(new Task(() => { _isPowerDownRequestInProgress = false; Thread.Sleep(2000); var r = SendRequest(new AbortPowerDownRequest(), new TransportRequestConfig() { ShouldLog = true }).Result; })); Task.Factory.StartNew(() => { Thread.Sleep(100); bool firstResponse = true; SendContinuousRequest(new StartPowerDownRequest(), new TransportContinuousRequestConfig() { ContinuousTimeout = TimeSpan.FromSeconds(2), ShouldLog = true }).ObserveOn(new NewThreadScheduler()).Subscribe((response) => { if (firstResponse) { firstResponse = false; UpdateStatus(MachineStatuses.ShuttingDown); } handler.RaiseStatusChanged(response); }, (ex) => { if (_isPowerDownRequestInProgress) { _isPowerDownRequestInProgress = false; LogManager.Log(ex, "Power down error."); handler.RaiseFailed(ex); } }, () => { if (_isPowerDownRequestInProgress) { _isPowerDownRequestInProgress = false; Status = MachineStatuses.PowerDown; handler.RaiseCompleted(); } }); }); PowerDownStarted?.Invoke(this, new PowerDownStartedEventArgs() { Handler = handler, }); return Task.FromResult(handler); } /// /// Turns off the machine. /// /// public Task PerformHeadCleaning(bool longCleaning) { if (_isHeadCleaningInProgress) { throw new InvalidOperationException("Head cleaning is already in progress."); } if (!CanPrint) { throw new InvalidOperationException($"Cannot perform head cleaning while machine status is '{Status}'."); } _isHeadCleaningInProgress = true; bool _completed = false; var context = CreateJobContext(null, null); HeadCleaningHandler handler = null; handler = new HeadCleaningHandler(() => { _isHeadCleaningInProgress = false; Thread.Sleep(1000); if (!_completed) { _completed = true; OnHeadCleaningEnded(context, handler, JobRunStatus.Aborted); } var r = SendRequest(new AbortHeadCleaningRequest(), new TransportRequestConfig() { ShouldLog = true }).Result; }); Task.Factory.StartNew(() => { Thread.Sleep(100); bool firstResponse = true; SendContinuousRequest(new StartHeadCleaningRequest() { IsLongJob = longCleaning }, new TransportContinuousRequestConfig() { ContinuousTimeout = TimeSpan.FromSeconds(5), ShouldLog = true }).ObserveOn(new NewThreadScheduler()).Subscribe((response) => { if (firstResponse) { firstResponse = false; } handler.RaiseStatusChanged(response); }, (ex) => { if (!(ex is ContinuousResponseAbortedException)) { _isHeadCleaningInProgress = false; LogManager.Log(ex, "Head cleaning error."); handler.RaiseFailed(ex); } if (!_completed) { _completed = true; OnHeadCleaningEnded(context, handler, JobRunStatus.Failed); } }, () => { _isHeadCleaningInProgress = false; handler.RaiseCompleted(); if (!_completed) { _completed = true; OnHeadCleaningEnded(context, handler, JobRunStatus.Completed); } }); }); return Task.FromResult(handler); } /// /// Starts the automatic thread loading process. /// /// public async Task StartThreadLoading() { var response = await SendRequest(new TryThreadLoadingRequest()); } /// /// Continues the current thread loading. /// /// The process parameters. /// public async Task ContinueThreadLoading(ProcessParametersTable processParameters) { var process = processParameters.ToProcessParametersPMR(); var r = await SendRequest(new ContinueThreadLoadingRequest() { ProcessParameters = process, }, new TransportRequestConfig() { ShouldLog = true }); } public async Task AbortThreadLoading() { ThreadLoadingStatus = null; var response = await SendRequest(new AbortThreadLoadingRequest()); } /// /// Attempts to jog the thread in order to check whether there are no thread breaking issues. /// /// public async Task AttemptThreadJogging() { var r = await SendRequest(new AttemptThreadJoggingRequest() { }, new TransportRequestConfig() { ShouldLog = true, Timeout = TimeSpan.FromSeconds(20) }); } /// /// Emulates a hardware event that will last for the specified timeout. /// /// Type of the event. /// The timeout. public async void PushEmulatedEvent(Event ev, TimeSpan timeout) { if (!_emulatedEvents.Exists(x => x.Type == ev.Type)) { _emulatedEvents.Add(ev); await Task.Delay(timeout); _emulatedEvents.Remove(ev); } } /// /// Gets the last machine built-in test results. /// /// public async Task> GetBitResults(List bitTypes) { if (_bitResults == null) { var response = await SendRequest(new BitResultsRequest(), new TransportRequestConfig() { ShouldLog = true, Timeout = TimeSpan.FromMinutes(5) }); var compositions = new List(); foreach (var bitType in bitTypes) { BitResultComposition composition = new BitResultComposition(); composition.BitType = bitType; composition.BitResult = new BitResult() { BitType = (PMR.Diagnostics.BitType)bitType.Code, Description = "Skipped" }; compositions.Add(composition); } foreach (var bitResult in response.Message.Results) { var composition = compositions.SingleOrDefault(x => x.BitType.Code == bitResult.BitType.ToInt32()); if (composition != null) { composition.BitResult = bitResult; } } compositions = compositions.OrderBy(x => x.Priority).ToList(); _bitResults = compositions; } return _bitResults; } /// /// Notifies the remote machine about spool type change. /// If the machine is not connected the spool type will be added to the connection request. /// /// Type of the spool. public async Task SetSpoolType(JobSpoolType spoolType) { _currentSpoolType = spoolType; if (IsConnected) { LogManager.Log($"Changing spool type to: '{spoolType}'."); try { await SendRequest(new SpoolTypeChangedRequest() { SpoolType = spoolType }); } catch (Exception ex) { LogManager.Log(ex, "Error changing the spool type on the machine."); } } } /// /// Completes the waste replacement sequence. /// /// Approve or decline the sequence. public async Task CompleteWasteReplacement(bool approved) { if (_lastWasteReplaceRequestToken != null) { await SendResponse(new WasteReplaceResponse() { Approved = approved }, _lastWasteReplaceRequestToken); _lastWasteReplaceRequestToken = null; } } /// /// Gets the list of firmware version descriptors. /// /// public async Task> GetFirmwareVersionDescriptors() { var response = await SendRequest(new GetVersionDescriptorsRequest()); return response.Message.Descriptors.ToList(); } /// /// Resets the firmware card by the specified card id. /// /// The card identifier. /// public Task ResetCard(int cardID) { return SendRequest(new ResetCardRequest() { CardID = cardID }, new TransportRequestConfig() { Timeout = TimeSpan.FromSeconds(3) }); } /// /// Sets the firmware device time to auto power down. /// /// Timeout Minutes. /// public async Task SetPowerSavingMode(int timeToIdleMinutes, int timeToPowerDownMinutes) { try { LogManager.Log($"Setting machine power saving mode to {timeToIdleMinutes},{timeToPowerDownMinutes} minutes..."); var r = await SendRequest(new SetPowerDownTimeRequest() { TimeToIdleMinutes = timeToIdleMinutes, TimeToPowerOffMinutes = timeToPowerDownMinutes }); } catch (Exception ex) { LogManager.Log(ex, $"Error setting machine power saving mode."); throw ex; } } /// /// Sets the jerrican inserted liters. /// /// IDS pack index. /// The inserted liters. /// public async Task SetJerricanInsertedLiters(int idsPackIndex, double insertedLiters) { try { LogManager.Log($"Setting jerrican level at index {idsPackIndex} to {insertedLiters} L."); var r = await SendRequest(new SetJerricanLevelRequest() { Index = idsPackIndex, InsertedLiters = insertedLiters }); } catch (Exception ex) { LogManager.Log(ex, $"Error setting jerrican level."); throw ex; } } /// /// Sets the machine buzzer settings. /// public async Task SetBuzzerSettings(bool enable, int duration) { if (duration < 10) duration = 10; try { await SendRequest(new SetBuzzerSettingsRequest() { Enabled = enable, Duration = duration }); LogManager.Log($"Machine buzzer set to {(enable ? "Active" : "Inactive")}, Duration: {duration}."); } catch (Exception ex) { LogManager.Log(ex, "Error setting machine buzzer settings."); } } public async Task SetWhiteThreadSkip(bool enable) { try { await SendRequest(new SetWhiteThreadSkipRequest() { Enabled = enable }); LogManager.Log($"Machine white thread skip is now {(enable ? "Enabled" : "Disabled")}."); } catch (Exception ex) { LogManager.Log(ex, "Error setting machine white thread skip settings."); } } #endregion #region Print /// /// Prints the specified job. /// The process parameters table will be calculated using color conversion gamut region. /// This method cannot accept brush stops with 'Volume' as color space. /// /// The job. /// Optional job configuration. /// public async Task Print(Job job, AdditionalJobConfiguration config = null) { ProcessParametersTable processParameters = null; if (config == null) config = new AdditionalJobConfiguration(); await Task.Factory.StartNew(() => { IColorConverter converter = new DefaultColorConverter(); if (job.Rml == null) { throw new NullReferenceException("Job RML is null"); } var processGroup = job.Rml.ProcessParametersTablesGroups.FirstOrDefault(x => x.Active); if (processGroup == null) { throw new NullReferenceException("Could not locate an active process parameters tables group for RML " + job.Rml.Name); } try { bool useLightInks = config.UseLightInks; if (job.OrderedSegmentsWithGroups.Count > 1 && !job.EnableInterSegment) useLightInks = false; processParameters = converter.GetRecommendedProcessParameters(job, useLightInks); } catch (Exception ex) { throw LogManager.Log(new InvalidOperationException($"An error occurred while trying to resolve the recommended process parameters.\n{ex.Message}")); } if (processParameters == null) { throw new NullReferenceException("Could not locate any process parameters table in group " + processGroup.Name + " for RML " + job.Rml.Name); } try { foreach (var stop in job.OrderedSegmentsWithGroups.SelectMany(x => x.BrushStops).ToList()) { stop.SetLiquidVolumes(job.Machine.Configuration, job.Rml, processParameters); } } catch (Exception ex) { throw LogManager.Log(new InvalidOperationException($"An error occurred while trying to apply final liquid volumes.\n{ex.Message}")); } }); return await Print(job, processParameters, config); } /// /// Prints the specified job using the specified job parameters. /// /// The job. /// Process parameters table /// public Task Print(Job job, ProcessParametersTable processParameters, AdditionalJobConfiguration config = null) { return Task.Run(async () => { if (config == null) config = new AdditionalJobConfiguration(); if (!CanPrint) { throw new InvalidOperationException("Could not print while status = " + Status); } LogManager.Log($"Executing job '{job.Name}'..."); if (MachineStatus == null) { LogManager.Log("Aborting job execution. No machine status received yet."); throw new InvalidOperationException("Cannot execute a job before at least one machine status has been received."); } var context = CreateJobContext(job, config); CurrentProcessParameters = processParameters; UseKeepAlive = false; try { //Color Conversion await ApplyColorConversion(context.Job, processParameters, config); //Lubrication await ApplyLubrication(context.Job, config); //Modify transparent/white brush stops. (Transparent/white stops should be all zeros and 100% TI) await ApplyTransparentBrushStops(context.Job); //Validate liquid quantities List requiredLiquids = await PerformLiquidQuantityValidation(context.Job, processParameters); //Normalize Segments Group (Deprecated) await ApplySegmentsGroupsToJob(context.Job); //Create Job Ticket context.JobTicket = await CreateJobTicket(context, processParameters); //Create Job Handler context.JobHandler = CreateJobHandler(processParameters, config, context); //Apply PMR Segments to Job Ticket await ApplyPMRSegmentsToJobTicket(context, processParameters); //Log Job Outline (Only first and last brush stops if gradient). //await LogJobOutline(job, processParameters, ticket); //Add job ticket segments per number of units for the actual request. context.JobRequest.JobTicket = await NormalizeJobTicketUnits(job, processParameters, context.JobTicket); //Send Job Preperation Request. await SendJobPrepareRequest(context, requiredLiquids); //Upload Job Description File. await UploadJobDescriptionFile(context); ApplyResumeConfig(context, config); //Cache job and machine status for job resume. SaveCachedJobOperation(context.ClonedJob, config); } catch (Exception) { UseKeepAlive = context.InitialUseKeepAlive; throw; } ThreadFactory.StartNew(() => { Thread.Sleep(100); //Give some time for callers to register for the job handler events.. UpdateStatus(MachineStatuses.GettingReady); OnPrintingStarted(context); SendContinuousRequest(context.JobRequest, new TransportContinuousRequestConfig() { Timeout = TimeSpan.FromSeconds(10), ContinuousTimeout = TimeSpan.FromSeconds(10), ShouldLog = true }).Subscribe((response) => { ProcessJobStatus(context, response.Message.Status, processParameters, config?.ResumeConfig); }, (ex) => { ProcessJobError(context, processParameters, ex); }, () => { ProcessJobCompletion(context, processParameters); }); }); return context.JobHandler; }); } private async void ResumeJob() { LogManager.Log("Checking if a job is in progress..."); try { var res = await SendRequest(new CurrentJobRequest(), new TransportRequestConfig() { ShouldLog = true }); if (res.Message.IsJobInProgress) { LogManager.Log("Job is in progress. Trying to resume job..."); CachedJobOperation cache = LoadCachedJobOperation(); if (cache == null) { LogManager.Log("Cannot resume current job with no cached operation.", LogCategory.Error); return; } if (res.Message.JobTicket == null) { LogManager.Log("Cannot resume current. No job ticket received by the embedded device.", LogCategory.Error); return; } Guid g; if (res.Message.JobTicket.Guid == null || !Guid.TryParse(res.Message.JobTicket.Guid, out g)) { LogManager.Log("Cannot resume current. Invalid job guid received by the embedded device.", LogCategory.Error); return; } var processParameters = cache.ProcessParametersDTO.ToObservable(); var job = cache.JobDTO.ToObservable(); var machineConfiguration = cache.MachineConfigurationDTO.ToObservable(); var config = cache.Config; CurrentProcessParameters = processParameters; _machineConfiguration = machineConfiguration; ResumingJobEventArgs args = new ResumingJobEventArgs((actualJob) => { var context = CreateJobContext(actualJob, config, _lastMachineStatus); context.IsResumingJob = true; context.JobTicket = res.Message.JobTicket; context.JobHandler = CreateJobHandler(processParameters, config, context); RunningJob = null; RunningJobStatus = null; var request = new ResumeCurrentJobRequest(); ThreadFactory.StartNew(() => { Thread.Sleep(1000); //Give some time for callers to register for the job handler events.. UpdateStatus(MachineStatuses.GettingReady); OnPrintingStarted(context); SendContinuousRequest(request, new TransportContinuousRequestConfig() { Timeout = TimeSpan.FromSeconds(10), ContinuousTimeout = TimeSpan.FromSeconds(10), ShouldLog = true }).Subscribe((response) => { ProcessJobStatus(context, response.Message.Status, processParameters, config?.ResumeConfig); }, (ex) => { ProcessJobError(context, processParameters, ex); }, () => { ProcessJobCompletion(context, processParameters); }); }); return context.JobHandler; }); args.JobGuid = res.Message.JobTicket.Guid; ResumingJob?.Invoke(this, args); } } catch (Exception ex) { LogManager.Log(ex, "An error occurred while attempting to resume a job in progress..."); } } private void ApplyResumeConfig(CurrentJobContext context, AdditionalJobConfiguration config) { if (config.ResumeConfig != null) { LogManager.Log("Job Resume Detected..."); context.JobRequest.FirstUnitStartPosition = config.ResumeConfig.GlobalStartPosition; context.JobRequest.JobTicket.Length = (context.JobRequest.JobTicket.Length / Math.Max(context.JobRequest.JobTicket.NumberOfUnits, 1)) * (int)Math.Max(config.ResumeConfig.RemainingUnits, 1); context.JobRequest.JobTicket.NumberOfUnits = (uint)Math.Max(config.ResumeConfig.RemainingUnits, 1); LogManager.Log($"Resume Config:\n{config.ResumeConfig.ToJsonString()}\n"); } } private void ProcessJobStatus(CurrentJobContext context, JobStatus status, ProcessParametersTable processParameters, AdditionalJobConfiguration.ResumeConfiguration resumeConfig) { if (!context.JobCompleted) { context.JobHandler.RaiseStatusReceived(status); if (!context.ResponseLogged) { context.HeatingStartDate = DateTime.UtcNow; context.RequestSent = true; context.ResponseLogged = true; } if (status.Progress > 0) { if (context.InitialUseKeepAlive != UseKeepAlive) { UseKeepAlive = context.InitialUseKeepAlive; } if (context.ActualStartDate == null) { context.ActualStartDate = DateTime.UtcNow; } } if (JobHandlingMode == JobHandlerModes.SettingUp) { var printingStartPosition = processParameters.DryerBufferLengthMeters; if (resumeConfig != null && resumeConfig.GlobalStartPosition > 0) { printingStartPosition = resumeConfig.GlobalStartPosition; } if (status.Progress > printingStartPosition) { if (!context.JobCompleted) { UpdateStatus(MachineStatuses.Printing); } } } else { if (status.Progress > 0) { if (!context.JobCompleted) { UpdateStatus(MachineStatuses.Printing); } } } } } private void ProcessJobError(CurrentJobContext context, ProcessParametersTable processParameters, Exception ex) { if (!context.JobCompleted) { context.JobCompleted = true; UseKeepAlive = context.InitialUseKeepAlive; if (Status != MachineStatuses.Disconnected) { UpdateStatus(MachineStatuses.ReadyToDye); } if (!context.JobHandler.IsCanceled) { Exception finalException = ex; if (ex is ContinuousResponseAbortedException continuousException) { finalException = new ContinuousResponseAbortedException($"Job aborted by the embedded device ({continuousException.Container.ErrorMessage})."); } context.JobHandler.RaiseFailed(finalException); OnPrintingFailed(context, finalException); } } } private void ProcessJobCompletion(CurrentJobContext context, ProcessParametersTable processParameters) { if (!context.JobCompleted) { context.JobCompleted = true; UseKeepAlive = context.InitialUseKeepAlive; UpdateStatus(MachineStatuses.ReadyToDye); context.JobHandler.RaiseCompleted(); OnPrintingCompleted(context); } } private void OnJobHandlerStatusChanged(object sender, RunningJobStatus e) { RunningJobStatus = e; } private async void OnJobHandlerCanceled(CurrentJobContext context) { try { if (context.JobHandler.CanCancel) { context.JobHandler.CanCancel = false; context.JobHandler.IsCanceled = true; LogManager.Log("Aborting current job..."); if (context.RequestSent) { var result = await SendRequest(new AbortJobRequest(), new TransportRequestConfig() { ShouldLog = true }); } if (Status != MachineStatuses.Disconnected) { UpdateStatus(MachineStatuses.ReadyToDye); } OnPrintingAborted(context); context.JobHandler.RaiseCanceled(); } } catch (Exception ex) { context.JobHandler.CanCancel = true; LogManager.Log(ex, "Failed to cancel job."); } } private async Task UploadJobDescriptionFile(CurrentJobContext context) { context.UploadingStartDate = DateTime.UtcNow; if (JobUploadStrategy == JobUploadStrategy.JobDescriptionFile) { LogManager.Log("Generating job description file..."); context.JobRequest.JobTicket.Segments.Clear(); JobDescriptionFile jobDescriptionFile = new JobDescriptionFile(context.JobTicket.Segments); MemoryStream ms = jobDescriptionFile.ToStream(); context.JobHandler.RaiseStatusReceived(new JobStatus() { CurrentSegmentIndex = 0, Progress = 0, Message = "Uploading job description file...", }); LogManager.Log("Creating storage API manager..."); var storage = CreateStorageManager(); LogManager.Log("Getting storage drive information..."); var storageInfo = await storage.GetStorageDrive(); LogManager.Log("Getting root folder information..."); var root_folder = await storage.GetRootFolder(); var existing_item = root_folder.Items.SingleOrDefault(x => x.Name == JOB_DESCRIPTION_FILE_NAME); if (existing_item != null) { LogManager.Log("Removing previous job description file..."); await storage.DeleteItem(existing_item); } String job_file_path = Path.Combine(storageInfo.Root, JOB_DESCRIPTION_FILE_NAME); LogManager.Log($"Uploading job description file '{job_file_path}' of size: {ms.Length} bytes..."); TaskCompletionSource uploadCompletion = new TaskCompletionSource(); var uploader = await storage.UploadFile(job_file_path, ms); uploader.Canceled += (_, __) => { uploadCompletion.SetResult(true); }; uploader.Completed += (_, __) => { uploadCompletion.SetResult(true); }; uploader.Failed += (_, e) => { uploadCompletion.SetException(e); }; await uploadCompletion.Task; context.JobRequest.JobTicket.JobDescriptionFile = job_file_path; } } private async Task SendJobPrepareRequest(CurrentJobContext context, List requiredLiquids) { if (requiredLiquids.Count > 0) { JobPrepareRequest prepareRequest = new JobPrepareRequest(); prepareRequest.ProcessParameters = context.JobTicket.ProcessParameters; foreach (var requiredLiquid in requiredLiquids) { JobPrepareDispenser prepareDispenser = new JobPrepareDispenser(); prepareDispenser.DispenserLiquidType = (DispenserLiquidType)requiredLiquid.IdsPack.LiquidType.Type; prepareDispenser.Index = requiredLiquid.IdsPack.PackIndex; prepareDispenser.TotalNanoliter = Convert.ToInt32(requiredLiquid.Quantity); prepareDispenser.Active = requiredLiquid.Quantity > 0; prepareRequest.Dispensers.Add(prepareDispenser); } var response = await SendRequest(prepareRequest, new TransportRequestConfig() { ShouldLog = true, Timeout = TimeSpan.FromSeconds(10) }); } } private Task NormalizeJobTicketUnits(Job job, ProcessParametersTable processParameters, JobTicket ticket) { return Task.Run(() => { ticket = ticket.Clone(); var segs = new List(); if (JobUnitsMethod == JobUnitsMethods.Operator) { for (int i = 0; i < job.NumberOfUnits; i++) { foreach (var s in ticket.Segments) { var cloned = s.Clone(); segs.Add(cloned); } } } else { foreach (var s in ticket.Segments) { var cloned = s.Clone(); segs.Add(cloned); } } if (segs.Count > 0) { ticket.Segments.Clear(); ticket.Segments.AddRange(segs); } if (!job.EnableInterSegment) { NormalizeJobTicket(ticket, processParameters); } return ticket; }); } private Task LogJobOutline(Job job, ProcessParametersTable processParameters, JobTicket ticket) { return Task.Run(() => { var ticketToLog = ticket.Clone(); ticketToLog.Segments.Clear(); foreach (var seg in ticket.Segments) { JobSegment segmentToLog = new JobSegment(); segmentToLog.Length = seg.Length; segmentToLog.BrushStops.Add(seg.BrushStops.First()); if (seg.BrushStops.Count > 1) { segmentToLog.BrushStops.Add(seg.BrushStops.Last()); } ticketToLog.Segments.Add(segmentToLog); } if (!job.EnableInterSegment) { NormalizeJobTicket(ticketToLog, processParameters); } LogManager.Log($"Job outline for '{job.Name}':\n{ticketToLog.ToJsonString()}"); }); } private static void RaiseHandlerInitialStatus(JobHandler handler) { handler.RaiseStatusReceived(new JobStatus() { CurrentSegmentIndex = 0, Progress = 0, Message = "Preparing Job...", }); } private Task ApplyPMRSegmentsToJobTicket(CurrentJobContext context, ProcessParametersTable processParameters) { return Task.Run(() => { // Use the ORIGINAL job's segment list, so ticket segments aren't pre-multiplied foreach (var segment in context.OriginalJob.OrderedSegmentsWithGroups.ToList()) { if (segment is Segment simpleSegment) { context.JobTicket.Segments.Add( CreatePMRJobSegment(simpleSegment, context.OriginalJob, processParameters)); } else if (segment is SegmentsGroup group) { List groupSegments = new List(); foreach (var innerSegment in group.OrderedSegments) { groupSegments.Add( CreatePMRJobSegment(innerSegment, context.OriginalJob, processParameters)); } for (int i = 0; i < group.Repeats; i++) { context.JobTicket.Segments.AddRange(groupSegments.ToList()); } } } }); } private JobHandler CreateJobHandler(ProcessParametersTable processParameters, AdditionalJobConfiguration config, CurrentJobContext context) { JobHandler handler = new JobHandler(() => OnJobHandlerCanceled(context), context.ClonedJob, context.JobTicket, processParameters, JobHandlingMode, config.ResumeConfig); handler.StatusChanged += OnJobHandlerStatusChanged; return handler; } private Task CreateJobTicket(CurrentJobContext context, ProcessParametersTable processParameters) { Job job = context.Job; return Task.Run(() => { JobTicket ticket = new JobTicket(); ticket.Guid = context.OriginalJob.Guid; ticket.EnableInterSegment = job.EnableInterSegment; ticket.InterSegmentLength = Math.Max(job.InterSegmentLength, 1); ticket.EnableLubrication = job.EnableLubrication; ticket.Length = job.Length; ticket.WindingMethod = (JobWindingMethod)job.WindingMethod.Code; ticket.UploadStrategy = JobUploadStrategy; if (JobUnitsMethod == JobUnitsMethods.Device) { ticket.NumberOfUnits = (uint)Math.Max(job.NumberOfUnits, 1); } //Spool parameters ticket.Spool = new JobSpool(); job.SpoolType.MapPropertiesTo(ticket.Spool); ticket.Spool.JobSpoolType = (JobSpoolType)job.SpoolType.Code; //Override spool parameters from RML Spool calibration var rmlSpool = job.Rml.RmlsSpools.FirstOrDefault(x => x.SpoolType.Guid == job.SpoolType.Guid); if (rmlSpool != null) { ticket.Spool.RotationsPerPassage = rmlSpool.RotationsPerPassage != null ? rmlSpool.RotationsPerPassage.Value : ticket.Spool.RotationsPerPassage; ticket.Spool.Length = rmlSpool.Length != null ? rmlSpool.Length.Value : ticket.Spool.Length; ticket.Spool.BackingRate = rmlSpool.BackingRate != null ? rmlSpool.BackingRate.Value : ticket.Spool.BackingRate; ticket.Spool.BottomBackingRate = rmlSpool.BottomBackingRate != null ? rmlSpool.BottomBackingRate.Value : ticket.Spool.BottomBackingRate; ticket.Spool.BtsrSpoolTension = rmlSpool.BtsrSpoolTension != null ? rmlSpool.BtsrSpoolTension.Value : ticket.Spool.BtsrSpoolTension; ticket.Spool.StartOffsetPulses = rmlSpool.StartOffsetPulses != null ? rmlSpool.StartOffsetPulses.Value : ticket.Spool.StartOffsetPulses; ticket.Spool.SegmentOffsetPulses = rmlSpool.SegmentOffsetPulses != null ? rmlSpool.SegmentOffsetPulses.Value : ticket.Spool.SegmentOffsetPulses; } //Override spool parameters from Machine Spool calibration var machineSpool = job.Machine.Spools.FirstOrDefault(x => x.SpoolType.Guid == job.SpoolType.Guid); if (machineSpool != null) { ticket.Spool.LimitSwitchStartPointOffset = machineSpool.LimitSwitchStartPointOffset != null ? machineSpool.LimitSwitchStartPointOffset.Value : ticket.Spool.LimitSwitchStartPointOffset; //ticket.Spool.StartOffsetPulses = machineSpool.StartOffsetPulses != null ? machineSpool.StartOffsetPulses.Value : ticket.Spool.StartOffsetPulses; //ticket.Spool.BackingRate = machineSpool.BackingRate != null ? machineSpool.BackingRate.Value : ticket.Spool.BackingRate; //ticket.Spool.SegmentOffsetPulses = machineSpool.SegmentOffsetPulses != null ? machineSpool.SegmentOffsetPulses.Value : ticket.Spool.SegmentOffsetPulses; //ticket.Spool.BottomBackingRate = machineSpool.BottomBackingRate != null ? machineSpool.BottomBackingRate.Value : ticket.Spool.BottomBackingRate; } //Thread Parameters ticket.ThreadParameters = new ThreadParameters(); job.Rml.MapPrimitivesTo(ticket.ThreadParameters); ProcessParameters process = new ProcessParameters(); processParameters.MapPrimitivesTo(process); ticket.ProcessParameters = process; //Head Cleaning Parameters ticket.HeadCleaningParameters = new HeadCleaningParameters(); ticket.HeadCleaningParameters.CleanerFlow = job.Rml.CleanerFlow; ticket.HeadCleaningParameters.ArcHeadCleaningMotorSpeed = job.Rml.ArcHeadCleaningMotorSpeed; //BTSR Parameters ticket.BtsrParameters = new PMR.BTSR.BtsrParameters(); ticket.BtsrParameters.BtsrApplicationType = job.Rml.BtsrApplicationType != null ? (PMR.BTSR.BtsrApplicationType)job.Rml.BtsrApplicationType.Code : PMR.BTSR.BtsrApplicationType.Seamless; ticket.BtsrParameters.BtsrYarnType = job.Rml.BtsrYarnType != null ? (PMR.BTSR.BtsrYarnType)job.Rml.BtsrYarnType.Code : PMR.BTSR.BtsrYarnType.AllYarn3; ticket.BtsrParameters.TensionError = (float)job.Rml.BtsrTensionError; return ticket; }); } private Task ApplySegmentsGroupsToJob(Job job) { return Task.Run(() => { int max = job.OrderedSegmentsWithGroups.Last().SegmentIndex + 1; for (int i = 0; i < job.NumberOfUnits - 1; i++) { foreach (var s in job.OrderedSegmentsWithGroups.ToList()) { var cloned = s.Clone(job); cloned.SegmentIndex = max++; if (cloned is Segment simpleSegment) { job.Segments.Add(simpleSegment); } else if (cloned is SegmentsGroup g) { job.SegmentsGroups.Add(g); } } } }); } private Task> PerformLiquidQuantityValidation(Job job, ProcessParametersTable processParameters) { return Task.Run(() => { List requiredLiquids = new List(); //Validate liquid quantities if (EnableJobLiquidQuantityValidation && MachineType == MachineTypes.TS1800) { requiredLiquids = ValidateJobLiquidQuantity(job, processParameters, job.Machine.Configuration); } else { LogManager.Log("Liquid quantity validation is disabled. Skipping..."); } return requiredLiquids; }); } private Task ApplyTransparentBrushStops(Job job) { return Task.Run(() => { LogManager.Log("Modifying all 'white' brush stops..."); foreach (var stop in job.OrderedSegmentsWithGroups.SelectMany(x => x.BrushStops).Where(x => x.IsTransparent || x.IsWhite).ToList()) { foreach (var liquidVolume in stop.LiquidVolumes.Where(x => x.LiquidType != LiquidTypes.Transparent && x.LiquidType != LiquidTypes.Lubricant).ToList()) { liquidVolume.Volume = 0; } var tiLiquid = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack != null && x.IdsPack.LiquidType != null && x.LiquidType == LiquidTypes.Transparent); if (tiLiquid != null) { tiLiquid.Volume = 100; } } }); } private Task ApplyLubrication(Job job, AdditionalJobConfiguration config) { return Task.Run(() => { if (job.EnableLubrication) { if (config.UseLubricantVolume) { LogManager.Log($"Job custom lubrication is enabled. Settings all brush stops to {config.LubricationVolume}% lubricant."); foreach (var stop in job.OrderedSegmentsWithGroups.ToList().SelectMany(x => x.BrushStops).ToList()) { var lubricantVolume = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack != null && x.IdsPack.LiquidType != null && x.LiquidType == LiquidTypes.Lubricant); if (lubricantVolume != null) { lubricantVolume.Volume = config.LubricationVolume; } } } else { LogManager.Log($"Job auto lubrication is enabled. Settings all none Volume brush stops to 100% lubricant."); foreach (var stop in job.OrderedSegmentsWithGroups.ToList().SelectMany(x => x.BrushStops).Where(x => x.BrushColorSpace != ColorSpaces.Volume).ToList()) { var lubricantVolume = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack != null && x.IdsPack.LiquidType != null && x.LiquidType == LiquidTypes.Lubricant); if (lubricantVolume != null) { lubricantVolume.Volume = 100; } } } } else { LogManager.Log("Job lubrication is disabled."); } }); } private Task ApplyColorConversion(Job job, ProcessParametersTable processParameters, AdditionalJobConfiguration config) { return Task.Run(() => { if (config.UseColorConversion) { IColorConverter converter = new DefaultColorConverter(); bool useLightInks = config.UseLightInks; //Use light inks only if one segment or inter segment is enabled. if (MachineType == MachineTypes.TS1800 && job.OrderedSegmentsWithGroups.Count > 1 && !job.EnableInterSegment) useLightInks = false; foreach (var segment in job.OrderedSegmentsWithGroups.ToList()) { foreach (var stop in segment.BrushStops) { try { var output = converter.Convert(stop, false, useLightInks && segment.BrushStops.Count == 1); //Use light inks only if this is a solid segment. output.ApplyOnBrushStopLiquidVolumes(stop, processParameters); } catch (Exception ex) { throw new InvalidOperationException($"Error processing the coordinates of stop '{stop.StopIndex}' of segment '{stop.Segment.SegmentIndex}'.", ex); } if (stop.IsLiquidVolumesOutOfRange) { throw new InvalidOperationException($"The specified ink volumes at segment {segment.SegmentIndex} exceeds the maximum allowed total volume for the current thread."); } } } } }); } protected CurrentJobContext CreateJobContext(Job job, AdditionalJobConfiguration config, MachineStatus machineStatus = null) { var jobContext = new CurrentJobContext(machineStatus ?? MachineStatus, GetJobRunLiquidQuantities); jobContext.InitialUseKeepAlive = UseKeepAlive; jobContext.StartDate = DateTime.UtcNow; RunningJob = null; RunningJobStatus = null; if (job != null) { if (job.NumberOfUnits < 1) { job.NumberOfUnits = 1; } jobContext.Job = job; jobContext.OriginalJob = job; var clonedJob = job.Clone(); clonedJob.Guid = job.Guid; clonedJob.Name = job.Name; jobContext.ClonedJob = clonedJob; job = job.Clone(); job.Guid = jobContext.OriginalJob.Guid; job.Name = jobContext.OriginalJob.Name; jobContext.Job = job; var jobForJobRun = job.Clone(); jobForJobRun.Guid = job.Guid; jobForJobRun.Name = job.Name; jobForJobRun.ID = job.ID; if (config.ResumeConfig != null) { jobForJobRun.ResumeStartPosition = config.ResumeConfig.ResumeProgress; } jobContext.JobForJobRun = jobForJobRun; RunningJob = clonedJob; } return jobContext; } private Task NormalizeJobTicket(JobTicket ticket, ProcessParametersTable processParameters) { return Task.Run(() => { var maxNanoStop = ticket.Segments.SelectMany(x => x.BrushStops).OrderBy(x => x.GetTotalNanoliterPerCentimeter()).Last(); double maxNanoliter = maxNanoStop.GetTotalNanoliterPerCentimeter(); LogManager.Log($"Normalizing brush stops TI quantities by {maxNanoliter} nanoliters..."); foreach (var stop in ticket.Segments.SelectMany(x => x.BrushStops).Where(x => x != maxNanoStop)) { stop.NormalizeStop(maxNanoliter, processParameters.MinInkUptake, processParameters.DyeingSpeed); } }); } #endregion } }