mattercontrol/PrinterCommunication/PrinterConnection.cs
2017-09-23 09:19:32 -07:00

2864 lines
No EOL
84 KiB
C#

/*
Copyright (c) 2014, Lars Brubaker
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MatterHackers.Agg;
using MatterHackers.GCodeVisualizer;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DataStorage;
using MatterHackers.MatterControl.PrinterCommunication.Io;
using MatterHackers.MatterControl.PrintQueue;
using MatterHackers.MatterControl.SlicerConfiguration;
using MatterHackers.SerialPortCommunication;
using MatterHackers.SerialPortCommunication.FrostedSerial;
using MatterHackers.VectorMath;
using Microsoft.Win32.SafeHandles;
namespace MatterHackers.MatterControl.PrinterCommunication
{
public enum CommunicationStates
{
Disconnected,
AttemptingToConnect,
FailedToConnect,
Connected,
PreparingToPrint,
Printing,
PrintingFromSd,
Paused,
FinishedPrint,
Disconnecting,
ConnectionLost
};
public enum DetailedPrintingState { HomingAxis, HeatingBed, HeatingExtruder, Printing };
public enum FirmwareTypes { Unknown, Repetier, Marlin, Sprinter };
/// <summary>
/// This is the class that communicates with a RepRap printer over the serial port.
/// It handles opening and closing the serial port and does quite a bit of gcode parsing.
/// It should be refactored into better modules at some point.
/// </summary>
public class PrinterConnection
{
public static RootedObjectEventHandler AnyCommunicationStateChanged = new RootedObjectEventHandler();
public static RootedObjectEventHandler AnyConnectionSucceeded = new RootedObjectEventHandler();
public RootedObjectEventHandler BedTemperatureRead = new RootedObjectEventHandler();
public RootedObjectEventHandler BedTemperatureSet = new RootedObjectEventHandler();
public RootedObjectEventHandler CommunicationStateChanged = new RootedObjectEventHandler();
public RootedObjectEventHandler CommunicationUnconditionalFromPrinter = new RootedObjectEventHandler();
public RootedObjectEventHandler CommunicationUnconditionalToPrinter = new RootedObjectEventHandler();
public RootedObjectEventHandler ConnectionFailed = new RootedObjectEventHandler();
public RootedObjectEventHandler ConnectionSucceeded = new RootedObjectEventHandler();
public RootedObjectEventHandler DestinationChanged = new RootedObjectEventHandler();
public RootedObjectEventHandler EnableChanged = new RootedObjectEventHandler();
public RootedObjectEventHandler HotendTemperatureRead = new RootedObjectEventHandler();
public RootedObjectEventHandler HotendTemperatureSet = new RootedObjectEventHandler();
public RootedObjectEventHandler FanSpeedSet = new RootedObjectEventHandler();
public RootedObjectEventHandler FirmwareVersionRead = new RootedObjectEventHandler();
public RootedObjectEventHandler PositionRead = new RootedObjectEventHandler();
public RootedObjectEventHandler PrintFinished = new RootedObjectEventHandler();
public RootedObjectEventHandler PauseOnLayer = new RootedObjectEventHandler();
public RootedObjectEventHandler FilamentRunout = new RootedObjectEventHandler();
public RootedObjectEventHandler PrintingStateChanged = new RootedObjectEventHandler();
public RootedObjectEventHandler ReadLine = new RootedObjectEventHandler();
public RootedObjectEventHandler WroteLine = new RootedObjectEventHandler();
public bool WatingForPositionRead
{
get
{
// make sure the longest we will wait under any circumstance is 60 seconds
if (waitingForPosition.ElapsedMilliseconds > 60000)
{
waitingForPosition.Reset();
PositionReadQueued = false;
}
return waitingForPosition.IsRunning || PositionReadQueued;
}
}
public TerminalLog TerminalLog { get; }
public RootedObjectEventHandler AtxPowerStateChanged = new RootedObjectEventHandler();
private bool atxPowerIsOn = false;
internal const int MAX_EXTRUDERS = 16;
private const int MAX_INVALID_CONNECTION_CHARS = 3;
private static PrinterConnection globalInstance;
private object locker = new object();
private readonly int JoinThreadTimeoutMs = 5000;
private PrintTask activePrintTask;
private double actualBedTemperature;
private int currentlyActiveExtruderIndex = 0;
private double[] actualHotendTemperature = new double[MAX_EXTRUDERS];
private CheckSumLines allCheckSumLinesSent = new CheckSumLines();
private int backupAmount = 0;
private CommunicationStates communicationState = CommunicationStates.Disconnected;
private string connectionFailureMessage = "Unknown Reason";
private Thread connectThread;
private PrinterMove currentDestination;
public double CurrentExtruderDestination { get { return currentDestination.extrusion; } }
public double CurrentFeedRate { get { return currentDestination.feedRate; } }
private double currentSdBytes = 0;
private PrinterMachineInstruction.MovementTypes extruderMode = PrinterMachineInstruction.MovementTypes.Absolute;
private int fanSpeed;
private bool firmwareUriGcodeSend = false;
private int currentLineIndexToSend = 0;
private bool ForceImmediateWrites = false;
private string itemNotFoundMessage = "Item not found".Localize();
private string lastLineRead = "";
private PrinterMove lastReportedPosition;
private GCodeFile loadedGCode = new GCodeMemoryFile();
private GCodeFileStream gCodeFileStream0 = null;
private PauseHandlingStream pauseHandlingStream1 = null;
private QueuedCommandsStream queuedCommandStream2 = null;
private MacroProcessingStream macroProcessingStream3 = null;
private RelativeToAbsoluteStream relativeToAbsoluteStream4 = null;
private PrintLevelingStream printLevelingStream5 = null;
private WaitForTempStream waitForTempStream6 = null;
private BabyStepsStream babyStepsStream7 = null;
private ExtrusionMultiplyerStream extrusionMultiplyerStream8 = null;
private FeedRateMultiplyerStream feedrateMultiplyerStream9 = null;
private RequestTemperaturesStream requestTemperaturesStream10 = null;
private ProcessWriteRegexStream processWriteRegExStream11 = null;
private GCodeStream totalGCodeStream = null;
private PrinterMachineInstruction.MovementTypes movementMode = PrinterMachineInstruction.MovementTypes.Absolute;
public CommunicationStates PrePauseCommunicationState { get; private set; } = CommunicationStates.Printing;
private DetailedPrintingState printingStatePrivate;
private FoundStringContainsCallbacks ReadLineContainsCallBacks = new FoundStringContainsCallbacks();
private FoundStringStartsWithCallbacks ReadLineStartCallBacks = new FoundStringStartsWithCallbacks();
// we start out by setting it to a nothing file
private IFrostedSerialPort serialPort;
private bool stopTryingToConnect = false;
private double targetBedTemperature;
private double[] targetHotendTemperature = new double[MAX_EXTRUDERS];
private Stopwatch timeHaveBeenWaitingForOK = new Stopwatch();
private Stopwatch timeSinceLastReadAnything = new Stopwatch();
private Stopwatch timeSinceLastWrite = new Stopwatch();
private Stopwatch timeSinceRecievedOk = new Stopwatch();
private Stopwatch timeSinceStartedPrint = new Stopwatch();
private Stopwatch timeWaitingForSdProgress = new Stopwatch();
private double totalSdBytes = 0;
private bool PositionReadQueued { get; set; } = false;
private Stopwatch waitingForPosition = new Stopwatch();
private FoundStringContainsCallbacks WriteLineContainsCallBacks = new FoundStringContainsCallbacks();
private FoundStringStartsWithCallbacks WriteLineStartCallBacks = new FoundStringStartsWithCallbacks();
private double secondsSinceUpdateHistory = 0;
private EventHandler unregisterEvents;
private double feedRateRatio = 1;
public PrinterConnection(PrinterConfig printer)
{
this.printer = printer;
TerminalLog = new TerminalLog(this);
MonitorPrinterTemperature = true;
StringComparer stringComparer = StringComparer.OrdinalIgnoreCase;
ReadLineStartCallBacks.AddCallbackToKey("start", FoundStart);
ReadLineStartCallBacks.AddCallbackToKey("start", PrintingCanContinue);
ReadLineStartCallBacks.AddCallbackToKey("ok", SuppressEcho);
ReadLineStartCallBacks.AddCallbackToKey("wait", SuppressEcho);
ReadLineStartCallBacks.AddCallbackToKey("T:", SuppressEcho); // repetier
ReadLineStartCallBacks.AddCallbackToKey("ok", PrintingCanContinue);
ReadLineStartCallBacks.AddCallbackToKey("Done saving file", PrintingCanContinue);
ReadLineStartCallBacks.AddCallbackToKey("B:", ReadTemperatures); // smoothie
ReadLineContainsCallBacks.AddCallbackToKey("T0:", ReadTemperatures); // marlin
ReadLineContainsCallBacks.AddCallbackToKey("T:", ReadTemperatures); // repatier
ReadLineStartCallBacks.AddCallbackToKey("SD printing byte", ReadSdProgress); // repetier
ReadLineStartCallBacks.AddCallbackToKey("C:", ReadTargetPositions);
ReadLineStartCallBacks.AddCallbackToKey("ok C:", ReadTargetPositions); // smoothie is reporting the C: with an ok first.
ReadLineStartCallBacks.AddCallbackToKey("X:", ReadTargetPositions);
ReadLineStartCallBacks.AddCallbackToKey("ok X:", ReadTargetPositions); //
ReadLineStartCallBacks.AddCallbackToKey("rs ", PrinterRequestsResend); // smoothie is lower case and no :
ReadLineStartCallBacks.AddCallbackToKey("RS:", PrinterRequestsResend);
ReadLineContainsCallBacks.AddCallbackToKey("Resend:", PrinterRequestsResend);
ReadLineContainsCallBacks.AddCallbackToKey("FIRMWARE_NAME:", PrinterStatesFirmware);
ReadLineStartCallBacks.AddCallbackToKey("EXTENSIONS:", PrinterStatesExtensions);
#region hardware failure callbacks
// smoothie temperature failures
ReadLineContainsCallBacks.AddCallbackToKey("T:inf", PrinterReportsError);
ReadLineContainsCallBacks.AddCallbackToKey("B:inf", PrinterReportsError);
// marlin temperature failures
ReadLineContainsCallBacks.AddCallbackToKey("MINTEMP", PrinterReportsError);
ReadLineContainsCallBacks.AddCallbackToKey("MAXTEMP", PrinterReportsError);
ReadLineContainsCallBacks.AddCallbackToKey("M999", PrinterReportsError);
ReadLineContainsCallBacks.AddCallbackToKey("Error: Extruder switched off", PrinterReportsError);
ReadLineContainsCallBacks.AddCallbackToKey("Heater decoupled", PrinterReportsError);
ReadLineContainsCallBacks.AddCallbackToKey("cold extrusion prevented", PrinterReportsError);
ReadLineContainsCallBacks.AddCallbackToKey("Error:Thermal Runaway, system stopped!", PrinterReportsError);
ReadLineContainsCallBacks.AddCallbackToKey("Error:Heating failed", PrinterReportsError);
ReadLineStartCallBacks.AddCallbackToKey("temp sensor defect", PrinterReportsError);
// repetier temperature failures
ReadLineContainsCallBacks.AddCallbackToKey("dry run mode", PrinterReportsError);
ReadLineStartCallBacks.AddCallbackToKey("accelerometer send i2c error", PrinterReportsError);
ReadLineStartCallBacks.AddCallbackToKey("accelerometer i2c recv error", PrinterReportsError);
// s3g temperature failures
ReadLineContainsCallBacks.AddCallbackToKey("Bot is Shutdown due to Overheat", PrinterReportsError);
#endregion hardware failure callbacks
WriteLineStartCallBacks.AddCallbackToKey("G90", MovementWasSetToAbsoluteMode);
WriteLineStartCallBacks.AddCallbackToKey("G91", MovementWasSetToRelativeMode);
WriteLineStartCallBacks.AddCallbackToKey("M80", AtxPowerUpWasWritenToPrinter);
WriteLineStartCallBacks.AddCallbackToKey("M81", AtxPowerDownWasWritenToPrinter);
WriteLineStartCallBacks.AddCallbackToKey("M82", ExtruderWasSetToAbsoluteMode);
WriteLineStartCallBacks.AddCallbackToKey("M83", ExtruderWasSetToRelativeMode);
WriteLineStartCallBacks.AddCallbackToKey("M104", HotendTemperatureWasWritenToPrinter);
WriteLineStartCallBacks.AddCallbackToKey("M106", FanSpeedWasWritenToPrinter);
WriteLineStartCallBacks.AddCallbackToKey("M107", FanOffWasWritenToPrinter);
WriteLineStartCallBacks.AddCallbackToKey("M109", HotendTemperatureWasWritenToPrinter);
WriteLineStartCallBacks.AddCallbackToKey("M140", BedTemperatureWasWritenToPrinter);
WriteLineStartCallBacks.AddCallbackToKey("M190", BedTemperatureWasWritenToPrinter);
WriteLineStartCallBacks.AddCallbackToKey("T", ExtruderIndexSet);
ActiveSliceSettings.SettingChanged.RegisterEvent((s, e) =>
{
var eventArgs = e as StringEventArgs;
if (eventArgs?.Data == SettingsKey.feedrate_ratio)
{
feedRateRatio = this.printer.Settings.GetValue<double>(SettingsKey.feedrate_ratio);
}
}, ref unregisterEvents);
}
private void ExtruderIndexSet(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
double extruderBeingSet = 0;
if (GCodeFile.GetFirstNumberAfter("T", foundStringEventArgs.LineToCheck, ref extruderBeingSet))
{
currentlyActiveExtruderIndex = (int)extruderBeingSet;
}
}
[Flags]
public enum Axis { X = 1, Y = 2, Z = 4, E = 8, XYZ = (X | Y | Z) }
public double ActualBedTemperature
{
get
{
return actualBedTemperature;
}
}
public int BaudRate
{
get
{
int baudRate = 250000;
if (this.printer.Settings != null)
{
try
{
if (!string.IsNullOrEmpty(printer.Settings.GetValue(SettingsKey.baud_rate)))
{
baudRate = Convert.ToInt32(printer.Settings.GetValue(SettingsKey.baud_rate));
}
}
catch
{
}
}
return baudRate;
}
}
public CommunicationStates CommunicationState
{
get
{
return communicationState;
}
set
{
switch (value)
{
case CommunicationStates.AttemptingToConnect:
#if DEBUG
if (serialPort == null)
{
throw new Exception("The serial port should be constructed prior to setting this or we can fail our connection on a write before it has a chance to be created.");
}
#endif
break;
case CommunicationStates.Connected:
SendLineToPrinterNow("M115");
ReadPosition();
ApplicationController.Instance.PrintingItemName = "";
break;
case CommunicationStates.ConnectionLost:
case CommunicationStates.Disconnected:
TurnOffBedAndExtruders();
for (int hotendIndex = 0; hotendIndex < MAX_EXTRUDERS; hotendIndex++)
{
actualHotendTemperature[hotendIndex] = 0;
OnHotendTemperatureRead(new TemperatureEventArgs(hotendIndex, GetActualHotendTemperature(hotendIndex)));
}
actualBedTemperature = 0;
OnBedTemperatureRead(new TemperatureEventArgs(0, ActualBedTemperature));
break;
}
if (communicationState != value)
{
CommunicationUnconditionalToPrinter.CallEvents(this, new StringEventArgs("Communication State: {0}\n".FormatWith(value.ToString())));
switch (communicationState)
{
// if it was printing
case CommunicationStates.PrintingFromSd:
case CommunicationStates.Printing:
{
// and is changing to paused
if (value == CommunicationStates.Paused)
{
if (communicationState == CommunicationStates.Printing)
{
PrePauseCommunicationState = CommunicationStates.Printing;
}
else
{
PrePauseCommunicationState = CommunicationStates.PrintingFromSd;
}
timeSinceStartedPrint.Stop();
}
else if (value == CommunicationStates.FinishedPrint)
{
if (activePrintTask != null)
{
TimeSpan printTimeSpan = DateTime.Now.Subtract(activePrintTask.PrintStart);
activePrintTask.PrintEnd = DateTime.Now;
activePrintTask.PercentDone = 100;
activePrintTask.PrintComplete = true;
activePrintTask.Commit();
ApplicationController.Instance.PrintingItemName = "";
}
// Set this early as we always want our functions to know the state we are in.
communicationState = value;
timeSinceStartedPrint.Stop();
PrintFinished.CallEvents(this, new PrintItemWrapperEventArgs(printer.Bed.printItem));
// clear single use setting on print completion
foreach (var keyValue in printer.Settings.BaseLayer)
{
string currentValue = printer.Settings.GetValue(keyValue.Key);
bool valueIsClear = currentValue == "0" | currentValue == "";
SliceSettingData data = SliceSettingsOrganizer.Instance.GetSettingsData(keyValue.Key);
if (data?.ResetAtEndOfPrint == true && !valueIsClear)
{
printer.Settings.ClearValue(keyValue.Key);
}
}
}
else
{
timeSinceStartedPrint.Stop();
timeSinceStartedPrint.Reset();
}
}
break;
// was paused
case CommunicationStates.Paused:
{
// changing to printing
if (value == CommunicationStates.Printing)
{
timeSinceStartedPrint.Start();
}
}
break;
default:
if (!timeSinceStartedPrint.IsRunning
&& value == CommunicationStates.Printing)
{
// If we are just starting to print (we know we were not paused or it would have stopped above)
timeSinceStartedPrint.Restart();
}
break;
}
communicationState = value;
OnCommunicationStateChanged(null);
}
}
}
public string ComPort => printer.Settings?.Helpers.ComPort();
public string DriverType => (this.ComPort == "Emulator") ? "Emulator" : printer.Settings?.GetValue("driver_type");
public bool AtxPowerEnabled
{
get
{
return atxPowerIsOn;
}
set
{
if (value)
{
SendLineToPrinterNow("M80");
}
else
{
SendLineToPrinterNow("M81");
}
}
}
public string ConnectionFailureMessage { get { return connectionFailureMessage; } }
public Vector3 CurrentDestination { get { return currentDestination.position; } }
public int CurrentlyPrintingLayer
{
get
{
if (gCodeFileStream0 != null)
{
int instructionIndex = gCodeFileStream0.LineIndex - backupAmount;
return loadedGCode.GetLayerIndex(instructionIndex);
}
return 0;
}
}
public string DeviceCode { get; private set; }
public bool Disconnecting
{
get
{
return CommunicationState == CommunicationStates.Disconnecting;
}
}
public int FanSpeed0To255
{
get { return fanSpeed; }
set
{
fanSpeed = Math.Max(0, Math.Min(255, value));
OnFanSpeedSet(null);
if (PrinterIsConnected)
{
SendLineToPrinterNow("M106 S{0}".FormatWith(fanSpeed));
}
}
}
public FirmwareTypes FirmwareType { get; private set; } = FirmwareTypes.Unknown;
public string FirmwareVersion { get; private set; }
public Vector3 LastReportedPosition { get { return lastReportedPosition.position; } }
public bool MonitorPrinterTemperature { get; set; }
public double PercentComplete
{
get
{
if (CommunicationState == CommunicationStates.PrintingFromSd
|| (communicationState == CommunicationStates.Paused && PrePauseCommunicationState == CommunicationStates.PrintingFromSd))
{
if (totalSdBytes > 0)
{
return currentSdBytes / totalSdBytes * 100;
}
return 0;
}
if (PrintIsFinished && !PrinterIsPaused)
{
return 100.0;
}
else if (NumberOfLinesInCurrentPrint > 0
&& loadedGCode != null
&& gCodeFileStream0 != null)
{
return loadedGCode.PercentComplete(gCodeFileStream0.LineIndex);
}
else
{
return 0.0;
}
}
}
public string PrinterConnectionStatus
{
get
{
switch (CommunicationState)
{
case CommunicationStates.Disconnected:
return "Not Connected".Localize();
case CommunicationStates.Disconnecting:
return "Disconnecting".Localize();
case CommunicationStates.AttemptingToConnect:
return "Connecting".Localize() + "...";
case CommunicationStates.ConnectionLost:
return "Connection Lost".Localize();
case CommunicationStates.FailedToConnect:
return "Unable to Connect".Localize();
case CommunicationStates.Connected:
return "Connected".Localize();
case CommunicationStates.PreparingToPrint:
return "Preparing To Print".Localize();
case CommunicationStates.Printing:
switch (DetailedPrintingState)
{
case DetailedPrintingState.HomingAxis:
return "Homing".Localize();
case DetailedPrintingState.HeatingBed:
return "Waiting for Bed to Heat to".Localize() + $" {TargetBedTemperature}°";
case DetailedPrintingState.HeatingExtruder:
return "Waiting for Extruder to Heat to".Localize() + $" {GetTargetHotendTemperature(0)}°";
case DetailedPrintingState.Printing:
default:
return "Printing".Localize();
}
case CommunicationStates.PrintingFromSd:
return "Printing From SD Card".Localize();
case CommunicationStates.Paused:
return "Paused".Localize();
case CommunicationStates.FinishedPrint:
return "Finished Print".Localize();
default:
throw new NotImplementedException("Make sure every status returns the correct connected state.");
}
}
}
public bool PrinterIsConnected
{
get
{
switch (CommunicationState)
{
case CommunicationStates.Disconnected:
case CommunicationStates.AttemptingToConnect:
case CommunicationStates.ConnectionLost:
case CommunicationStates.FailedToConnect:
return false;
case CommunicationStates.Disconnecting:
case CommunicationStates.Connected:
case CommunicationStates.PreparingToPrint:
case CommunicationStates.Printing:
case CommunicationStates.PrintingFromSd:
case CommunicationStates.Paused:
case CommunicationStates.FinishedPrint:
return true;
default:
throw new NotImplementedException("Make sure every status returns the correct connected state.");
}
}
}
public bool PrinterIsPaused
{
get
{
return CommunicationState == CommunicationStates.Paused;
}
}
public bool PrinterIsPrinting
{
get
{
switch (CommunicationState)
{
case CommunicationStates.Disconnected:
case CommunicationStates.Disconnecting:
case CommunicationStates.AttemptingToConnect:
case CommunicationStates.ConnectionLost:
case CommunicationStates.FailedToConnect:
case CommunicationStates.Connected:
case CommunicationStates.PreparingToPrint:
case CommunicationStates.Paused:
case CommunicationStates.FinishedPrint:
return false;
case CommunicationStates.Printing:
case CommunicationStates.PrintingFromSd:
return true;
default:
throw new NotImplementedException("Make sure every status returns the correct connected state.");
}
}
}
public DetailedPrintingState DetailedPrintingState
{
get
{
return printingStatePrivate;
}
set
{
if (printingStatePrivate != value)
{
printingStatePrivate = value;
PrintingStateChanged.CallEvents(this, null);
}
}
}
public bool PrintIsActive
{
get
{
switch (CommunicationState)
{
case CommunicationStates.Disconnected:
case CommunicationStates.Disconnecting:
case CommunicationStates.AttemptingToConnect:
case CommunicationStates.ConnectionLost:
case CommunicationStates.FailedToConnect:
case CommunicationStates.Connected:
case CommunicationStates.FinishedPrint:
return false;
case CommunicationStates.Printing:
case CommunicationStates.PrintingFromSd:
case CommunicationStates.PreparingToPrint:
case CommunicationStates.Paused:
return true;
default:
throw new NotImplementedException("Make sure every status returns the correct connected state.");
}
}
}
public bool PrintIsFinished
{
get
{
return CommunicationState == CommunicationStates.FinishedPrint;
}
}
public string PrintJobName { get; private set; } = null;
public bool PrintWasCanceled { get; set; } = false;
public double RatioIntoCurrentLayer
{
get
{
if (gCodeFileStream0 == null)
{
return 0;
}
int instructionIndex = gCodeFileStream0.LineIndex - backupAmount;
return loadedGCode.Ratio0to1IntoContainedLayer(instructionIndex);
}
}
public int SecondsPrinted
{
get
{
if (PrinterIsPrinting || PrinterIsPaused || PrintIsFinished)
{
return (int)(timeSinceStartedPrint.ElapsedMilliseconds / 1000);
}
return 0;
}
}
public double TargetBedTemperature
{
get
{
return targetBedTemperature;
}
set
{
if (targetBedTemperature != value)
{
targetBedTemperature = value;
OnBedTemperatureSet(new TemperatureEventArgs(0, TargetBedTemperature));
if (PrinterIsConnected)
{
SendLineToPrinterNow("M140 S{0}".FormatWith(targetBedTemperature));
}
}
}
}
public int TotalLayersInPrint
{
get
{
try
{
return loadedGCode.LayerCount;
}
catch (Exception)
{
return -1;
}
}
}
public int TotalSecondsInPrint
{
get
{
if (loadedGCode.LineCount > 0)
{
if (feedRateRatio != 0)
{
return (int)(loadedGCode.TotalSecondsInPrint / feedRateRatio);
}
return (int)(loadedGCode.TotalSecondsInPrint);
}
return 0;
}
}
// HACK: PrinterConnection must be revised to take a constructor that receives and stores a reference to its parent PrinterConfig - this
private PrinterConfig printer { get; set; }
private int NumberOfLinesInCurrentPrint => loadedGCode.LineCount;
/// <summary>
/// Abort an ongoing attempt to establish communication with a printer due to the specified problem. This is a specialized
/// version of the functionality that's previously been in .Disable but focused specifically on the task of aborting an
/// ongoing connection. Ideally we should unify all abort invocations to use this implementation rather than the mix
/// of occasional OnConnectionFailed calls, .Disable and .stopTryingToConnect
/// </summary>
/// <param name="abortReason">The concise message which will be used to describe the connection failure</param>
/// <param name="shutdownReadLoop">Shutdown/join the readFromPrinterThread</param>
public void AbortConnectionAttempt(string abortReason, bool shutdownReadLoop = true)
{
// Set .Disconnecting to allow the read loop to exit gracefully before a forced thread join (and extended timeout)
CommunicationState = CommunicationStates.Disconnecting;
// Shutdown the connectionAttempt thread
if (connectThread != null)
{
connectThread.Join(JoinThreadTimeoutMs); //Halt connection thread
}
// Shutdown the readFromPrinter thread
if (shutdownReadLoop)
{
ReadThread.Join();
}
// Shutdown the serial port
if (serialPort != null)
{
// Close and dispose the serial port
serialPort.Close();
serialPort.Dispose();
serialPort = null;
}
// Set the final communication state
CommunicationState = CommunicationStates.Disconnected;
// Set the connection failure message and call OnConnectionFailed
connectionFailureMessage = abortReason;
// Notify
OnConnectionFailed(null);
}
public void BedTemperatureWasWritenToPrinter(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
string[] splitOnS = foundStringEventArgs.LineToCheck.Split('S');
if (splitOnS.Length == 2)
{
string temp = splitOnS[1];
try
{
double tempBeingSet = double.Parse(temp);
if (TargetBedTemperature != tempBeingSet)
{
// we set the private variable so that we don't get the callbacks called and get in a loop of setting the temp
targetBedTemperature = tempBeingSet;
OnBedTemperatureSet(new TemperatureEventArgs(0, TargetBedTemperature));
}
}
catch (Exception)
{
}
}
}
public void Connect(bool showHelpIfNoPort = false)
{
if (printer.Settings != null)
{
// Start the process of requesting permission and exit if permission is not currently granted
if (!printer.Settings.GetValue<bool>(SettingsKey.enable_network_printing)
&& !FrostedSerialPort.EnsureDeviceAccess())
{
CommunicationState = CommunicationStates.FailedToConnect;
return;
}
TerminalLog.Clear();
//Attempt connecting to a specific printer
this.stopTryingToConnect = false;
this.FirmwareType = FirmwareTypes.Unknown;
firmwareUriGcodeSend = false;
// On Android, there will never be more than one serial port available for us to connect to. Override the current .ComPort value to account for
// this aspect to ensure the validation logic that verifies port availability/in use status can proceed without additional workarounds for Android
#if __ANDROID__
string currentPortName = FrostedSerialPort.GetPortNames().FirstOrDefault();
if (!string.IsNullOrEmpty(currentPortName))
{
// TODO: Ensure that this does *not* cause a write to the settings file and should be an in memory update only
printer.Settings?.Helpers.SetComPort(currentPortName);
}
#endif
if (SerialPortIsAvailable(this.ComPort))
{
//Create a timed callback to determine whether connection succeeded
Timer connectionTimer = new Timer(new TimerCallback(ConnectionCallbackTimer));
connectionTimer.Change(100, 0);
//Create and start connection thread
connectThread = new Thread(Connect_Thread);
connectThread.Name = "Connect To Printer";
connectThread.IsBackground = true;
connectThread.Start();
}
else
{
Debug.WriteLine("Connection failed: {0}".FormatWith(this.ComPort));
connectionFailureMessage = string.Format(
"{0} is not available".Localize(),
this.ComPort);
OnConnectionFailed(null);
#if !__ANDROID__
// Only pop up the com port helper if the USER actually CLICKED the connect button.
if (showHelpIfNoPort)
{
WizardWindow.ShowComPortSetup(printer);
}
#endif
}
}
}
public void DeleteFileFromSdCard(string fileName)
{
// Register to detect the file deleted confirmation.
// This should have worked without this by getting the normal 'ok' on the next line. But the ok is not on its own line.
ReadLineStartCallBacks.AddCallbackToKey("File deleted:", FileDeleteConfirmed);
// and send the line to delete the file
SendLineToPrinterNow("M30 {0}".FormatWith(fileName.ToLower()));
}
public void Disable()
{
if (PrinterIsConnected)
{
// Make sure we send this without waiting for the printer to respond. We want to try and turn off the heaters.
// It may be possible in the future to make this go into the printer queue for assured sending but it means
// the program has to be smart about closing an able to wait until the printer has agreed that it shut off
// the motors and heaters (a good idea and something for the future).
ForceImmediateWrites = true;
ReleaseMotors();
TurnOffBedAndExtruders();
FanSpeed0To255 = 0;
ForceImmediateWrites = false;
CommunicationState = CommunicationStates.Disconnecting;
ReadThread.Join();
if (serialPort != null)
{
serialPort.Close();
serialPort.Dispose();
}
serialPort = null;
CommunicationState = CommunicationStates.Disconnected;
}
else
{
//Need to reset UI - even if manual disconnect
TurnOffBedAndExtruders();
FanSpeed0To255 = 0;
}
OnEnabledChanged(null);
}
public void HotendTemperatureWasWritenToPrinter(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
double tempBeingSet = 0;
if (GCodeFile.GetFirstNumberAfter("S", foundStringEventArgs.LineToCheck, ref tempBeingSet))
{
double exturderIndex = 0;
if (GCodeFile.GetFirstNumberAfter("T", foundStringEventArgs.LineToCheck, ref exturderIndex))
{
// we set the private variable so that we don't get the callbacks called and get in a loop of setting the temp
int hotendIndex0Based = Math.Min((int)exturderIndex, MAX_EXTRUDERS - 1);
targetHotendTemperature[hotendIndex0Based] = tempBeingSet;
}
else
{
// we set the private variable so that we don't get the callbacks called and get in a loop of setting the temp
targetHotendTemperature[currentlyActiveExtruderIndex] = tempBeingSet;
}
OnHotendTemperatureSet(new TemperatureEventArgs((int)exturderIndex, tempBeingSet));
}
}
public void FanOffWasWritenToPrinter(object sender, EventArgs e)
{
fanSpeed = 0;
OnFanSpeedSet(null);
}
public void FanSpeedWasWritenToPrinter(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
string[] splitOnS = foundStringEventArgs.LineToCheck.Split('S');
if (splitOnS.Length != 2)
{
// when there is no explicit S value the assumption is 255
splitOnS = "M106 S255".Split('S');
}
if (splitOnS.Length == 2)
{
string fanSpeedString = splitOnS[1];
try
{
int fanSpeedBeingSet = int.Parse(fanSpeedString);
if (FanSpeed0To255 != fanSpeedBeingSet)
{
fanSpeed = fanSpeedBeingSet;
OnFanSpeedSet(null);
}
}
catch (Exception)
{
}
}
}
public void FoundStart(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
foundStringEventArgs.SendToDelegateFunctions = false;
}
public double GetActualHotendTemperature(int hotendIndex0Based)
{
hotendIndex0Based = Math.Min(hotendIndex0Based, MAX_EXTRUDERS - 1);
return actualHotendTemperature[hotendIndex0Based];
}
public double GetTargetHotendTemperature(int hotendIndex0Based)
{
hotendIndex0Based = Math.Min(hotendIndex0Based, MAX_EXTRUDERS - 1);
return targetHotendTemperature[hotendIndex0Based];
}
public void HaltConnectionThread()
{
this.stopTryingToConnect = true;
}
public void HomeAxis(Axis axis)
{
string command = "G28";
// If we are homing everything we don't need to add any details
if (!axis.HasFlag(Axis.XYZ))
{
if ((axis & Axis.X) == Axis.X)
{
command += " X0";
}
if ((axis & Axis.Y) == Axis.Y)
{
command += " Y0";
}
if ((axis & Axis.Z) == Axis.Z)
{
command += " Z0";
}
}
SendLineToPrinterNow(command);
ReadPosition();
}
public void MoveAbsolute(Axis axis, double axisPositionMm, double feedRateMmPerMinute)
{
SetMovementToAbsolute();
SendLineToPrinterNow("G1 {0}{1:0.###} F{2}".FormatWith(axis, axisPositionMm, feedRateMmPerMinute));
}
public void MoveAbsolute(Vector3 position, double feedRateMmPerMinute)
{
SetMovementToAbsolute();
SendLineToPrinterNow("G1 X{0:0.###}Y{1:0.###}Z{2:0.###} F{3}".FormatWith(position.x, position.y, position.z, feedRateMmPerMinute));
}
public void MoveExtruderRelative(double moveAmountMm, double feedRateMmPerMinute, int extruderNumber = 0)
{
if (moveAmountMm != 0)
{
// TODO: Long term we need to track the active extruder and make requiresToolChange be driven by the extruder you're actually on
bool requiresToolChange = extruderNumber != 0;
SetMovementToRelative();
if (requiresToolChange)
{
SendLineToPrinterNow("T{0}".FormatWith(extruderNumber)); //Set active extruder
}
SendLineToPrinterNow("G1 E{0:0.###} F{1}".FormatWith(moveAmountMm, feedRateMmPerMinute));
if (requiresToolChange)
{
SendLineToPrinterNow("T0"); //Reset back to extruder one
}
SetMovementToAbsolute();
}
}
public void MoveRelative(Axis axis, double moveAmountMm, double feedRateMmPerMinute)
{
if (moveAmountMm != 0)
{
SetMovementToRelative();
SendLineToPrinterNow("G1 {0}{1:0.###} F{2}".FormatWith(axis, moveAmountMm, feedRateMmPerMinute));
SetMovementToAbsolute();
}
}
public void OnCommunicationStateChanged(EventArgs e)
{
// Call global even
AnyCommunicationStateChanged.CallEvents(this, e);
// Call instance event
CommunicationStateChanged.CallEvents(this, e);
PrintingStateChanged.CallEvents(this, null);
#if __ANDROID__
//Path to the printer output file
string pathToPrintOutputFile = Path.Combine(ApplicationDataStorage.Instance.PublicDataStoragePath, "print_output.txt");
if (CommunicationState == CommunicationStates.FinishedPrint)
{
//Only write to the text file if file exists
if (File.Exists(pathToPrintOutputFile))
{
Task.Run(() =>
{
File.WriteAllLines(pathToPrintOutputFile, ApplicationController.Instance.ActivePrinter.Connection.TerminalLog.PrinterLines);
});
}
}
#endif
}
public void OnConnectionFailed(EventArgs e)
{
ConnectionFailed.CallEvents(this, e);
CommunicationState = CommunicationStates.FailedToConnect;
OnEnabledChanged(e);
}
public void OnIdle()
{
if (PrinterIsConnected && ReadThread.NumRunning == 0)
{
ReadThread.Start(this);
}
}
public void PrinterRequestsResend(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
if (foundStringEventArgs != null
&& !string.IsNullOrEmpty(foundStringEventArgs.LineToCheck))
{
string line = foundStringEventArgs.LineToCheck;
// marlin and repetier send a : before the number and then and ok
if (!GCodeFile.GetFirstNumberAfter(":", line, ref currentLineIndexToSend))
{
if (currentLineIndexToSend == allCheckSumLinesSent.Count)
{
// asking for the next line don't do anything, conitue with sending next instruction
return;
}
// smoothie sends an N before the number and no ok
if (GCodeFile.GetFirstNumberAfter("N", line, ref currentLineIndexToSend))
{
// clear waiting for ok because smoothie will not send it
PrintingCanContinue(null, null);
}
}
if (currentLineIndexToSend == allCheckSumLinesSent.Count)
{
// asking for the next line don't do anything, conitue with sending next instruction
return;
}
if (currentLineIndexToSend >= allCheckSumLinesSent.Count
|| currentLineIndexToSend == 1)
{
SendLineToPrinterNow("M110 N1");
allCheckSumLinesSent.SetStartingIndex(1);
waitingForPosition.Reset();
PositionReadQueued = false;
}
}
}
private bool haveReportedError = false;
public event EventHandler ErrorReported;
public void PrinterReportsError(object sender, EventArgs e)
{
if (!haveReportedError)
{
haveReportedError = true;
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
if (foundStringEventArgs != null)
{
ErrorReported?.Invoke(null, foundStringEventArgs);
}
}
}
public void PrinterStatesExtensions(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
if (foundStringEventArgs != null)
{
if (foundStringEventArgs.LineToCheck.Contains("URI_GCODE_SEND"))
{
firmwareUriGcodeSend = true;
}
}
}
public void PrinterStatesFirmware(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
string firmwareName = "";
if (GCodeFile.GetFirstStringAfter("FIRMWARE_NAME:", foundStringEventArgs.LineToCheck, " ", ref firmwareName))
{
firmwareName = firmwareName.ToLower();
if (firmwareName.Contains("repetier"))
{
this.FirmwareType = FirmwareTypes.Repetier;
}
else if (firmwareName.Contains("marlin"))
{
this.FirmwareType = FirmwareTypes.Marlin;
}
else if (firmwareName.Contains("sprinter"))
{
this.FirmwareType = FirmwareTypes.Sprinter;
}
}
string firmwareVersionReported = "";
if (GCodeFile.GetFirstStringAfter("MACHINE_TYPE:", foundStringEventArgs.LineToCheck, " EXTRUDER_COUNT", ref firmwareVersionReported))
{
char splitChar = '^';
if (firmwareVersionReported.Contains(splitChar))
{
string[] split = firmwareVersionReported.Split(splitChar);
if (split.Count() == 2)
{
DeviceCode = split[0];
firmwareVersionReported = split[1];
}
}
//Firmware version was detected and is different
if (firmwareVersionReported != "" && FirmwareVersion != firmwareVersionReported)
{
FirmwareVersion = firmwareVersionReported;
OnFirmwareVersionRead(null);
}
}
}
// this is to make it misbehave
//int okCount = 1;
public void PrintingCanContinue(object sender, EventArgs e)
{
//if ((okCount++ % 67) != 0)
{
timeHaveBeenWaitingForOK.Stop();
}
}
public void ArduinoDtrReset()
{
// TODO: Ideally we would shutdown the printer connection when this method is called and we're connected. The
// current approach results in unpredictable behavior if the caller fails to close the connection
if (serialPort == null && this.printer.Settings != null)
{
IFrostedSerialPort resetSerialPort = FrostedSerialPortFactory.GetAppropriateFactory(this.DriverType).Create(this.ComPort);
resetSerialPort.Open();
Thread.Sleep(500);
ToggleHighLowHigh(resetSerialPort);
resetSerialPort.Close();
}
}
public void ReadFromPrinter(ReadThread readThreadHolder)
{
string dataLastRead = string.Empty;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
timeSinceLastReadAnything.Restart();
// we want this while loop to be as fast as possible. Don't allow any significant work to happen in here
while (CommunicationState == CommunicationStates.AttemptingToConnect
|| (PrinterIsConnected && serialPort != null && serialPort.IsOpen && !Disconnecting && readThreadHolder.IsCurrentThread()))
{
if ((PrinterIsConnected
|| this.communicationState == CommunicationStates.AttemptingToConnect)
&& CommunicationState != CommunicationStates.PrintingFromSd)
{
TryWriteNextLineFromGCodeFile();
}
try
{
while (serialPort != null
&& serialPort.BytesToRead > 0
&& readThreadHolder.IsCurrentThread())
{
lock (locker)
{
string allDataRead = serialPort.ReadExisting();
//Debug.Write("r: " + allDataRead);
allDataRead = allDataRead.Replace("\r\n", "\n");
allDataRead = allDataRead.Replace('\r', '\n');
dataLastRead += allDataRead;
do
{
int returnPosition = dataLastRead.IndexOf('\n');
// Abort if we're AttemptingToConnect, no newline was found in the accumulator string and there's too many non-ascii chars
if (this.communicationState == CommunicationStates.AttemptingToConnect && returnPosition < 0)
{
int totalInvalid = dataLastRead.Count(c => c == '?');
if (totalInvalid > MAX_INVALID_CONNECTION_CHARS)
{
AbortConnectionAttempt("Invalid printer response".Localize(), false);
}
}
if (returnPosition < 0)
{
// there is no return keep getting characters
break;
}
if (dataLastRead.Length > 0)
{
if (lastLineRead.StartsWith("ok"))
{
timeSinceRecievedOk.Restart();
}
lastLineRead = dataLastRead.Substring(0, returnPosition);
lastLineRead = ProcessReadRegEx(lastLineRead);
dataLastRead = dataLastRead.Substring(returnPosition + 1);
// process this command
{
StringEventArgs currentEvent = new StringEventArgs(lastLineRead);
if (PrinterIsPrinting)
{
CommunicationUnconditionalFromPrinter.CallEvents(this, new StringEventArgs("{0} [{1:0.000}]\n".FormatWith(lastLineRead, timeSinceStartedPrint.Elapsed.TotalSeconds)));
}
else
{
CommunicationUnconditionalFromPrinter.CallEvents(this, currentEvent);
}
FoundStringEventArgs foundResponse = new FoundStringEventArgs(currentEvent.Data);
ReadLineStartCallBacks.CheckForKeys(foundResponse);
ReadLineContainsCallBacks.CheckForKeys(foundResponse);
if (foundResponse.SendToDelegateFunctions)
{
ReadLine.CallEvents(this, currentEvent);
}
}
// If we've encountered a newline character and we're still in .AttemptingToConnect
if (CommunicationState == CommunicationStates.AttemptingToConnect)
{
// TODO: This is an initial proof of concept for validating the printer response after DTR. More work is
// needed to test this technique across existing hardware and/or edge cases where this simple approach
// (initial line having more than 3 non-ASCII characters) may not be adequate or appropriate.
// TODO: Revise the INVALID char count to an agreed upon threshold
string[] segments = lastLineRead.Split('?');
if (segments.Length <= MAX_INVALID_CONNECTION_CHARS)
{
CommunicationState = CommunicationStates.Connected;
TurnOffBedAndExtruders(); // make sure our ui and the printer agree and that the printer is in a known state (not heating).
haveReportedError = false;
// now send any command that initialize this printer
ClearQueuedGCode();
string connectGCode = printer.Settings.GetValue(SettingsKey.connect_gcode);
SendLineToPrinterNow(connectGCode);
// Call global event
AnyConnectionSucceeded.CallEvents(this, null);
// Call instance event
ConnectionSucceeded.CallEvents(this, null);
}
else
{
// Force port shutdown and cleanup
AbortConnectionAttempt("Invalid printer response".Localize(), false);
}
}
}
} while (true);
}
timeSinceLastReadAnything.Restart();
}
if (PrinterIsPrinting)
{
Thread.Sleep(0);
}
else
{
Thread.Sleep(1);
}
}
catch (TimeoutException)
{
}
catch (IOException e2)
{
TerminalLog.WriteLine("Exception:" + e2.Message);
OnConnectionFailed(null);
}
catch (InvalidOperationException ex)
{
TerminalLog.WriteLine("Exception:" + ex.Message);
// this happens when the serial port closes after we check and before we read it.
OnConnectionFailed(null);
}
catch (UnauthorizedAccessException e3)
{
TerminalLog.WriteLine("Exception:" + e3.Message);
OnConnectionFailed(null);
}
catch (Exception)
{
}
}
}
public void ReadPosition(bool forceToTopOfQueue = false)
{
SendLineToPrinterNow("M114", forceToTopOfQueue);
PositionReadQueued = true;
}
public void ReadSdProgress(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
if (foundStringEventArgs != null)
{
string sdProgressString = foundStringEventArgs.LineToCheck.Substring("Sd printing byte ".Length);
string[] values = sdProgressString.Split('/');
currentSdBytes = long.Parse(values[0]);
totalSdBytes = long.Parse(values[1]);
}
// We read it so we are no longer waiting
timeWaitingForSdProgress.Stop();
}
public void ReadTargetPositions(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
string lineToParse = foundStringEventArgs.LineToCheck;
GCodeFile.GetFirstNumberAfter("X:", lineToParse, ref lastReportedPosition.position.x);
GCodeFile.GetFirstNumberAfter("Y:", lineToParse, ref lastReportedPosition.position.y);
GCodeFile.GetFirstNumberAfter("Z:", lineToParse, ref lastReportedPosition.position.z);
GCodeFile.GetFirstNumberAfter("E:", lineToParse, ref lastReportedPosition.extrusion);
//if (currentDestination != positionRead)
{
currentDestination = lastReportedPosition;
DestinationChanged.CallEvents(this, null);
if (totalGCodeStream != null)
{
totalGCodeStream.SetPrinterPosition(currentDestination);
}
}
PositionRead.CallEvents(this, null);
waitingForPosition.Reset();
PositionReadQueued = false;
}
public static void ParseTemperatureString(string temperatureString,
double[] actualHotendTemperature, Action<TemperatureEventArgs> hotendTemperatureChange,
ref double actualBedTemperature, Action<TemperatureEventArgs> bedTemperatureChanged)
{
{
double readHotendTemp = 0;
if (GCodeFile.GetFirstNumberAfter("T:", temperatureString, ref readHotendTemp))
{
if (actualHotendTemperature[0] != readHotendTemp)
{
actualHotendTemperature[0] = readHotendTemp;
hotendTemperatureChange?.Invoke(new TemperatureEventArgs(0, readHotendTemp));
}
}
for (int hotendIndex = 0; hotendIndex < MAX_EXTRUDERS; hotendIndex++)
{
string multiExtruderCheck = "T{0}:".FormatWith(hotendIndex);
if (GCodeFile.GetFirstNumberAfter(multiExtruderCheck, temperatureString, ref readHotendTemp))
{
if (actualHotendTemperature[hotendIndex] != readHotendTemp)
{
actualHotendTemperature[hotendIndex] = readHotendTemp;
hotendTemperatureChange?.Invoke(new TemperatureEventArgs(hotendIndex, readHotendTemp));
}
}
else
{
break;
}
}
}
{
double readBedTemp = 0;
if (GCodeFile.GetFirstNumberAfter("B:", temperatureString, ref readBedTemp))
{
if (actualBedTemperature != readBedTemp)
{
actualBedTemperature = readBedTemp;
bedTemperatureChanged?.Invoke(new TemperatureEventArgs(0, readBedTemp));
}
}
}
}
public void ReadTemperatures(object sender, FoundStringEventArgs foundStringEventArgs)
{
ParseTemperatureString(foundStringEventArgs.LineToCheck, actualHotendTemperature, OnHotendTemperatureRead,
ref actualBedTemperature, OnBedTemperatureRead);
}
public void RebootBoard()
{
try
{
if (printer.Settings.PrinterSelected)
{
// first make sure we are not printing if possible (cancel slicing)
if (serialPort != null) // we still have a serial port
{
Stop(false);
ClearQueuedGCode();
CommunicationState = CommunicationStates.Disconnecting;
ReadThread.Join();
ToggleHighLowHigh(serialPort);
if (serialPort != null)
{
serialPort.Close();
serialPort.Dispose();
}
serialPort = null;
// make sure we clear out the stream processors
CreateStreamProcessors(null, false);
CommunicationState = CommunicationStates.Disconnected;
// We were connected to a printer so try to reconnect
Connect();
}
else
{
// We reset the board while attempting to connect, so now we don't have a serial port.
// Create one and do the DTR to reset
var resetSerialPort = FrostedSerialPortFactory.GetAppropriateFactory(this.DriverType).Create(this.ComPort);
resetSerialPort.Open();
Thread.Sleep(500);
ToggleHighLowHigh(resetSerialPort);
resetSerialPort.Close();
// let the process know we canceled not ended normally.
CommunicationState = CommunicationStates.Disconnected;
}
}
}
catch (Exception)
{
}
}
private void ToggleHighLowHigh(IFrostedSerialPort serialPort)
{
serialPort.RtsEnable = true;
serialPort.DtrEnable = true;
Thread.Sleep(100);
serialPort.RtsEnable = false;
serialPort.DtrEnable = false;
Thread.Sleep(100);
serialPort.RtsEnable = true;
serialPort.DtrEnable = true;
}
public void ReleaseMotors()
{
SendLineToPrinterNow("M84");
}
public void RequestPause()
{
if (PrinterIsPrinting)
{
if (CommunicationState == CommunicationStates.PrintingFromSd)
{
CommunicationState = CommunicationStates.Paused;
SendLineToPrinterNow("M25"); // : Pause SD print
return;
}
pauseHandlingStream1.DoPause(PauseHandlingStream.PauseReason.UserRequested);
}
}
public void ResetToReadyState()
{
if (CommunicationState == CommunicationStates.FinishedPrint)
{
CommunicationState = CommunicationStates.Connected;
}
else
{
throw new Exception("You should only reset after a print has finished.");
}
}
public void Resume()
{
if (PrinterIsPaused)
{
if (PrePauseCommunicationState == CommunicationStates.PrintingFromSd)
{
CommunicationState = CommunicationStates.PrintingFromSd;
SendLineToPrinterNow("M24"); // Start/resume SD print
}
else
{
pauseHandlingStream1.Resume();
CommunicationState = CommunicationStates.Printing;
}
}
}
public void SendLinesToPrinterNow(string[] linesToWrite)
{
if (PrinterIsPrinting && CommunicationState != CommunicationStates.PrintingFromSd)
{
for (int i = linesToWrite.Length - 1; i >= 0; i--)
{
string line = linesToWrite[i].Trim();
if (line.Length > 0)
{
SendLineToPrinterNow(line);
}
}
}
else
{
for (int i = 0; i < linesToWrite.Length; i++)
{
string line = linesToWrite[i].Trim();
if (line.Length > 0)
{
SendLineToPrinterNow(line);
}
}
}
}
public void SendLineToPrinterNow(string lineToWrite, bool forceTopOfQueue = false)
{
lock (locker)
{
if (lineToWrite.Contains("\\n"))
{
lineToWrite = lineToWrite.Replace("\\n", "\n");
}
//Check line for line breaks, split and process separate if necessary
if (lineToWrite.Contains("\n"))
{
string[] linesToWrite = lineToWrite.Split(new string[] { "\n" }, StringSplitOptions.None);
SendLinesToPrinterNow(linesToWrite);
return;
}
if (CommunicationState == CommunicationStates.PrintingFromSd
|| ForceImmediateWrites)
{
lineToWrite = lineToWrite.Split(';')[0].Trim();
if (lineToWrite.Trim().Length > 0)
{
// sometimes we need to send code without buffering (like when we are closing the program).
WriteRawToPrinter(lineToWrite + "\n", lineToWrite);
}
}
else
{
if (lineToWrite.Trim().Length > 0)
{
// insert the command into the printing queue at the head
InjectGCode(lineToWrite, forceTopOfQueue);
}
}
}
}
#region ProcessRead
static Regex getQuotedParts = new Regex(@"([""'])(\\?.)*?\1", RegexOptions.Compiled);
string read_regex = "";
private List<(Regex Regex, string Replacement)> ReadLineReplacements = new List<(Regex Regex, string Replacement)>();
private string ProcessReadRegEx(string lineBeingRead)
{
if (read_regex != printer.Settings.GetValue(SettingsKey.read_regex))
{
ReadLineReplacements.Clear();
string splitString = "\\n";
read_regex = printer.Settings.GetValue(SettingsKey.read_regex);
foreach (string regExLine in read_regex.Split(splitString.ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
{
var matches = getQuotedParts.Matches(regExLine);
if (matches.Count == 2)
{
var search = matches[0].Value.Substring(1, matches[0].Value.Length - 2);
var replace = matches[1].Value.Substring(1, matches[1].Value.Length - 2);
ReadLineReplacements.Add((new Regex(search, RegexOptions.Compiled), replace));
}
}
}
foreach (var item in ReadLineReplacements)
{
lineBeingRead = item.Regex.Replace(lineBeingRead, item.Replacement);
}
return lineBeingRead;
}
#endregion // ProcessRead
public bool SerialPortIsAvailable(string portName)
//Check is serial port is in the list of available serial ports
{
if (IsNetworkPrinting())
{
return true;
}
try
{
string[] portNames = FrostedSerialPort.GetPortNames();
return portNames.Any(x => string.Compare(x, portName, true) == 0);
}
catch (Exception)
{
return false;
}
}
public void SetMovementToAbsolute()
{
SendLineToPrinterNow("G90");
}
public void SetMovementToRelative()
{
SendLineToPrinterNow("G91");
}
public void SetTargetHotendTemperature(int hotendIndex0Based, double temperature, bool forceSend = false)
{
hotendIndex0Based = Math.Min(hotendIndex0Based, MAX_EXTRUDERS - 1);
if (targetHotendTemperature[hotendIndex0Based] != temperature
|| forceSend)
{
targetHotendTemperature[hotendIndex0Based] = temperature;
OnHotendTemperatureSet(new TemperatureEventArgs(hotendIndex0Based, temperature));
if (PrinterIsConnected)
{
SendLineToPrinterNow("M104 T{0} S{1}".FormatWith(hotendIndex0Based, targetHotendTemperature[hotendIndex0Based]));
}
}
}
public async void StartPrint(string gcodeFilename, PrintTask printTaskToUse = null)
{
if (!PrinterIsConnected || PrinterIsPrinting)
{
return;
}
haveReportedError = false;
PrintWasCanceled = false;
waitingForPosition.Reset();
PositionReadQueued = false;
ClearQueuedGCode();
activePrintTask = printTaskToUse;
await Task.Run(() =>
{
LoadGCodeToPrint(gcodeFilename);
});
DoneLoadingGCodeToPrint();
}
public bool StartSdCardPrint(string m23FileName)
{
if (!PrinterIsConnected
|| PrinterIsPrinting
|| string.IsNullOrEmpty(m23FileName))
{
return false;
}
currentSdBytes = 0;
ClearQueuedGCode();
CommunicationState = CommunicationStates.PrintingFromSd;
SendLineToPrinterNow($"M23 {m23FileName.ToLower()}"); // Select SD File
SendLineToPrinterNow("M24"); // Start/resume SD print
ReadLineStartCallBacks.AddCallbackToKey("Done printing file", DonePrintingSdFile);
return true;
}
public void Stop(bool markPrintCanceled = true)
{
switch (CommunicationState)
{
case CommunicationStates.PrintingFromSd:
CancelSDCardPrint();
break;
case CommunicationStates.Printing:
CancelPrint(markPrintCanceled);
break;
case CommunicationStates.Paused:
if (PrePauseCommunicationState == CommunicationStates.PrintingFromSd)
{
CancelSDCardPrint();
CommunicationState = CommunicationStates.Connected;
}
else
{
CancelPrint(markPrintCanceled);
// We have to continue printing the end gcode, so we set this to Printing.
CommunicationState = CommunicationStates.Printing;
}
break;
case CommunicationStates.AttemptingToConnect:
CommunicationState = CommunicationStates.FailedToConnect;
connectThread.Join(JoinThreadTimeoutMs);
CommunicationState = CommunicationStates.Disconnecting;
ReadThread.Join();
if (serialPort != null)
{
serialPort.Close();
serialPort.Dispose();
serialPort = null;
}
CommunicationState = CommunicationStates.Disconnected;
break;
case CommunicationStates.PreparingToPrint:
SlicingQueue.Instance.CancelCurrentSlicing();
CommunicationState = CommunicationStates.Connected;
break;
}
}
private void CancelPrint(bool markPrintCanceled)
{
lock (locker)
{
// get rid of all the gcode we have left to print
ClearQueuedGCode();
string cancelGCode = printer.Settings.GetValue(SettingsKey.cancel_gcode);
if (cancelGCode.Trim() != "")
{
// add any gcode we want to print while canceling
InjectGCode(cancelGCode);
}
// let the process know we canceled not ended normally.
this.PrintWasCanceled = true;
if (markPrintCanceled
&& activePrintTask != null)
{
TimeSpan printTimeSpan = DateTime.Now.Subtract(activePrintTask.PrintStart);
activePrintTask.PrintEnd = DateTime.Now;
activePrintTask.PrintComplete = false;
activePrintTask.PrintingGCodeFileName = "";
activePrintTask.Commit();
}
// no matter what we no longer have a print task
activePrintTask = null;
}
}
private void CancelSDCardPrint()
{
lock (locker)
{
// get rid of all the gcode we have left to print
ClearQueuedGCode();
// let the process know we canceled not ended normally.
CommunicationState = CommunicationStates.Connected;
SendLineToPrinterNow("M25"); // : Pause SD print
SendLineToPrinterNow("M26"); // : Set SD position
// never leave the extruder and the bed hot
DonePrintingSdFile(this, null);
}
}
public void SuppressEcho(object sender, EventArgs e)
{
FoundStringEventArgs foundStringEventArgs = e as FoundStringEventArgs;
foundStringEventArgs.SendToDelegateFunctions = false;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr securityAttrs, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
private void AttemptToConnect(string serialPortName, int baudRate)
{
// make sure we don't have a left over print task
activePrintTask = null;
connectionFailureMessage = "Unknown Reason".Localize();
if (PrinterIsConnected)
{
#if DEBUG
throw new Exception("You can only connect when not currently connected.".Localize());
#else
return;
#endif
}
var portFactory = FrostedSerialPortFactory.GetAppropriateFactory(this.DriverType);
bool serialPortIsAvailable = portFactory.SerialPortIsAvailable(serialPortName);
bool serialPortIsAlreadyOpen = this.ComPort != "Emulator" &&
portFactory.SerialPortAlreadyOpen(serialPortName);
if (serialPortIsAvailable && !serialPortIsAlreadyOpen)
{
if (!PrinterIsConnected)
{
try
{
serialPort = portFactory.CreateAndOpen(serialPortName, baudRate, true);
#if __ANDROID__
ToggleHighLowHigh(serialPort);
#endif
// wait a bit of time to let the firmware start up
Thread.Sleep(500);
CommunicationState = CommunicationStates.AttemptingToConnect;
ReadThread.Join();
Console.WriteLine("ReadFromPrinter thread created.");
ReadThread.Start(this);
CreateStreamProcessors(null, false);
// We have to send a line because some printers (like old print-r-bots) do not send anything when connecting and there is no other way to know they are there.
SendLineToPrinterNow("M110 N1");
ClearQueuedGCode();
// We do not need to wait for the M105
PrintingCanContinue(null, null);
}
catch (System.ArgumentOutOfRangeException e)
{
TerminalLog.WriteLine("Exception:" + e.Message);
connectionFailureMessage = "Unsupported Baud Rate".Localize();
OnConnectionFailed(null);
}
catch (Exception ex)
{
TerminalLog.WriteLine("Exception:" + ex.Message);
OnConnectionFailed(null);
}
}
}
else
{
// If the serial port isn't available (i.e. the specified port name wasn't found in GetPortNames()) or the serial
// port is already opened in another instance or process, then report the connection problem back to the user
connectionFailureMessage = (serialPortIsAlreadyOpen ?
this.ComPort + " " + "in use".Localize() :
"Port not found".Localize());
OnConnectionFailed(null);
}
}
private void ClearQueuedGCode()
{
loadedGCode.Clear();
WriteChecksumLineToPrinter("M110 N1");
}
private void Connect_Thread()
{
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
// Allow the user to set the appropriate properties.
var portNames = FrostedSerialPort.GetPortNames();
//Debug.WriteLine("Open ports: {0}".FormatWith(portNames.Length));
if (portNames.Length > 0 || IsNetworkPrinting())
{
AttemptToConnect(this.ComPort, this.BaudRate);
if (CommunicationState == CommunicationStates.FailedToConnect)
{
OnConnectionFailed(null);
}
}
else
{
OnConnectionFailed(null);
}
}
private void ConnectionCallbackTimer(object state)
{
Timer t = (Timer)state;
if (!ContinueConnectionThread())
{
t.Dispose();
}
else
{
t.Change(100, 0);
}
}
private bool ContinueConnectionThread()
{
if (CommunicationState == CommunicationStates.AttemptingToConnect)
{
if (this.stopTryingToConnect)
{
connectThread.Join(JoinThreadTimeoutMs); //Halt connection thread
Disable();
connectionFailureMessage = "Canceled".Localize();
OnConnectionFailed(null);
return false;
}
else
{
return true;
}
}
else
{
// If we're no longer in the .AttemptingToConnect state, shutdown the connection thread and fire the
// OnConnectonSuccess event if we're connected and not Disconnecting
connectThread.Join(JoinThreadTimeoutMs);
return false;
}
}
private void DonePrintingSdFile(object sender, FoundStringEventArgs e)
{
ReadLineStartCallBacks.RemoveCallbackFromKey("Done printing file", DonePrintingSdFile);
CommunicationState = CommunicationStates.FinishedPrint;
this.PrintJobName = null;
// never leave the extruder and the bed hot
TurnOffBedAndExtruders();
ReleaseMotors();
}
private void ExtruderWasSetToAbsoluteMode(object sender, EventArgs e)
{
extruderMode = PrinterMachineInstruction.MovementTypes.Absolute;
}
private void ExtruderWasSetToRelativeMode(object sender, EventArgs e)
{
extruderMode = PrinterMachineInstruction.MovementTypes.Relative;
}
private void FileDeleteConfirmed(object sender, EventArgs e)
{
ReadLineStartCallBacks.RemoveCallbackFromKey("File deleted:", FileDeleteConfirmed);
PrintingCanContinue(this, null);
}
private void InjectGCode(string codeToInject, bool forceTopOfQueue = false)
{
codeToInject = codeToInject.Replace("\\n", "\n");
string[] lines = codeToInject.Split('\n');
for (int i = 0; i < lines.Length; i++)
{
queuedCommandStream2?.Add(lines[i], forceTopOfQueue);
}
}
private void KeepTrackOfAbsolutePostionAndDestination(string lineBeingSent)
{
if (lineBeingSent.StartsWith("G0 ")
|| lineBeingSent.StartsWith("G1 ")
|| lineBeingSent.StartsWith("G2 ")
|| lineBeingSent.StartsWith("G3 "))
{
PrinterMove newDestination = currentDestination;
if (movementMode == PrinterMachineInstruction.MovementTypes.Relative)
{
newDestination.position = Vector3.Zero;
}
GCodeFile.GetFirstNumberAfter("X", lineBeingSent, ref newDestination.position.x);
GCodeFile.GetFirstNumberAfter("Y", lineBeingSent, ref newDestination.position.y);
GCodeFile.GetFirstNumberAfter("Z", lineBeingSent, ref newDestination.position.z);
GCodeFile.GetFirstNumberAfter("E", lineBeingSent, ref newDestination.extrusion);
GCodeFile.GetFirstNumberAfter("F", lineBeingSent, ref newDestination.feedRate);
if (movementMode == PrinterMachineInstruction.MovementTypes.Relative)
{
newDestination.position += currentDestination.position;
}
if (currentDestination.position != newDestination.position)
{
currentDestination = newDestination;
DestinationChanged.CallEvents(this, null);
}
}
}
private void CreateStreamProcessors(string gcodeFilename, bool recoveryEnabled)
{
totalGCodeStream?.Dispose();
GCodeStream firstStream = null;
if (gcodeFilename != null)
{
loadedGCode = GCodeFile.Load(gcodeFilename, CancellationToken.None);
gCodeFileStream0 = new GCodeFileStream(loadedGCode);
if (printer.Settings.GetValue<bool>(SettingsKey.recover_is_enabled)
&& activePrintTask != null) // We are resuming a failed print (do lots of interesting stuff).
{
pauseHandlingStream1 = new PauseHandlingStream(printer, new PrintRecoveryStream(printer, gCodeFileStream0, activePrintTask.PercentDone));
// And increment the recovery count
activePrintTask.RecoveryCount++;
activePrintTask.Commit();
}
else
{
pauseHandlingStream1 = new PauseHandlingStream(printer, gCodeFileStream0);
}
firstStream = pauseHandlingStream1;
}
else
{
firstStream = new NotPrintingStream();
}
queuedCommandStream2 = new QueuedCommandsStream(firstStream);
macroProcessingStream3 = new MacroProcessingStream(queuedCommandStream2, printer);
relativeToAbsoluteStream4 = new RelativeToAbsoluteStream(macroProcessingStream3);
printLevelingStream5 = new PrintLevelingStream(printer.Settings, relativeToAbsoluteStream4, true);
waitForTempStream6 = new WaitForTempStream(this, printLevelingStream5);
babyStepsStream7 = new BabyStepsStream(printer.Settings, waitForTempStream6, gcodeFilename == null ? 2000 : 1);
if (activePrintTask != null)
{
// make sure we are in the position we were when we stopped printing
babyStepsStream7.Offset = new Vector3(activePrintTask.PrintingOffsetX, activePrintTask.PrintingOffsetY, activePrintTask.PrintingOffsetZ);
}
extrusionMultiplyerStream8 = new ExtrusionMultiplyerStream(babyStepsStream7);
feedrateMultiplyerStream9 = new FeedRateMultiplyerStream(extrusionMultiplyerStream8);
requestTemperaturesStream10 = new RequestTemperaturesStream(this, feedrateMultiplyerStream9);
processWriteRegExStream11 = new ProcessWriteRegexStream(this.printer.Settings, requestTemperaturesStream10, queuedCommandStream2);
totalGCodeStream = processWriteRegExStream11;
// Get the current position of the printer any time we reset our streams
ReadPosition();
}
private void LoadGCodeToPrint(string gcodeFilename)
{
CreateStreamProcessors(gcodeFilename, printer.Settings.GetValue<bool>(SettingsKey.recover_is_enabled));
}
private void DoneLoadingGCodeToPrint()
{
switch (communicationState)
{
case CommunicationStates.Connected:
// This can happen if the printer is reset during the slicing of the part.
break;
case CommunicationStates.PreparingToPrint:
{
var activePrintItem = printer.Bed.printItem;
if (activePrintItem.PrintItem.Id == 0)
{
activePrintItem.PrintItem.Commit();
}
if (activePrintTask == null)
{
// TODO: Fix printerItemID int requirement
activePrintTask = new PrintTask
{
PrintStart = DateTime.Now,
PrinterId = this.printer.Settings.ID.GetHashCode(),
PrintName = activePrintItem.PrintItem.Name,
PrintItemId = activePrintItem.PrintItem.Id,
PrintingGCodeFileName = activePrintItem.GetGCodePathAndFileName(),
PrintComplete = false
};
activePrintTask.Commit();
}
}
CommunicationState = CommunicationStates.Printing;
break;
default:
#if DEBUG
throw new Exception("We are not preparing to print so we should not be starting to print");
//#else
CommunicationState = CommunicationStates.Connected;
#endif
break;
}
}
private void MovementWasSetToAbsoluteMode(object sender, EventArgs e)
{
movementMode = PrinterMachineInstruction.MovementTypes.Absolute;
}
private void MovementWasSetToRelativeMode(object sender, EventArgs e)
{
movementMode = PrinterMachineInstruction.MovementTypes.Relative;
}
private void AtxPowerUpWasWritenToPrinter(object sender, EventArgs e)
{
OnAtxPowerStateChanged(true);
}
private void AtxPowerDownWasWritenToPrinter(object sender, EventArgs e)
{
OnAtxPowerStateChanged(false);
}
private void OnBedTemperatureRead(EventArgs e)
{
BedTemperatureRead.CallEvents(this, e);
}
private void OnBedTemperatureSet(EventArgs e)
{
BedTemperatureSet.CallEvents(this, e);
}
private void OnEnabledChanged(EventArgs e)
{
EnableChanged.CallEvents(this, e);
}
private void OnHotendTemperatureRead(EventArgs e)
{
HotendTemperatureRead.CallEvents(this, e);
}
private void OnHotendTemperatureSet(EventArgs e)
{
HotendTemperatureSet.CallEvents(this, e);
}
private void OnFanSpeedSet(EventArgs e)
{
FanSpeedSet.CallEvents(this, e);
}
private void OnFirmwareVersionRead(EventArgs e)
{
FirmwareVersionRead.CallEvents(this, e);
}
private bool IsNetworkPrinting()
{
return printer.Settings.GetValue<bool>(SettingsKey.enable_network_printing);
}
private void OnAtxPowerStateChanged(bool enableAtxPower)
{
atxPowerIsOn = enableAtxPower;
AtxPowerStateChanged.CallEvents(this, null);
}
private void SetDetailedPrintingState(string lineBeingSetToPrinter)
{
if (lineBeingSetToPrinter.StartsWith("G28"))
{
DetailedPrintingState = DetailedPrintingState.HomingAxis;
}
else if (waitForTempStream6?.HeatingBed ?? false)
{
DetailedPrintingState = DetailedPrintingState.HeatingBed;
}
else if (waitForTempStream6?.HeatingExtruder ?? false)
{
DetailedPrintingState = DetailedPrintingState.HeatingExtruder;
}
else
{
DetailedPrintingState = DetailedPrintingState.Printing;
}
}
private string currentSentLine;
private string previousSentLine;
private int ExpectedWaitSeconds(string lastInstruction)
{
if (lastInstruction.Contains("G0 ") || lastInstruction.Contains("G1 "))
{
// for moves we wait only as much as 2 seconds
return 2;
}
return 10;
}
private void TryWriteNextLineFromGCodeFile()
{
if (totalGCodeStream == null)
{
// don't try to write until we are initialized
return;
}
// wait until the printer responds from the last command with an OK OR we waited too long
if (timeHaveBeenWaitingForOK.IsRunning)
{
lock (locker)
{
// we are still sending commands
if (currentSentLine != null)
{
// This code is to try and make sure the printer does not stop on transmission errors.
// If it has been more than 10 seconds since the printer responded anything
// and it was not ok, and it's been more than 30 second since we sent the command.
if ((timeSinceLastReadAnything.Elapsed.TotalSeconds > 10 && timeSinceLastWrite.Elapsed.TotalSeconds > 30)
|| timeHaveBeenWaitingForOK.Elapsed.TotalSeconds > ExpectedWaitSeconds(currentSentLine))
{
// Basically we got some response but it did not contain an OK.
// The theory is that we may have received a transmission error (like 'OP' rather than 'OK')
// and in that event we don't want the print to just stop and wait forever.
currentLineIndexToSend--; // we are going to resend the last command
}
else
{
// we are waiting for the ok so let's wait
return;
}
}
}
}
lock (locker)
{
if (currentLineIndexToSend < allCheckSumLinesSent.Count)
{
WriteRawToPrinter(allCheckSumLinesSent[currentLineIndexToSend++] + "\n", "resend");
}
else
{
int waitTimeInMs = 60000; // 60 seconds
if (waitingForPosition.IsRunning
&& waitingForPosition.ElapsedMilliseconds < waitTimeInMs
&& PrinterIsConnected)
{
// we are waiting for a position response don't print more
return;
}
previousSentLine = this.currentSentLine;
currentSentLine = totalGCodeStream.ReadLine();
if (currentSentLine != null)
{
if (currentSentLine.Contains("M114")
&& PrinterIsConnected)
{
waitingForPosition.Restart();
}
double secondsSinceStartedPrint = timeSinceStartedPrint.Elapsed.TotalSeconds;
if (secondsSinceUpdateHistory > secondsSinceStartedPrint
|| secondsSinceUpdateHistory + 1 < secondsSinceStartedPrint)
{
double currentDone = loadedGCode.PercentComplete(gCodeFileStream0.LineIndex);
// Only update the amount done if it is greater than what is recorded.
// We don't want to mess up the resume before we actually resume it.
if (activePrintTask != null
&& babyStepsStream7 != null
&& activePrintTask.PercentDone < currentDone)
{
activePrintTask.PercentDone = currentDone;
activePrintTask.PrintingOffsetX = (float)babyStepsStream7.Offset.x;
activePrintTask.PrintingOffsetY = (float)babyStepsStream7.Offset.y;
activePrintTask.PrintingOffsetZ = (float)babyStepsStream7.Offset.z;
try
{
Task.Run(() => activePrintTask.Commit());
}
catch
{
// Can't write for some reason, continue with the write.
}
}
secondsSinceUpdateHistory = secondsSinceStartedPrint;
}
currentSentLine = currentSentLine.Trim();
// Check if there is anything in front of the ;.
if (currentSentLine.Split(';')[0].Trim().Length > 0)
{
if (currentSentLine.Length > 0)
{
WriteChecksumLineToPrinter(currentSentLine);
currentLineIndexToSend++;
}
}
}
else if (this.PrintWasCanceled)
{
CommunicationState = CommunicationStates.Connected;
// never leave the extruder and the bed hot
ReleaseMotors();
TurnOffBedAndExtruders();
this.PrintWasCanceled = false;
}
else if (communicationState == CommunicationStates.Printing)// we finished printing normally
{
CommunicationState = CommunicationStates.FinishedPrint;
this.PrintJobName = null;
// get us back to the no printing setting (this will clear the queued commands)
CreateStreamProcessors(null, false);
// never leave the extruder and the bed hot
ReleaseMotors();
TurnOffBedAndExtruders();
}
}
}
}
private void TurnOffBedAndExtruders()
{
for (int i = 0; i < printer.Settings.GetValue<int>(SettingsKey.extruder_count); i++)
{
SetTargetHotendTemperature(i, 0, true);
}
TargetBedTemperature = 0;
}
// this is to make it misbehave, chaos monkey, bad checksum
//int checkSumCount = 1;
private void WriteChecksumLineToPrinter(string lineToWrite)
{
// remove the comment if any
lineToWrite = RemoveCommentIfAny(lineToWrite);
KeepTrackOfAbsolutePostionAndDestination(lineToWrite);
// always send the reset line number without a checksum so that it is accepted
string lineWithCount;
if (lineToWrite.StartsWith("M110"))
{
lineWithCount = $"N1 {lineToWrite}";
GCodeFile.GetFirstNumberAfter("N", lineToWrite, ref currentLineIndexToSend);
allCheckSumLinesSent.SetStartingIndex(currentLineIndexToSend);
}
else
{
lineWithCount = $"N{allCheckSumLinesSent.Count} {lineToWrite}";
if (lineToWrite.StartsWith("M999"))
{
allCheckSumLinesSent.SetStartingIndex(1);
}
}
string lineWithChecksum = lineWithCount + "*" + GCodeFile.CalculateChecksum(lineWithCount).ToString();
allCheckSumLinesSent.Add(lineWithChecksum);
//if ((checkSumCount++ % 11) == 0)
//lineWithChecksum = lineWithCount + "*" + (GCodeFile.CalculateChecksum(lineWithCount) + checkSumCount).ToString();
WriteRawToPrinter(lineWithChecksum + "\n", lineToWrite);
SetDetailedPrintingState(lineToWrite);
}
private static string RemoveCommentIfAny(string lineToWrite)
{
int commentIndex = lineToWrite.IndexOf(';');
if (commentIndex > 0) // there is content in front of the ;
{
lineToWrite = lineToWrite.Substring(0, commentIndex).Trim();
}
return lineToWrite;
}
private void WriteRawToPrinter(string lineToWrite, string lineWithoutChecksum)
{
if (PrinterIsConnected || CommunicationState == CommunicationStates.AttemptingToConnect)
{
if (serialPort != null && serialPort.IsOpen)
{
FoundStringEventArgs foundStringEvent = new FoundStringEventArgs(lineWithoutChecksum);
// If we get a home command, ask the printer where it is after sending it.
if (lineWithoutChecksum.StartsWith("G28") // is a home
|| lineWithoutChecksum.StartsWith("G29") // is a bed level
|| lineWithoutChecksum.StartsWith("G30") // is a bed level
|| lineWithoutChecksum.StartsWith("G92") // is a reset of printer position
|| (lineWithoutChecksum.StartsWith("T") && !lineWithoutChecksum.StartsWith("T:"))) // is a switch extruder (verify this is the right time to ask this)
{
ReadPosition(true);
}
// write data to communication
{
StringEventArgs currentEvent = new StringEventArgs(lineToWrite);
if (PrinterIsPrinting)
{
string lineWidthoutCR = lineToWrite.TrimEnd();
CommunicationUnconditionalToPrinter.CallEvents(this, new StringEventArgs("{0} [{1:0.000}]\n".FormatWith(lineWidthoutCR, timeSinceStartedPrint.Elapsed.TotalSeconds)));
}
else
{
CommunicationUnconditionalToPrinter.CallEvents(this, currentEvent);
}
if (lineWithoutChecksum != null)
{
WriteLineStartCallBacks.CheckForKeys(foundStringEvent);
WriteLineContainsCallBacks.CheckForKeys(foundStringEvent);
if (foundStringEvent.SendToDelegateFunctions)
{
WroteLine.CallEvents(this, currentEvent);
}
}
}
try
{
lock (locker)
{
serialPort.Write(lineToWrite);
timeSinceLastWrite.Restart();
timeHaveBeenWaitingForOK.Restart();
}
//Debug.Write("w: " + lineToWrite);
}
catch (IOException ex)
{
TerminalLog.WriteLine("Exception:" + ex.Message);
if (CommunicationState == CommunicationStates.AttemptingToConnect)
{
// Handle hardware disconnects by relaying the failure reason and shutting down open resources
AbortConnectionAttempt("Connection Lost - " + ex.Message);
}
}
catch (TimeoutException e2) // known ok
{
// This writes on the next line, and there may have been another write attempt before it is printer. Write indented to attempt to show its association.
TerminalLog.WriteLine(" Error writing command:" + e2.Message);
}
catch (UnauthorizedAccessException e3)
{
TerminalLog.WriteLine("Exception:" + e3.Message);
AbortConnectionAttempt(e3.Message);
}
catch (Exception)
{
}
}
else
{
OnConnectionFailed(null);
}
}
}
public void MacroStart()
{
queuedCommandStream2?.Reset();
macroProcessingStream3.Reset();
}
public void MacroCancel()
{
babyStepsStream7?.CancelMoves();
waitForTempStream6?.Cancel();
queuedCommandStream2?.Cancel();
}
public void MacroContinue()
{
macroProcessingStream3?.Continue();
}
public class ReadThread
{
private static int currentReadThreadIndex = 0;
private int creationIndex;
private static int numRunning = 0;
public static int NumRunning
{
get
{
return numRunning;
}
}
private ReadThread(PrinterConnection printerConnection)
{
numRunning++;
currentReadThreadIndex++;
creationIndex = currentReadThreadIndex;
Task.Run(() =>
{
try
{
printerConnection.ReadFromPrinter(this);
}
catch
{
}
printerConnection.CommunicationUnconditionalToPrinter.CallEvents(this, new StringEventArgs("Read Thread Has Exited.\n"));
numRunning--;
});
}
internal static void Join()
{
currentReadThreadIndex++;
}
internal static void Start(PrinterConnection printerConnection)
{
new ReadThread(printerConnection);
}
internal bool IsCurrentThread()
{
return currentReadThreadIndex == creationIndex;
}
}
private class CheckSumLines
{
private static readonly int RingBufferCount = 64;
private int addedCount = 0;
private string[] ringBuffer = new string[RingBufferCount];
public int Count { get { return addedCount; } }
public string this[int index]
{
get
{
return ringBuffer[index % RingBufferCount];
}
set
{
ringBuffer[index % RingBufferCount] = value;
}
}
internal void Add(string lineWithChecksum)
{
this[addedCount++] = lineWithChecksum;
}
internal void SetStartingIndex(int startingIndex)
{
addedCount = startingIndex;
}
}
}
public class PrintItemWrapperEventArgs : EventArgs
{
public PrintItemWrapperEventArgs(PrintItemWrapper printItemWrapper)
{
this.PrintItemWrapper = printItemWrapper;
}
public PrintItemWrapper PrintItemWrapper { get; }
}
/// <summary>
/// This is a class to pass temperatures to callbacks that expect them.
/// </summary>
public class TemperatureEventArgs : EventArgs
{
public TemperatureEventArgs(int index0Based, double temperature)
{
this.Index0Based = index0Based;
this.Temperature = temperature;
}
public int Index0Based { get; }
public double Temperature { get; }
}
}