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; 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"; public const int MAX_DISPENSER_NANOLITER = 130000000; private bool _diagnosticsSent; private bool _eventsSent; private bool _debugSent; private bool _machineStatusSent; private EmbeddedLogItem _last_embedded_debug_log; private static RunningJobStatus _last_job_status; public static String EmbeddedLogsFolder { get; private set; } public static String EmbeddedLogsTag { get; private set; } #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); } } /// /// Initializes a new instance of the class. /// public MachineOperator() : base() { DeviceInformation = new DeviceInformation(); MachineEventsStateProvider = new DefaultMachineEventsStateProvider(); JobRunsLogger = new BasicJobRunsLogger(this); JobRunsLogger.Start(); EnableEventsNotification = true; EnableMachineStatusUpdates = true; EnableJobResume = true; LogEmbeddedDebuggingToFile = true; FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; GradientGenerationConfiguration = new DefaultGradientGenerationConfiguration(); EmergencyNotificationProvider = new UsbEmergencyNotificationProvider("COM1"); EnableJobLiquidQuantityValidation = true; FailsWithAdapter = true; } /// /// 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; /// /// Occurs when a request has been sent. /// public event EventHandler RequestSent; /// /// Occurs when a response has been sent. /// public event EventHandler ResponseSent; /// /// Occurs when a request has timed out. /// public event EventHandler RequestFailed; /// /// Occurs when a request response has been received. /// public event EventHandler ResponseReceived; /// /// 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; #endregion #region Properties /// /// 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; } 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)); LogManager.Log("Machine operator status changed: " + _status); } } } private MachineStatus _machineStatus; /// /// Gets the machine embedded device status. /// public MachineStatus MachineStatus { get { return _machineStatus; } set { _machineStatus = 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; } } 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 _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(); } } /// /// 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; } #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; SendContinuousRequest(request).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { OnDiagnosticsDataAvailable(response); if (!responseLogged) { LogResponseReceived(response.Message); responseLogged = true; } }, (ex) => { _diagnosticsSent = false; if (!(ex is ContinuousResponseAbortedException)) { LogRequestFailed(request, ex); } }, () => { _diagnosticsSent = false; LogManager.Log("Diagnostics response completed!?", LogCategory.Warning); }); LogRequestSent(request); } else if (_diagnosticsSent) { _diagnosticsSent = false; if (State == TransportComponentState.Connected) { var req = new StopDiagnosticsRequest(); try { LogRequestSent(req); var res = await SendRequest(req); LogResponseReceived(res.Message); } catch (Exception ex) { LogRequestFailed(req, ex); } } } } /// /// 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).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { OnEventsNotification(response); if (!responseLogged) { LogResponseReceived(response.Message); responseLogged = true; } }, (ex) => { _eventsSent = false; if (!(ex is ContinuousResponseAbortedException)) { LogRequestFailed(request, ex); } }, () => { _eventsSent = false; LogManager.Log("Events Notification response completed!?", LogCategory.Warning); }); LogRequestSent(request); } else if (_eventsSent) { _eventsSent = false; if (State == TransportComponentState.Connected) { var req = new StopEventsNotificationRequest(); try { LogRequestSent(req); var res = await SendRequest(req); LogResponseReceived(res.Message); } catch (Exception ex) { LogRequestFailed(req, ex); } } } } /// /// 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).ObserveOn(new NewThreadScheduler()) .Subscribe ( (response) => { if (!responseLogged) { LogResponseReceived(response.Message); responseLogged = true; } OnDebugLogAvailable(response); }, (ex) => { _debugSent = false; if (!(ex is ContinuousResponseAbortedException)) { LogRequestFailed(request, ex); } }, () => { _debugSent = false; }); LogRequestSent(request); } else if (_debugSent) { _debugSent = false; if (State == TransportComponentState.Connected) { var req = new StopDebugLogRequest(); try { LogRequestSent(req); var res = await SendRequest(req); LogResponseReceived(res.Message); } catch (Exception ex) { LogRequestFailed(req, ex); } } } } /// /// 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).ObserveOn(new NewThreadScheduler()).Subscribe( (response) => { OnMachineStatusChanged(response); if (!responseLogged) { LogResponseReceived(response.Message); responseLogged = true; } }, (ex) => { _machineStatusSent = false; if (!(ex is ContinuousResponseAbortedException)) { LogRequestFailed(request, ex); } }, () => { _machineStatusSent = false; LogManager.Log("Machine status update response completed!?", LogCategory.Warning); }); LogRequestSent(request); } else if (_machineStatusSent) { _machineStatusSent = false; if (State == TransportComponentState.Connected) { var req = new StopMachineStatusUpdateRequest(); try { LogRequestSent(req); var res = await SendRequest(req); LogResponseReceived(res.Message); } catch (Exception ex) { LogRequestFailed(req, ex); } } } } /// /// Invokes the event. /// /// The sensors data. protected virtual void OnDiagnosticsDataAvailable(StartDiagnosticsResponse data) { DiagnosticsDataAvailable?.Invoke(this, data); } /// /// Called when events notification message has been received. /// /// The response. protected virtual void OnEventsNotification(StartEventsNotificationResponse response) { if (MachineEventsStateProvider != null) { MachineEventsStateProvider.ApplyEvents(response.Events); } EventsNotification?.Invoke(this, response); } /// /// Invokes the event. /// /// The sensors data. protected virtual void OnDebugLogAvailable(StartDebugLogResponse data) { if (_last_embedded_debug_log == null || _last_embedded_debug_log.DebugLogResponse.Message != data.Message) { _last_embedded_debug_log = new EmbeddedLogItem(data); if (LogEmbeddedDebuggingToFile && EmbeddedLogManager != null) { EmbeddedLogManager.Log(_last_embedded_debug_log); } DebugLogAvailable?.Invoke(this, data); } else { _last_embedded_debug_log.Repeated++; if (LogEmbeddedDebuggingToFile && EmbeddedLogManager != null) { EmbeddedLogManager.Log(new EmbeddedLogItem(data)); } } } /// /// Called when the machine status has been update /// /// The response. protected virtual void OnMachineStatusChanged(StartMachineStatusUpdateResponse response) { if (response.Status == null) return; bool changed = (MachineStatus == null || response.Status.State != MachineStatus.State); MachineStatus = response.Status; MachineStatusChanged?.Invoke(this, MachineStatus); if (changed) { LogManager.Log($"Machine State Changed: {MachineStatus.State}."); switch (MachineStatus.State) { case MachineState.Initializing: Status = MachineStatuses.Service; break; //case MachineState.PreparingJob: // Status = MachineStatuses.GettingReady; // break; case MachineState.Ready: Status = MachineStatuses.ReadyToDye; break; //case MachineState.Sleep: // Status = MachineStatuses.Standby; // break; case MachineState.PowerOff: Status = MachineStatuses.ShuttingDown; break; case MachineState.Error: Status = MachineStatuses.Error; break; } } } /// /// Called when the request has been sent /// /// The request. protected virtual void OnRequestSent(IMessage request) { RequestSent?.Invoke(this, request); } /// /// Called when the response has been received /// /// The response. protected virtual void OnResponseReceived(IMessage response) { ResponseReceived?.Invoke(this, response); } /// /// Called when a new request has been received. /// /// The request. protected override void OnRequestReceived(MessageContainer container) { base.OnRequestReceived(container); if (container.Type == MessageType.CartridgeValidationRequest) { OnCartridgeValidationRequestReceived(container.Token, MessageFactory.ExtractMessageFromContainer(container)); } else if (container.Type == MessageType.UpdateStatusRequest) { OnUpdateStatusRequestReceived(container.Token, MessageFactory.ExtractMessageFromContainer(container)); } } /// /// Called when the response has been sent /// /// The response. protected virtual void OnResponseSent(IMessage response) { ResponseSent?.Invoke(this, response); } /// /// Called when the request has been failed /// /// The request. protected virtual void OnRequestFailed(IMessage request, Exception exception) { RequestFailed?.Invoke(this, new RequestFailedEventArgs(request, exception)); } /// /// 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 { Status = (MachineStatuses)request.Status; } catch (Exception ex) { LogManager.Log(ex); } SendResponse(new UpdateStatusResponse()); } #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; Status = MachineStatuses.Disconnected; } } /// /// Disconnects the machine operator and the underlying transporter. /// /// public async override Task Disconnect() { if (Status == MachineStatuses.Upgrading) return; if (State == TransportComponentState.Connected) { DisconnectRequest request = new DisconnectRequest(); LogRequestSent(request); try { var response = await SendRequest(request); LogResponseReceived(response.Message); Status = MachineStatuses.Disconnected; } catch (Exception ex) { LogRequestFailed(request, ex); } } if (MachineEventsStateProvider != null) { MachineEventsStateProvider.Reset(); } 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() }; LogRequestSent(request); try { var response = await SendRequest(request); LogResponseReceived(response.Message); if (Status != MachineStatuses.Upgrading) { Status = MachineStatuses.ReadyToDye; } DeviceInformation = response.Message.DeviceInformation; _diagnosticsSent = false; _eventsSent = false; _debugSent = false; _machineStatusSent = false; OnEnableDiagnosticsChanged(EnableDiagnostics); OnEnableEmbeddedDebuggingChanged(EnableEmbeddedDebugging); OnEnableEventsNotification(EnableEventsNotification); OnEnableMachineStatusUpdatesChanged(EnableMachineStatusUpdates); if (EnableJobResume) { ResumeJob(); } } catch (Exception ex) { LogRequestFailed(request, ex); await base.Disconnect(); throw ex; } finally { UseKeepAlive = keep_alive; } } } #endregion #region Private Methods private async void ResumeJob() { LogManager.Log("Checking if a job is in progress..."); try { var res = await SendRequest(new CurrentJobRequest()); if (res.Message.IsJobInProgress) { JobTicket jobTicket = res.Message.JobTicket; ProcessParametersTable processParameters = new ProcessParametersTable(); jobTicket.ProcessParameters.MapPrimitivesTo(processParameters); ResumingJobEventArgs args = new ResumingJobEventArgs((job) => { if (Status != MachineStatuses.ReadyToDye) { throw new InvalidOperationException("Could not print while status = " + Status); } RunningJob = null; RunningJobStatus = null; var originalJob = job; CurrentProcessParameters = processParameters; var request = new ResumeCurrentJobRequest(); JobHandler handler = null; handler = new JobHandler(async () => { try { var result = await SendRequest(new AbortJobRequest()); PrintingAborted?.Invoke(this, new PrintingEventArgs(handler, originalJob)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, originalJob)); handler.RaiseCanceled(); } catch (Exception ex) { LogManager.Log(ex, "Failed to cancel job."); } }, originalJob, jobTicket, processParameters, JobHandlingMode); handler.StatusChanged += (x, s) => { RunningJobStatus = s; }; LogRequestSent(request); bool responseLogged = false; Thread.Sleep(500); //Just wait maybe Shlomo is getting this message to fast after restart ? bool completed = false; SendContinuousRequest(request, null, TimeSpan.FromSeconds(2)).Subscribe((response) => { if (!completed) { if (!responseLogged) { if (_last_job_status != null) { _last_job_status.IsCanceled = false; _last_job_status.IsCompleted = false; _last_job_status.IsFailed = false; handler.Status = _last_job_status; } } handler.RaiseStatusReceived(response.Message.Status); if (!responseLogged) { Status = MachineStatuses.GettingReady; responseLogged = true; RunningJob = originalJob; PrintingStarted?.Invoke(this, new PrintingEventArgs(handler, originalJob)); LogResponseReceived(response.Message); } if (JobHandlingMode == JobHandlerModes.SettingUp) { if (response.Message.Status.Progress > processParameters.DryerBufferLengthMeters) { if (!completed) { Status = MachineStatuses.Printing; } } } else { if (response.Message.Status.Progress > 0) { if (!completed) { Status = MachineStatuses.Printing; } } } } }, (ex) => { if (!completed) { completed = true; if (!(ex is ContinuousResponseAbortedException)) { Status = MachineStatuses.ReadyToDye; if (!handler.IsCanceled) { PrintingFailed?.Invoke(this, new PrintingFailedEventArgs(handler, originalJob, ex)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, originalJob)); handler.RaiseFailed(ex); LogRequestFailed(request, ex); } } else { Status = MachineStatuses.ReadyToDye; } } }, () => { if (!completed) { completed = true; Status = MachineStatuses.ReadyToDye; PrintingCompleted?.Invoke(this, new PrintingEventArgs(handler, originalJob)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, originalJob)); handler.RaiseCompleted(); } }); return handler; }); args.JobGuid = jobTicket.Guid; ResumingJob?.Invoke(this, args); } } catch (Exception ex) { LogManager.Log(ex); } } /// /// Logs the request sent. /// /// The message. protected void LogRequestSent(IMessage message) { if (!(message is FileChunkUploadRequest) && !(message is FileDownloadRequest)) { LogManager.Log(String.Format("Sending request '{0}'...{1}{2}", message.GetType().Name, Environment.NewLine, message.ToJsonString())); OnRequestSent(message); } } /// /// Logs the request failed. /// /// The message. protected void LogRequestFailed(IMessage message, Exception ex) { LogManager.Log(String.Format("Request failed '{0}'...{1}{2}{1}{3}", message.GetType().Name, Environment.NewLine, message.ToJsonString(), ex.ToString()), LogCategory.Error); OnRequestFailed(message, ex); } /// /// Logs the response received. /// /// The message. protected void LogResponseReceived(IMessage message) { if (!(message is FileChunkUploadResponse) && !(message is FileDownloadResponse)) { LogManager.Log(String.Format("Response received '{0}'...{1}{2}", message.GetType().Name, Environment.NewLine, message.ToJsonString())); OnResponseReceived(message); } } /// /// 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..."); stops = GradientGenerationConfiguration.Generate(segment, job, processParameters, (e) => { PreparingJobProgress?.Invoke(this, e); }); LogManager.Log($"Gradient generated."); } 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; LogRequestSent(request); bool responseLogged = false; var previous_segments_length = job.Segments.Where(x => x.SegmentIndex < segment.SegmentIndex).Sum(x => x.Length); SendContinuousRequest(request, null, TimeSpan.FromSeconds(2)).Subscribe((response) => { response.Message.Status.Progress += previous_segments_length; handler.RaiseStatusReceived(response.Message.Status); if (!responseLogged && segment == job.OrderedSegments.First()) { responseLogged = true; Status = MachineStatuses.Printing; RunningJob = handler.Job; PrintingStarted?.Invoke(this, new PrintingEventArgs(handler, handler.Job)); LogResponseReceived(response.Message); } }, (ex) => { if (!(ex is ContinuousResponseAbortedException)) { Status = MachineStatuses.ReadyToDye; if (!handler.IsCanceled) { PrintingFailed?.Invoke(this, new PrintingFailedEventArgs(handler, handler.Job, ex)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, handler.Job)); handler.RaiseFailed(ex); LogRequestFailed(request, ex); } } else { Status = MachineStatuses.ReadyToDye; } }, () => { if (segment == job.OrderedSegments.Last()) { Status = MachineStatuses.ReadyToDye; PrintingCompleted?.Invoke(this, new PrintingEventArgs(handler, handler.Job)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, handler.Job)); handler.RaiseCompleted(); } else { handler.RaiseSpoolChangeRequired(() => { ContinueSingleSpoolJob(segment.GetNextSegment(), job, processParameters, handler); }, () => { PrintingAborted?.Invoke(this, new PrintingEventArgs(handler, handler.Job)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, handler.Job)); Status = MachineStatuses.ReadyToDye; handler.RaiseCanceled(); }); } }); } private void ValidateJobLiquidQuantity(Job job, ProcessParametersTable processParameters, Configuration configuration) { Dictionary liquidQuantities = new Dictionary(); foreach (var pack in configuration.NoneEmptyIdsPacks.OrderBy(x => x.PackIndex)) { liquidQuantities.Add(pack.PackIndex, 0); } 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; 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 liquidVolumes in stop.LiquidVolumes) { liquidQuantities[liquidVolumes.IdsPack.PackIndex] += liquidVolumes.NanoliterPerCentimeter * stop_length_centimeters; } } } } 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.DispenserLevel, Required = (int)liquidQuantities[index] }; if (liquidQuantities[index] > packLevel.DispenserLevel) { shouldThrow = true; } 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) { 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); } } #endregion #region Public Methods /// /// 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. /// public Task Print(Job job) { IColorConverter converter = new DefaultColorConverter(); var jobSegments = job.OrderedSegments; 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); } var processParameters = converter.GetRecommendedProcessParameters(job); if (processParameters == null) { throw new NullReferenceException("Could not locate any process parameters table in group " + processGroup.Name + " for RML " + job.Rml.Name); } //Perform color correction foreach (var stop in jobSegments.SelectMany(x => x.BrushStops)) { if (stop.LiquidVolumes == null) { if (stop.BrushColorSpace == ColorSpaces.RGB || stop.BrushColorSpace == ColorSpaces.LAB) { var output = converter.Convert(stop, false); //TODO: Restore this when Mirta conversion is working as expected. //if (suggestions.OutOfGamut) //{ // throw new InvalidOperationException("Cannot print a brush stop which is out of gamut."); //} stop.SetLiquidVolumes(job.Machine.Configuration, job.Rml, processParameters); foreach (var outputLiquid in output.SingleCoordinates.OutputLiquids) { var liquidVolume = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack.LiquidType.Code == outputLiquid.LiquidType.ToInt32()); if (liquidVolume == null) { throw new NullReferenceException("Liquid volume not found for color conversion output liquid '" + outputLiquid.LiquidType + "'."); } liquidVolume.Volume = outputLiquid.Volume; } } else if (stop.BrushColorSpace == ColorSpaces.Catalog) { if (stop.ColorCatalogsItem != null) { stop.SetLiquidVolumes(job.Machine.Configuration, job.Rml, processParameters); if (stop.ColorCatalogsItem.Cyan > 0) { var liquidVolume = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack.LiquidType.Code == LiquidTypes.Cyan.ToInt32()); if (liquidVolume == null) { throw new NullReferenceException("Liquid volume not found for color conversion output liquid '" + LiquidTypes.Cyan + "'."); } liquidVolume.Volume = stop.ColorCatalogsItem.Cyan; } if (stop.ColorCatalogsItem.Magenta > 0) { var liquidVolume = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack.LiquidType.Code == LiquidTypes.Magenta.ToInt32()); if (liquidVolume == null) { throw new NullReferenceException("Liquid volume not found for color conversion output liquid '" + LiquidTypes.Magenta + "'."); } liquidVolume.Volume = stop.ColorCatalogsItem.Magenta; } if (stop.ColorCatalogsItem.Yellow > 0) { var liquidVolume = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack.LiquidType.Code == LiquidTypes.Yellow.ToInt32()); if (liquidVolume == null) { throw new NullReferenceException("Liquid volume not found for color conversion output liquid '" + LiquidTypes.Yellow + "'."); } liquidVolume.Volume = stop.ColorCatalogsItem.Yellow; } if (stop.ColorCatalogsItem.Black > 0) { var liquidVolume = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack.LiquidType.Code == LiquidTypes.Black.ToInt32()); if (liquidVolume == null) { throw new NullReferenceException("Liquid volume not found for color conversion output liquid '" + LiquidTypes.Black + "'."); } liquidVolume.Volume = stop.ColorCatalogsItem.Black; } } else { throw new InvalidOperationException($"No catalog item specified for segment color."); } } else if (stop.BrushColorSpace == ColorSpaces.Volume) { stop.SetLiquidVolumes(job.Machine.Configuration, job.Rml, processParameters); } else { throw new InvalidOperationException($"Unsupported color space {stop.BrushColorSpace}."); } } if (job.EnableLubrication) { var lubricantVolume = stop.LiquidVolumes.SingleOrDefault(x => x.IdsPack != null && x.IdsPack.LiquidType != null && x.IdsPack.LiquidType.Code == LiquidTypes.Lubricant.ToInt32()); if (lubricantVolume != null) { lubricantVolume.Volume = 100; } } } return Print(job, processParameters); } /// /// Prints the specified job using the specified job parameters. /// /// The job. /// Process parameters table /// public Task Print(Job job, ProcessParametersTable processParameters) { return Task.Factory.StartNew(() => { if (Status != MachineStatuses.ReadyToDye) { throw new InvalidOperationException("Could not print while status = " + Status); } LogManager.Log($"Executing job '{job.Name}'..."); RunningJob = null; RunningJobStatus = null; if (job.NumberOfUnits < 1) { job.NumberOfUnits = 1; } if (EnableJobLiquidQuantityValidation) { ValidateJobLiquidQuantity(job, processParameters, job.Machine.Configuration); } var originalJob = job; var clonedJob = job.Clone(); clonedJob.Guid = job.Guid; clonedJob.Name = job.Name; CurrentProcessParameters = processParameters; JobRequest request = new JobRequest(); job = job.Clone(); job.Guid = originalJob.Guid; job.Name = originalJob.Name; int max = job.OrderedSegments.Last().SegmentIndex + 1; var segments = job.OrderedSegments.ToList(); for (int i = 0; i < job.NumberOfUnits - 1; i++) { foreach (var s in segments) { var cloned = s.Clone(job); cloned.SegmentIndex = max++; job.Segments.Add(cloned); } } JobTicket ticket = new JobTicket(); ticket.Guid = 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; if (JobUnitsMethod == JobUnitsMethods.Device) { ticket.NumberOfUnits = (uint)job.NumberOfUnits; } ticket.Spool = new JobSpool(); job.SpoolType.MapPrimitivesTo(ticket.Spool); var spool = job.Machine.Spools.SingleOrDefault(x => x.SpoolType == job.SpoolType); if (spool == null) { throw new InvalidOperationException("Job spool type is not registered with this machine."); } else { spool.MapPrimitivesTo(ticket.Spool); } ticket.Spool.JobSpoolType = (JobSpoolType)job.SpoolType.Code; ProcessParameters process = new ProcessParameters(); processParameters.MapPrimitivesTo(process); ticket.ProcessParameters = process; JobHandler handler = null; bool canceled = false; bool requestSent = false; handler = new JobHandler(async () => { try { if (!canceled) { canceled = true; LogManager.Log($"Aborting current gradient generation..."); GradientGenerationConfiguration.AbortCurrentGeneration(); if (requestSent) { var result = await SendRequest(new AbortJobRequest()); } PrintingAborted?.Invoke(this, new PrintingEventArgs(handler, clonedJob)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, clonedJob)); handler.RaiseCanceled(); } } catch (Exception ex) { LogManager.Log(ex, "Failed to cancel job."); } }, clonedJob, ticket, processParameters, JobHandlingMode); handler.StatusChanged += (x, s) => { RunningJobStatus = s; }; if (!job.IsAllSegmentsPerSpool) { ContinueSingleSpoolJob(job.OrderedSegments.First(), job, processParameters, handler); return handler; } ThreadFactory.StartNew(async () => { Status = MachineStatuses.GettingReady; RunningJob = clonedJob; PrintingStarted?.Invoke(this, new PrintingEventArgs(handler, clonedJob)); Thread.Sleep(100); handler.RaiseStatusReceived(new JobStatus() { CurrentSegmentIndex = 0, Progress = 0, Message = "Preparing Job...", }); foreach (var segment in originalJob.OrderedSegments) { try { ticket.Segments.Add(CreatePMRJobSegment(segment, originalJob, processParameters)); } catch (Exception ex) { handler.RaiseFailed(ex); Status = MachineStatuses.ReadyToDye; return; } if (handler.IsCanceled) { Status = MachineStatuses.ReadyToDye; return; } } if (handler.IsCanceled) { Status = MachineStatuses.ReadyToDye; return; } 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); } request.JobTicket = ticket.Clone(); request.JobTicket.UploadStrategy = JobUploadStrategy; //Use this if you want to log the entire job... var logRequest = request.Clone(); LogManager.Log($"Job upload method is set to {JobUploadStrategy}..."); var oldKeepAlive = UseKeepAlive; if (JobUploadStrategy == JobUploadStrategy.JobDescriptionFile) { LogManager.Log("Generating job description file..."); try { request.JobTicket.Segments.Clear(); JobDescriptionFile jobDescriptionFile = new JobDescriptionFile(ticket.Segments); MemoryStream ms = jobDescriptionFile.ToStream(); handler.RaiseStatusReceived(new JobStatus() { CurrentSegmentIndex = 0, Progress = 0, Message = "Uploading job description file...", }); LogManager.Log("Creating storage API manager..."); var storage = CreateStorageManager(); //Suppress keep alive while job uploads. //storage.SuppressKeepAliveWhileFileUploads = true; UseKeepAlive = false; //This is a work around for Shlomo not managing to keep alive while parsing the file. 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}"); await storage.UploadFileSync(job_file_path, ms); LogManager.Log("Job upload completed successfully."); ms.Dispose(); request.JobTicket.JobDescriptionFile = job_file_path; } catch (Exception ex) { UseKeepAlive = oldKeepAlive; Status = MachineStatuses.ReadyToDye; PrintingFailed?.Invoke(this, new PrintingFailedEventArgs(handler, clonedJob, ex)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, clonedJob)); handler.RaiseFailed(ex); LogRequestFailed(request, ex); return; } } if (handler.IsCanceled) { UseKeepAlive = oldKeepAlive; Status = MachineStatuses.ReadyToDye; return; } LogRequestSent(request); bool responseLogged = false; bool completed = false; //Use this in case Shlomo is sending progress after completion. SendContinuousRequest(request, null, TimeSpan.FromSeconds(2)).Subscribe((response) => { if (!completed) { handler.RaiseStatusReceived(response.Message.Status); _last_job_status = handler.Status; if (response.Message.Status.Progress > 0) { if (oldKeepAlive != UseKeepAlive) { UseKeepAlive = oldKeepAlive; } } if (!responseLogged) { requestSent = true; responseLogged = true; LogResponseReceived(response.Message); } if (JobHandlingMode == JobHandlerModes.SettingUp) { if (response.Message.Status.Progress > processParameters.DryerBufferLengthMeters) { if (!completed) { Status = MachineStatuses.Printing; } } } else { if (response.Message.Status.Progress > 0) { if (!completed) { Status = MachineStatuses.Printing; } } } } }, (ex) => { if (!completed) { completed = true; UseKeepAlive = oldKeepAlive; if (!(ex is ContinuousResponseAbortedException)) { Status = MachineStatuses.ReadyToDye; if (!handler.IsCanceled) { PrintingFailed?.Invoke(this, new PrintingFailedEventArgs(handler, clonedJob, ex)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, clonedJob)); handler.RaiseFailed(ex); LogRequestFailed(request, ex); } } else { Status = MachineStatuses.ReadyToDye; } } }, () => { if (!completed) { completed = true; UseKeepAlive = oldKeepAlive; Status = MachineStatuses.ReadyToDye; PrintingCompleted?.Invoke(this, new PrintingEventArgs(handler, clonedJob)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, clonedJob)); handler.RaiseCompleted(); } }); }); return handler; }); } /// /// Executes a print stub for emulating a full 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. /// /// /// Cannot print a brush stop with volume color space when process parameters table has not been specified. /// or /// Could not print while status = " + Status /// /// /// Job RML is null /// or /// Could not locate an active process parameters tables group for RML " + job.Rml.Name /// or /// Could not locate process parameters table index " + processParametersTableIndex + " in group " + processGroup.Name + " for RML " + job.Rml.Name /// or /// Liquid volume not found for color conversion output liquid '" + outputLiquid.LiquidType + "'. /// public Task PrintStub(Job job) { return Task.Factory.StartNew(() => { //Check not brush stop has color space 'Volume'. if (job.Segments.SelectMany(x => x.BrushStops).ToList().Exists(x => x.ColorSpace.Code == ColorSpaces.Volume.ToInt32())) { throw new InvalidOperationException("Cannot print a brush stop with volume color space when process parameters table has not been specified."); } //Get least common process parameters table index. int processParametersTableIndex = 0; 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); } var processParameters = processGroup.ProcessParametersTables.FirstOrDefault(x => x.TableIndex == processParametersTableIndex); if (processParameters == null) { throw new NullReferenceException("Could not locate process parameters table index " + processParametersTableIndex + " in group " + processGroup.Name + " for RML " + job.Rml.Name); } //Perform color correction foreach (var stop in job.Segments.SelectMany(x => x.BrushStops)) { if (stop.LiquidVolumes == null) { stop.SetLiquidVolumes(job.Machine.Configuration, job.Rml, processParameters); } foreach (var liquidVolume in stop.LiquidVolumes) { liquidVolume.Volume = 10; } } if (Status != MachineStatuses.ReadyToDye) { throw new InvalidOperationException("Could not print while status = " + Status); } RunningJob = null; RunningJobStatus = null; var originalJob = job; CurrentProcessParameters = processParameters; StubJobRequest request = new StubJobRequest(); if (job.NumberOfUnits < 1) { job.NumberOfUnits = 1; } job = job.Clone(); var segments = job.OrderedSegments.ToList(); for (int i = 0; i < job.NumberOfUnits - 1; i++) { foreach (var s in segments) { job.Segments.Add(s); } } JobTicket ticket = new JobTicket(); ticket.Guid = originalJob.Guid; ticket.EnableInterSegment = job.EnableInterSegment; ticket.InterSegmentLength = job.InterSegmentLength; ticket.Length = job.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; foreach (var segment in job.OrderedSegments) { JobSegment jobSegment = new JobSegment(); jobSegment.Length = segment.LengthWithFactor; jobSegment.Name = segment.Name; foreach (var stop in segment.BrushStops) { 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; 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); } ticket.Segments.Add(jobSegment); } request.JobTicket = ticket; JobHandler handler = null; handler = new JobHandler(async () => { try { var result = await SendRequest(new StubAbortJobRequest()); PrintingAborted?.Invoke(this, new PrintingEventArgs(handler, originalJob)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, originalJob)); handler.RaiseCanceled(); } catch (Exception ex) { LogManager.Log(ex, "Failed to cancel job."); } }, originalJob, ticket, processParameters, JobHandlingMode); handler.StatusChanged += (x, s) => { RunningJobStatus = s; }; LogRequestSent(request); bool responseLogged = false; SendContinuousRequest(request, null, TimeSpan.FromSeconds(2)).Subscribe((response) => { handler.RaiseStatusReceived(response.Message.Status); if (!responseLogged) { responseLogged = true; Status = MachineStatuses.Printing; RunningJob = originalJob; PrintingStarted?.Invoke(this, new PrintingEventArgs(handler, originalJob)); LogResponseReceived(response.Message); } }, (ex) => { if (!(ex is ContinuousResponseAbortedException)) { Status = MachineStatuses.ReadyToDye; if (!handler.IsCanceled) { PrintingFailed?.Invoke(this, new PrintingFailedEventArgs(handler, originalJob, ex)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, originalJob)); handler.RaiseFailed(ex); LogRequestFailed(request, ex); } } else { Status = MachineStatuses.ReadyToDye; } }, () => { Status = MachineStatuses.ReadyToDye; PrintingCompleted?.Invoke(this, new PrintingEventArgs(handler, originalJob)); PrintingEnded?.Invoke(this, new PrintingEventArgs(handler, originalJob)); handler.RaiseCompleted(); }); return handler; }); } /// /// 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; LogRequestSent(request); response = await SendRequest(request); LogResponseReceived(response); } catch (Exception ex) { LogRequestFailed(request, 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) { 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 { CurrentHardwareConfiguration = hardwareConfiguration; LogRequestSent(request); response = await SendRequest(request); LogResponseReceived(response); } catch (Exception ex) { LogRequestFailed(request, ex); throw ex; } return response; } /// /// Starts jogging the specified motor. /// /// The request. /// public async Task StartMotorJogging(MotorJoggingRequest request) { MotorJoggingResponse response = null; try { LogRequestSent(request); response = await SendRequest(request); LogResponseReceived(response); } catch (Exception ex) { LogRequestFailed(request, ex); throw ex; } return response; } /// /// Stops jogging the specified motor. /// /// The request. /// public async Task StopMotorJogging(MotorAbortJoggingRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// Starts homing the specified motor. /// /// The request. /// public IObservable StartMotorHoming(MotorHomingRequest request) { LogRequestSent(request); return SendContinuousRequest(request, null, TimeSpan.FromSeconds(5)).Select(x => x.Message); } /// /// Stops homing the specified motor. /// /// The request. /// public async Task StopMotorHoming(MotorAbortHomingRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// Starts jogging the specified dispenser. /// /// The request. /// public async Task StartDispenserJogging(DispenserJoggingRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// Stops jogging the specified dispenser. /// /// The request. /// public async Task StopDispenserJogging(DispenserAbortJoggingRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// Starts homing the specified dispenser. /// /// The request. /// public IObservable StartDispenserHoming(DispenserHomingRequest request) { LogRequestSent(request); return SendContinuousRequest(request, null, TimeSpan.FromSeconds(5)).Select(x => x.Message); } /// /// Stops homing the specified dispenser. /// /// The request. /// public async Task StopDispenserHoming(DispenserAbortHomingRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// Turn on/off the specified digital output pin. /// /// The request. /// public async Task SetDigitalOut(SetDigitalOutRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// Starts jogging the thread motion system. /// /// The request. /// public async Task StartThreadJogging(ThreadJoggingRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// Stops jogging the thread motion system. /// /// The request. /// public async Task StopThreadJogging(ThreadAbortJoggingRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// Sets the specified component value. /// /// The request. /// public async Task SetComponentValue(SetComponentValueRequest request) { LogRequestSent(request); return await SendRequest(request); } /// /// 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 { LogRequestSent(request); response = await SendRequest(request); LogResponseReceived(response); } catch (Exception ex) { LogRequestFailed(request, 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 { LogRequestSent(request); response = await SendRequest(request); LogResponseReceived(response); } catch (Exception ex) { LogRequestFailed(request, 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 { LogRequestSent(request); response = await SendRequest(request); LogResponseReceived(response); } catch (Exception ex) { LogRequestFailed(request, 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 }; LogRequestSent(request); return await SendRequest(request); } /// /// Resets the embedded device. /// /// public async Task Reset() { StubFpgaWriteRegResponse response = null; StubFpgaWriteRegRequest request = null; try { request = new StubFpgaWriteRegRequest() { Address = 0x60000800 | 0x3D0, Value = 0x0 }; LogRequestSent(request); response = await SendRequest(request); LogResponseReceived(response); } catch (Exception ex) { LogRequestFailed(request, ex); throw ex; } Thread.Sleep(1000); try { request = new StubFpgaWriteRegRequest() { Address = 0x60000800 | 0x3D0, Value = 0x1 }; LogRequestSent(request); response = await SendRequest(request); LogResponseReceived(response); } catch (Exception ex) { LogRequestFailed(request, ex); throw ex; } return response; } /// /// 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). /// public async Task UpgradeFirmware(Stream tfpStream) { bool cancel = false; ZipFile zip = null; Action abortAction = null; var upgradeHandler = new FirmwareUpgradeHandler(() => { cancel = true; abortAction?.Invoke(); }); try { if (Status != MachineStatuses.ReadyToDye) { throw LogManager.Log(new InvalidOperationException($"Could not perform firmware upgrade while operator status is '{Status}'.")); } var package_info = await GetFirmwarePackageInfo(tfpStream); tfpStream.Position = 0; zip = ZipFile.Read(tfpStream); var storage = CreateStorageManager(); var drive = await storage.GetStorageDrive(); var root = await storage.GetRootFolder(); var existing_folder = root.Items.SingleOrDefault(x => x.Name == FIRMWARE_UPGRADE_FOLDER_NAME); if (existing_folder != null) { await storage.DeleteItem(existing_folder); } String package_folder = Path.Combine(drive.Root, FIRMWARE_UPGRADE_FOLDER_NAME); await storage.CreateFolder(package_folder); List handlers = new List(); List entries = zip.Entries.ToList(); List streams = new List(); var keepAlive = UseKeepAlive; UseKeepAlive = false; Action upgradeDFU = null; Action uploadNext = null; Action validate = null; Action activate = null; Action postActivation = null; Status = MachineStatuses.Upgrading; abortAction = new Action(() => { Status = MachineStatuses.ReadyToDye; }); upgradeDFU = new Action(() => { try { if (FirmwareUpgradeMode.HasFlag(FirmwareUpgradeModes.DFU)) { var mcuEntry = zip.Entries.Single(x => x.FileName == package_info.FileDescriptors.Single(y => y.Destination == VersionFileDestination.Mcu).FileName); 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.Total = (long)e.Total; upgradeHandler.RaiseProgress((long)e.Progress, FirmwareUpgradeStatus.Upgrading, e.State.ToDescription()); }; Adapter.Disconnect().Wait(); if (MachineEventsStateProvider != null) { MachineEventsStateProvider.Reset(); } upgradeManager.PerformUpgrade(data).Wait(); upgradeHandler.RaiseProgress(100, FirmwareUpgradeStatus.Upgrading, "Waiting for the device..."); Thread.Sleep(5000); upgradeHandler.RaiseProgress(100, FirmwareUpgradeStatus.Upgrading, "Connecting..."); Adapter.Connect().Wait(); Connect().Wait(); upgradeHandler.RaiseProgress(100, FirmwareUpgradeStatus.Upgrading, "Connected."); Thread.Sleep(2000); upgradeHandler.RaiseProgress(100, FirmwareUpgradeStatus.Upgrading, "Waiting..."); Thread.Sleep(2000); Status = MachineStatuses.Upgrading; } if (FirmwareUpgradeMode.HasFlag(FirmwareUpgradeModes.TFP_PACKAGE)) { upgradeHandler.Total = zip.Entries.Sum(x => x.UncompressedSize); uploadNext(); } else { postActivation(); } } catch (Exception ex) { Status = MachineStatuses.ReadyToDye; upgradeHandler.RaiseFailed(ex); return; } }); uploadNext = new Action(() => { if (entries.Count > 0) { try { var entry = entries.First(); entries.Remove(entry); 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(upgradeHandler.Current + e.Delta, FirmwareUpgradeStatus.Uploading, $"Uploading '{entry.FileName}'..."); }; } catch (Exception ex) { abortAction(); upgradeHandler.RaiseFailed(ex); } } else { validate(); } }); validate = new Action(() => { try { streams.ForEach(x => x.Dispose()); upgradeHandler.RaiseProgress(upgradeHandler.Total, FirmwareUpgradeStatus.Validating, "Validating version..."); var validateRequest = new ValidateVersionRequest(); validateRequest.Path = package_folder; var validateResponse = SendRequest(validateRequest, TimeSpan.FromSeconds(10)).Result; activate(); } catch (Exception ex) { upgradeHandler.RaiseFailed(ex); } }); activate = new Action(() => { try { upgradeHandler.RaiseProgress(upgradeHandler.Total, FirmwareUpgradeStatus.Activating, "Activating version..."); var activateRequest = new ActivateVersionRequest(); activateRequest.Path = package_folder; var activateResponse = SendRequest(activateRequest, TimeSpan.FromSeconds(10)).Result; postActivation(); } catch (Exception ex) { upgradeHandler.RaiseFailed(ex); } }); postActivation = new Action(() => { upgradeHandler.RaiseCompleted(); Status = MachineStatuses.ReadyToDye; UseKeepAlive = keepAlive; }); ThreadFactory.StartNew(() => { upgradeDFU(); }); 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, TimeSpan.FromSeconds(10)); } /// /// 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, TimeSpan.FromSeconds(10)); } #endregion } }