diff --git a/PTIReliance.sln.DotSettings b/PTIReliance.sln.DotSettings
index e20bbb1..daaca2d 100644
--- a/PTIReliance.sln.DotSettings
+++ b/PTIReliance.sln.DotSettings
@@ -4,4 +4,5 @@
REL
RTC
USB
- USBSN
\ No newline at end of file
+ USBSN
+ <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="PTIRelianceLib" ModuleVersionMask="*" ClassMask="PTIRelianceLib.Logging.LogProviders.*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="PTIRelianceLib" ModuleVersionMask="*" ClassMask="PTIRelianceLib.Logging.*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="RelianceCLI" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="PTIRelianceLib.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data>
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/Flash/RELStreamerTests.cs b/PTIRelianceLib.Tests/Flash/RELStreamerTests.cs
new file mode 100644
index 0000000..b1d7c82
--- /dev/null
+++ b/PTIRelianceLib.Tests/Flash/RELStreamerTests.cs
@@ -0,0 +1,17 @@
+using PTIRelianceLib.Flash;
+using Xunit;
+
+namespace PTIRelianceLib.Tests.Flash
+{
+ public class RELStreamerTests
+ {
+
+ [Fact]
+ public void TestNullStreamer()
+ {
+ var streamer = new RELStreamer(null, null);
+ var status = streamer.StreamFlashData(null);
+ Assert.Equal(ReturnCodes.InvalidRequestPayload, status);
+ }
+ }
+}
diff --git a/PTIRelianceLib.Tests/Imaging/BasePrintLogoTests.cs b/PTIRelianceLib.Tests/Imaging/BasePrintLogoTests.cs
new file mode 100644
index 0000000..75e77c2
--- /dev/null
+++ b/PTIRelianceLib.Tests/Imaging/BasePrintLogoTests.cs
@@ -0,0 +1,37 @@
+using PTIRelianceLib.Imaging;
+using Xunit;
+
+namespace PTIRelianceLib.Tests.Imaging
+{
+ using System.ComponentModel;
+
+ public class BasePrintLogoTests
+ {
+ [Fact()]
+ [Category("BMP")]
+ public void ApplyColorInversionTest()
+ {
+ // Input are expected are provided as resources, dithered is what
+ // we are testing
+
+ var input = BinaryFile.From(Properties.Resources.white_bitmap);
+
+ var logo = new BasePrintLogo(input);
+
+ Assert.False(logo.IsInverted);
+
+ logo.ApplyColorInversion();
+
+ var inverted = logo.ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.black_bitmap)).ImageData;
+
+ // White should ivnert to black
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, inverted));
+ Assert.True(logo.IsInverted);
+
+ // Flip back to white, test that the inversion flag is cleared
+ logo.ApplyColorInversion();
+ Assert.False(logo.IsInverted);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/Imaging/DitherFactoryTests.cs b/PTIRelianceLib.Tests/Imaging/DitherFactoryTests.cs
new file mode 100644
index 0000000..b0653b5
--- /dev/null
+++ b/PTIRelianceLib.Tests/Imaging/DitherFactoryTests.cs
@@ -0,0 +1,129 @@
+#region Header
+// DitherFactoryTests.cs
+// PTIRelianceLib.Tests
+// Cory Todd
+// 13-06-2018
+// 2:17 PM
+#endregion
+
+namespace PTIRelianceLib.Tests.Imaging
+{
+ using System;
+ using System.ComponentModel;
+ using PTIRelianceLib.Imaging;
+ using Xunit;
+
+ public class DitherFactoryTests
+ {
+ ///
+ /// Generates various perfect-gray input dithers and compares to known
+ /// good dither generators, e.g. photoshop
+ ///
+ [Fact()]
+ public void GetDithererAtkinsonFact()
+ {
+
+ // Input are expected are provided as resources, dithered is what
+ // we are testing
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_atkinson)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.Atkinson).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+ [Fact()]
+ public void GetDithererBurkesFact()
+ {
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_burkes)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.Burkes).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+ [Fact()]
+ public void GetDithererFloydSteinbergFact()
+ {
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_floydsteinbergs)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.FloydSteinberg).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+ // TODO test false floyd steinberg
+
+ [Fact()]
+ public void GetDithererJarvisJudiceNinkeFact()
+ {
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_jjn)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.JarvisJudiceNinke).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+ [Fact()]
+ [Category("BMP")]
+ public void GetDithererNoneFact()
+ {
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.white_bitmap)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.None).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+ [Fact()]
+ public void GetDithererSierraFact()
+ {
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_sierra)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.Sierra).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+ [Fact()]
+ public void GetDithererSierra2Fact()
+ {
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_sierra2)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.Sierra2).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+ [Fact()]
+ public void GetDithererSierraLiteFact()
+ {
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_sierralite)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.SierraLite).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+ [Fact()]
+ public void GetDithererStuckiFact()
+ {
+ var input = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_bitmap)).ImageData;
+ var expected = new BasePrintLogo(BinaryFile.From(Properties.Resources.gray_stucki)).ImageData;
+ var dithered = DitherFactory.GetDitherer(DitherAlgorithms.Stucki).GenerateDithered(input);
+
+ Assert.True(ImageTestHelpers.CompareCrc32(expected, dithered));
+ }
+
+
+ [Fact()]
+ public void BadDitherCtorFact()
+ {
+ // Cannot have a null algorith matrix
+ Assert.Throws(() => new Dither(null, 1, 1));
+
+ // Cannot allow a zero divisor
+ Assert.Throws(() => new Dither(new byte[,] { }, 0, 0));
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/Imaging/ImageExtTests.cs b/PTIRelianceLib.Tests/Imaging/ImageExtTests.cs
new file mode 100644
index 0000000..3951d68
--- /dev/null
+++ b/PTIRelianceLib.Tests/Imaging/ImageExtTests.cs
@@ -0,0 +1,179 @@
+#region Header
+
+// ImageExtTests.cs
+// PTIRelianceLib.Tests
+// Cory Todd
+// 13-06-2018
+// 2:08 PM
+
+#endregion
+
+namespace PTIRelianceLib.Tests.Imaging
+{
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Drawing;
+ using System.Drawing.Imaging;
+ using System.IO;
+ using System.Linq;
+ using Properties;
+ using PTIRelianceLib.Imaging;
+ using Xunit;
+
+ public class ImageExtTests
+ {
+ ///
+ /// Given a known bitmap, esnure that it generates the correct colorspace buffer with full opacity
+ ///
+ [Fact()]
+ public void BitmapToBufferFact()
+ {
+ var inbmp = new BasePrintLogo(BinaryFile.From(Resources.gray_bitmap)).ImageData;
+ var expectedBuff =
+ ImageTestHelpers.BgraGenerator(new byte[] { 128, 128, 128, 255}, inbmp.Height * inbmp.Width);
+ Assert.Equal(ImageConvertResults.Success, ImageTestHelpers.TestBitmapConversion(inbmp, expectedBuff));
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.white_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {255, 255, 255, 255}, inbmp.Height * inbmp.Width);
+ Assert.Equal(ImageConvertResults.Success, ImageTestHelpers.TestBitmapConversion(inbmp, expectedBuff));
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.black_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {0, 0, 0, 255}, inbmp.Height * inbmp.Width);
+ Assert.Equal(ImageConvertResults.Success, ImageTestHelpers.TestBitmapConversion(inbmp, expectedBuff));
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.red_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {0, 0, 255, 255}, inbmp.Height * inbmp.Width);
+ Assert.Equal(ImageConvertResults.Success, ImageTestHelpers.TestBitmapConversion(inbmp, expectedBuff));
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.green_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {0, 255, 0, 255}, inbmp.Height * inbmp.Width);
+ Assert.Equal(ImageConvertResults.Success, ImageTestHelpers.TestBitmapConversion(inbmp, expectedBuff));
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.blue_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {255, 0, 0, 255}, inbmp.Height * inbmp.Width);
+ Assert.Equal(ImageConvertResults.Success, ImageTestHelpers.TestBitmapConversion(inbmp, expectedBuff));
+ }
+
+ [Fact()]
+ [Category("BMP")]
+ public void BitmapImageToBitmapFact()
+ {
+ var bmps = new List
+ {
+ new BasePrintLogo(BinaryFile.From(Resources.gray_bitmap)).ImageData,
+ new BasePrintLogo(BinaryFile.From(Resources.white_bitmap)).ImageData,
+ new BasePrintLogo(BinaryFile.From(Resources.black_bitmap)).ImageData,
+ new BasePrintLogo(BinaryFile.From(Resources.red_bitmap)).ImageData,
+ new BasePrintLogo(BinaryFile.From(Resources.green_bitmap)).ImageData,
+ new BasePrintLogo(BinaryFile.From(Resources.blue_bitmap)).ImageData
+ };
+
+ foreach (var inbmp in bmps)
+ {
+ using (var memory = new MemoryStream())
+ {
+ inbmp.Save(memory, ImageFormat.Png);
+ memory.Position = 0;
+
+ var id = new Bitmap(memory);
+ Assert.True(ImageTestHelpers.CompareCrc32(inbmp, id));
+ }
+ }
+ }
+
+
+ ///
+ /// Given a known bitmap, esnure that it generates the correct colorspace buffer with full opacity
+ ///
+ [Fact()]
+ [Category("BMP")]
+ public void BitmapInvertColorChannelsFact()
+ {
+ var inbmp = new BasePrintLogo(BinaryFile.From(Resources.gray_bitmap)).ImageData;
+ var expectedBuff =
+ ImageTestHelpers.BgraGenerator(new byte[] {127, 127, 127, 255}, inbmp.Height * inbmp.Width);
+ inbmp.InvertColorChannels();
+ var actualBuff = inbmp.ToBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.white_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {0, 0, 0, 255}, inbmp.Height * inbmp.Width);
+ inbmp.InvertColorChannels();
+ actualBuff = inbmp.ToBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.black_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {255, 255, 255, 255}, inbmp.Height * inbmp.Width);
+ inbmp.InvertColorChannels();
+ actualBuff = inbmp.ToBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.red_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {255, 255, 0, 255}, inbmp.Height * inbmp.Width);
+ inbmp.InvertColorChannels();
+ actualBuff = inbmp.ToBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.green_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {255, 0, 255, 255}, inbmp.Height * inbmp.Width);
+ inbmp.InvertColorChannels();
+ actualBuff = inbmp.ToBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.blue_bitmap)).ImageData;
+ expectedBuff = ImageTestHelpers.BgraGenerator(new byte[] {0, 255, 255, 255}, inbmp.Height * inbmp.Width);
+ inbmp.InvertColorChannels();
+ actualBuff = inbmp.ToBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+ }
+
+
+ [Fact()]
+ public void BitmapToLogoBufferSimpleFact()
+ {
+ var inbmp = new BasePrintLogo(BinaryFile.From(Resources.gray_bitmap)).ImageData;
+ var expectedBuff = Repeated(255, (inbmp.Height * inbmp.Width) >> 3).ToArray();
+ var actualBuff = inbmp.ToLogoBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.white_bitmap)).ImageData;
+ expectedBuff = Repeated(0, (inbmp.Height * inbmp.Width) >> 3).ToArray();
+ actualBuff = inbmp.ToLogoBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.black_bitmap)).ImageData;
+ expectedBuff = Repeated(255, (inbmp.Height * inbmp.Width) >> 3).ToArray();
+ actualBuff = inbmp.ToLogoBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.red_bitmap)).ImageData;
+ expectedBuff = Repeated(255, (inbmp.Height * inbmp.Width) >> 3).ToArray();
+ actualBuff = inbmp.ToLogoBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.green_bitmap)).ImageData;
+ expectedBuff = Repeated(255, (inbmp.Height * inbmp.Width) >> 3).ToArray();
+ actualBuff = inbmp.ToLogoBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+
+ inbmp = new BasePrintLogo(BinaryFile.From(Resources.blue_bitmap)).ImageData;
+ expectedBuff = Repeated(255, (inbmp.Height * inbmp.Width) >> 3).ToArray();
+ actualBuff = inbmp.ToLogoBuffer();
+ Assert.Equal(expectedBuff, actualBuff);
+ }
+
+ ///
+ /// Returns a list of type T with value repeat count times
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static List Repeated(T value, int count)
+ {
+ var ret = new List(count);
+ ret.AddRange(Enumerable.Repeat(value, count));
+ return ret;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/Imaging/ImageTestHelpers.cs b/PTIRelianceLib.Tests/Imaging/ImageTestHelpers.cs
new file mode 100644
index 0000000..bcc61cf
--- /dev/null
+++ b/PTIRelianceLib.Tests/Imaging/ImageTestHelpers.cs
@@ -0,0 +1,105 @@
+#region Header
+
+// ImageTestHelpers.cs
+// PTIRelianceLib.Tests
+// Cory Todd
+// 13-06-2018
+// 2:00 PM
+
+#endregion
+
+namespace PTIRelianceLib.Tests.Imaging
+{
+ using System;
+ using System.Drawing;
+ using System.Linq;
+ using PTIRelianceLib.Imaging;
+
+ public enum ImageConvertResults
+ {
+ Success,
+ ErrToBuffer,
+ ErrToBitmap,
+ }
+
+ public static class ImageTestHelpers
+ {
+ public static ImageConvertResults TestBitmapConversion(Bitmap bmp, byte[] expectedBuff)
+ {
+ var actualBuff = bmp.ToBuffer();
+ if (!expectedBuff.SequenceEqual(actualBuff))
+ {
+ return ImageConvertResults.ErrToBuffer;
+ }
+
+
+ // Now convert back to bitmap
+ var expectedBmp = new Bitmap(bmp);
+ var actualBmp = actualBuff.AsBitmap(expectedBmp.Width, expectedBmp.Height);
+
+ return !CompareCrc32(expectedBmp, actualBmp)
+ ? ImageConvertResults.ErrToBitmap
+ : ImageConvertResults.Success;
+ }
+
+ ///
+ /// Compare checksum of two bitmaps to verify equality
+ ///
+ /// First image to compare
+ /// Second image to comare
+ /// True if images are equal
+ public static bool CompareCrc32(Bitmap b1, Bitmap b2)
+ {
+ // both null, treat nulls as equal
+ if (b1 == null && b2 == null)
+ {
+ return true;
+ }
+
+ // One or the other is null, this is not equal
+ if (b1 == null || b2 == null)
+ {
+ return false;
+ }
+
+ if (!b1.Size.Equals(b2.Size))
+ {
+ return false;
+ }
+
+ var left = b1.ToBuffer();
+ var right = b1.ToBuffer();
+
+ return (Crc32.ComputeChecksum(left) == Crc32.ComputeChecksum(right));
+ }
+
+ ///
+ /// Fills a repeated BGRA pattern into a buffer of count bytes. Byte order is
+ /// Blue, Green, Red, Alpha
+ ///
+ /// 4-byte BGRA code
+ /// Number of times to repeat pattern
+ /// new buffer
+ public static byte[] BgraGenerator(byte[] pattern, int count)
+ {
+ if (pattern.Length != 4)
+ {
+ throw new ArgumentException("pattern length must be 4");
+ }
+
+ if (count <= 0)
+ {
+ throw new ArgumentException("count be greater than zero");
+ }
+
+ var result = new byte[pattern.Length * count];
+
+ for (var i = 0; i < result.Length; i += 4)
+ {
+ Array.Copy(pattern, 0, result, i, 4);
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/Imaging/PixelTests.cs b/PTIRelianceLib.Tests/Imaging/PixelTests.cs
new file mode 100644
index 0000000..0c8bec0
--- /dev/null
+++ b/PTIRelianceLib.Tests/Imaging/PixelTests.cs
@@ -0,0 +1,69 @@
+#region Header
+// PixelTests.cs
+// PTIRelianceLib.Tests
+// Cory Todd
+// 13-06-2018
+// 2:04 PM
+#endregion
+
+namespace PTIRelianceLib.Tests.Imaging
+{
+ using PTIRelianceLib.Imaging;
+ using Xunit;
+
+ public class PixelTests
+ {
+ [Fact]
+ public void PixelCtorTest()
+ {
+ var pxDefault = new Pixel();
+ Assert.Equal(0, pxDefault.A);
+ Assert.Equal(0, pxDefault.R);
+ Assert.Equal(0, pxDefault.G);
+ Assert.Equal(0, pxDefault.B);
+
+ var pxByte = new Pixel((byte)0, (byte)1, (byte)2, (byte)3);
+ Assert.Equal(0, pxByte.A);
+ Assert.Equal(1, pxByte.R);
+ Assert.Equal(2, pxByte.G);
+ Assert.Equal(3, pxByte.B);
+
+ var pxInt = new Pixel(0, 1, 2, 3);
+ Assert.Equal(0, pxInt.A);
+ Assert.Equal(1, pxInt.R);
+ Assert.Equal(2, pxInt.G);
+ Assert.Equal(3, pxInt.B);
+
+ var pxBytes = new Pixel(new byte[] { 0, 1, 2, 3 });
+ Assert.Equal(3, pxBytes.A);
+ Assert.Equal(2, pxBytes.R);
+ Assert.Equal(1, pxBytes.G);
+ Assert.Equal(0, pxBytes.B);
+ }
+
+ [Fact]
+ public void PixelWhiteTest()
+ {
+ var input = new Pixel(new byte[] { 0, 0, 0, 0 });
+ Assert.True(input.IsNotWhite());
+
+ input = new Pixel(new byte[] { 255, 0, 0, 0 }); // Black/Full alpha is NOT white
+ Assert.True(input.IsNotWhite());
+
+ input = new Pixel(new byte[] { 0, 255, 0, 0 }); // Blue/0 alpha is NOT white
+ Assert.True(input.IsNotWhite());
+
+ input = new Pixel(new byte[] { 0, 0, 255, 0 }); // Green/0 alpha is NOT white
+ Assert.True(input.IsNotWhite());
+
+ input = new Pixel(new byte[] { 0, 0, 0, 255 }); // Red/0 alpha is NOT white
+ Assert.True(input.IsNotWhite());
+
+ input = new Pixel(new byte[] { 255, 255, 255, 255 }); // White/Full alpha is white
+ Assert.False(input.IsNotWhite());
+
+ input = new Pixel(new byte[] { 0, 255, 255, 255 }); // White/0 alpha is white
+ Assert.False(input.IsNotWhite());
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/Logo/RELLogoBankTests.cs b/PTIRelianceLib.Tests/Logo/RELLogoBankTests.cs
new file mode 100644
index 0000000..d9fe361
--- /dev/null
+++ b/PTIRelianceLib.Tests/Logo/RELLogoBankTests.cs
@@ -0,0 +1,36 @@
+using PTIRelianceLib.Logo;
+using Xunit;
+
+namespace PTIRelianceLib.Tests.Logo
+{
+ using System.Collections.Generic;
+ using PTIRelianceLib.Imaging;
+
+ public class RELLogoBankTests
+ {
+ [Fact]
+ public void TestMakeHeader()
+ {
+ var logoBank = new RELLogoBank();
+ var logo = new BasePrintLogo(BinaryFile.From(Properties.Resources.white_bitmap));
+ var headers = logoBank.MakeHeaders(new List{logo});
+
+ Assert.NotEmpty(headers);
+
+ foreach (var header in headers)
+ {
+ Assert.NotNull(header);
+ Assert.NotNull(header.LogoData);
+ Assert.False(string.IsNullOrEmpty(header.Name));
+ Assert.Equal(0, header.LeftMargin);
+ Assert.Equal(logo.Dimensions.SizeInBytes, (int) header.Size);
+ Assert.Equal(logo.Dimensions.Height, header.HeightDots);
+ Assert.Equal(logo.Dimensions.WidthBytes, header.WidthBytes);
+ Assert.Equal(0x001E0000, (int) header.StartAddr);
+
+ Assert.Equal(131072, logoBank.TotalBankSize);
+ Assert.Equal(131072 / 1024, logoBank.TotalBankSizeKb);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/Logo/RELLogoHeaderTests.cs b/PTIRelianceLib.Tests/Logo/RELLogoHeaderTests.cs
new file mode 100644
index 0000000..a241ac9
--- /dev/null
+++ b/PTIRelianceLib.Tests/Logo/RELLogoHeaderTests.cs
@@ -0,0 +1,39 @@
+using Xunit;
+
+namespace PTIRelianceLib.Tests.Logo
+{
+ using PTIRelianceLib.Logo;
+
+ public class RELLogoHeaderTests
+ {
+ [Fact]
+ public void TestSerialize()
+ {
+ var logoHeader = new RELLogoHeader();
+ var data = logoHeader.Serialize();
+
+ Assert.NotNull(data);
+ Assert.Equal(28, data.Length);
+ }
+
+ [Fact]
+ public void TestSerializeShortName()
+ {
+ var logoHeader = new RELLogoHeader {Name = "A"};
+ var data = logoHeader.Serialize();
+
+ Assert.NotNull(data);
+ Assert.Equal(28, data.Length);
+ }
+
+ [Fact]
+ public void TestSerializeLongName()
+ {
+ var logoHeader = new RELLogoHeader { Name = "AaBbCcDdEeFf" };
+ var data = logoHeader.Serialize();
+
+ Assert.NotNull(data);
+ Assert.Equal(28, data.Length);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/Logo/RELLogoParserTests.cs b/PTIRelianceLib.Tests/Logo/RELLogoParserTests.cs
new file mode 100644
index 0000000..dc64cec
--- /dev/null
+++ b/PTIRelianceLib.Tests/Logo/RELLogoParserTests.cs
@@ -0,0 +1,28 @@
+using Moq;
+using PTIRelianceLib.Logo;
+using System;
+using Xunit;
+
+namespace PTIRelianceLib.Tests.Logo
+{
+ public class RELLogoParserTests
+ {
+ [Fact]
+ public void TestParser()
+ {
+ var logoParser = new RELLogoParser();
+
+ var result = logoParser.Parse(0, Properties.Resources.white_bitmap);
+ Assert.NotNull(result);
+ }
+
+ [Fact]
+ public void TestParserNull()
+ {
+ var logoParser = new RELLogoParser();
+
+ var result = logoParser.Parse(0, null);
+ Assert.Null(result);
+ }
+ }
+}
diff --git a/PTIRelianceLib.Tests/Logo/RELLogoUpdaterTests.cs b/PTIRelianceLib.Tests/Logo/RELLogoUpdaterTests.cs
new file mode 100644
index 0000000..7f3b88e
--- /dev/null
+++ b/PTIRelianceLib.Tests/Logo/RELLogoUpdaterTests.cs
@@ -0,0 +1,129 @@
+using Xunit;
+
+namespace PTIRelianceLib.Tests.Logo
+{
+ using System;
+ using PTIRelianceLib.IO;
+ using PTIRelianceLib.IO.Internal;
+ using PTIRelianceLib.Logo;
+ using PTIRelianceLib.Transport;
+
+ public class RELLogoUpdaterTests
+ {
+ private static readonly object MTestLock = new object();
+
+ private readonly HidDeviceConfig _mConfig;
+ private readonly FakeNativeMethods _mNativeMock;
+ private readonly IProgressMonitor _mReporter;
+ public const int VendorId = 0x0425;
+ public const int ProductId = 0x8147;
+
+ public RELLogoUpdaterTests()
+ {
+ _mReporter = new DevNullMonitor();
+
+ _mNativeMock = new FakeNativeMethods();
+
+ // Reliance will "always" use report lengths of 34 bytes
+ _mConfig = new HidDeviceConfig
+ {
+ VendorId = VendorId,
+ ProductId = ProductId,
+ InReportLength = 34,
+ OutReportLength = 34,
+ InReportId = 2,
+ OutReportId = 1,
+ NativeHid = _mNativeMock
+ };
+ }
+
+ [Fact]
+ public void TestNormalUpdate()
+ {
+ lock (MTestLock)
+ {
+ _mNativeMock.GetNextResponse = (d) => GenerateHidData(0xAA);
+ var port = new HidPort(_mConfig);
+ var logo = Properties.Resources.white_bitmap;
+ var header = new RELLogoHeader
+ {
+ LogoData = logo,
+ };
+
+ var updater = new RELLogoUpdater(port, header)
+ {
+ Reporter = _mReporter
+ };
+
+ updater.ExecuteUpdate();
+ }
+ }
+
+ [Fact]
+ public void TestEmptyLogo()
+ {
+ lock (MTestLock)
+ {
+ _mNativeMock.GetNextResponse = (d) => GenerateHidData(0xAA);
+ var port = new HidPort(_mConfig);
+ var logo = new byte[0];
+ var header = new RELLogoHeader
+ {
+ LogoData = logo,
+ };
+
+ var updater = new RELLogoUpdater(port, header)
+ {
+ Reporter = _mReporter
+ };
+
+ updater.ExecuteUpdate();
+ }
+ }
+
+ [Fact]
+ public void TestCtorNullPort()
+ {
+ lock (MTestLock)
+ {
+ IPort port = null;
+ var logo = Properties.Resources.gray_burkes;
+ var header = new RELLogoHeader
+ {
+ LogoData = logo,
+ };
+
+ Assert.Throws(() => new RELLogoUpdater(port, header));
+ }
+ }
+
+ [Fact]
+ public void TestCtorNullFile()
+ {
+ lock (MTestLock)
+ {
+ var port = new HidPort(_mConfig);
+ RELLogoHeader header = null;
+
+ Assert.Throws(() => new RELLogoUpdater(port, header));
+ }
+ }
+
+ ///
+ /// Generates a valid inreport
+ ///
+ /// Payload to pack
+ ///
+ private byte[] GenerateHidData(params byte[] payload)
+ {
+ var packet = new ReliancePacket(payload);
+ payload = packet.Package().GetBytes();
+
+ var buff = new byte[_mConfig.InReportLength];
+ buff[0] = _mConfig.InReportId;
+ buff[1] = (byte)payload.Length;
+ Array.Copy(payload, 0, buff, 2, Math.Min(payload.Length, buff.Length - 2));
+ return buff;
+ }
+ }
+}
diff --git a/PTIRelianceLib.Tests/MutableReliancePacket.cs b/PTIRelianceLib.Tests/MutableReliancePacket.cs
new file mode 100644
index 0000000..a3a3e51
--- /dev/null
+++ b/PTIRelianceLib.Tests/MutableReliancePacket.cs
@@ -0,0 +1,48 @@
+#region Header
+// MutableReliancePacket.cs
+// PTIRelianceLib.Tests
+// Cory Todd
+// 14-06-2018
+// 11:33 AM
+#endregion
+
+namespace PTIRelianceLib.Tests
+{
+ using System;
+ using PTIRelianceLib.Transport;
+
+ internal class MutableReliancePacket : ReliancePacket
+ {
+ internal MutableReliancePacket(byte[] data) : base(data)
+ {
+ }
+
+ ///
+ /// Injects newVal into byte array at specified index. Does not
+ /// modify length or checksum
+ ///
+ /// 0 base index to insert
+ /// 1 or more bytes to insert
+ public void Mutate(int index, params byte[] newVal)
+ {
+ var data = GetBytes();
+ var newData = new byte[data.Length + newVal.Length];
+ Array.Copy(data, newData, data.Length);
+ Array.Copy(newVal, 0, newData, data.Length, newVal.Length);
+
+ Clear();
+ Add(newData);
+ }
+
+ ///
+ /// Corrupt the checksum on this packet
+ ///
+ public void BreakChecksum()
+ {
+ var data = GetBytes();
+ data[Count - 1] = (byte) (data[Count - 1] + 1);
+ Clear();
+ Add(data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/PTIRelianceLib.Tests.csproj b/PTIRelianceLib.Tests/PTIRelianceLib.Tests.csproj
index ed7d835..d110733 100644
--- a/PTIRelianceLib.Tests/PTIRelianceLib.Tests.csproj
+++ b/PTIRelianceLib.Tests/PTIRelianceLib.Tests.csproj
@@ -8,6 +8,16 @@
AnyCPU;x64
+
+
+
+
+
+
+ Never
+
+
+
@@ -20,4 +30,19 @@
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
diff --git a/PTIRelianceLib.Tests/Properties/Resources.Designer.cs b/PTIRelianceLib.Tests/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..9d8e92c
--- /dev/null
+++ b/PTIRelianceLib.Tests/Properties/Resources.Designer.cs
@@ -0,0 +1,203 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace PTIRelianceLib.Tests.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PTIRelianceLib.Tests.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] black_bitmap {
+ get {
+ object obj = ResourceManager.GetObject("black_bitmap", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] blue_bitmap {
+ get {
+ object obj = ResourceManager.GetObject("blue_bitmap", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_atkinson {
+ get {
+ object obj = ResourceManager.GetObject("gray_atkinson", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_bitmap {
+ get {
+ object obj = ResourceManager.GetObject("gray_bitmap", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_burkes {
+ get {
+ object obj = ResourceManager.GetObject("gray_burkes", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_floydsteinbergs {
+ get {
+ object obj = ResourceManager.GetObject("gray_floydsteinbergs", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_jjn {
+ get {
+ object obj = ResourceManager.GetObject("gray_jjn", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_sierra {
+ get {
+ object obj = ResourceManager.GetObject("gray_sierra", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_sierra2 {
+ get {
+ object obj = ResourceManager.GetObject("gray_sierra2", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_sierralite {
+ get {
+ object obj = ResourceManager.GetObject("gray_sierralite", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] gray_stucki {
+ get {
+ object obj = ResourceManager.GetObject("gray_stucki", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] green_bitmap {
+ get {
+ object obj = ResourceManager.GetObject("green_bitmap", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] red_bitmap {
+ get {
+ object obj = ResourceManager.GetObject("red_bitmap", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] white_bitmap {
+ get {
+ object obj = ResourceManager.GetObject("white_bitmap", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+ }
+}
diff --git a/PTIRelianceLib.Tests/Properties/Resources.resx b/PTIRelianceLib.Tests/Properties/Resources.resx
new file mode 100644
index 0000000..d1f1e79
--- /dev/null
+++ b/PTIRelianceLib.Tests/Properties/Resources.resx
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ ..\Resources\black_bitmap.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\blue_bitmap.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_atkinson.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_bitmap.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_burkes.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_floydsteinbergs.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_jjn.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_sierra.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_sierra2.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_sierralite.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\gray_stucki.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\green_bitmap.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\red_bitmap.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\white_bitmap.bmp;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/PTIRelianceLib.Tests/ReliancePrinterTests.cs b/PTIRelianceLib.Tests/ReliancePrinterTests.cs
index 4c78b97..09ebdeb 100644
--- a/PTIRelianceLib.Tests/ReliancePrinterTests.cs
+++ b/PTIRelianceLib.Tests/ReliancePrinterTests.cs
@@ -89,9 +89,11 @@ public void TestStatus()
_mNativeMock.GetNextResponse = (d) => GenerateHidData(data.Serialize());
using (var printer = new ReliancePrinter(_mConfig))
{
- var resp = printer.GetStatus();
+ var resp = printer.GetStatus();
Assert.Equal(data.HeadVoltage, resp.HeadVoltage);
}
+
+
}
}
@@ -169,6 +171,39 @@ public void TestFlashUpdate()
}
}
+ [Fact]
+ public void TestFlashUpdateNoReporter()
+ {
+ lock (MTestLock)
+ {
+ var count = 0;
+ _mNativeMock.GetNextResponse = (d) =>
+ {
+ if (count++ != 0)
+ {
+ return GenerateHidData(0xAA);
+ }
+
+ var rev = new Revlev(1, 27, 127);
+ return GenerateHidData(rev.Serialize());
+ };
+ using (var printer = new ReliancePrinter(_mConfig))
+ {
+ var fakeFirmware = new byte[204848];
+ var csum = ((uint)0xCC731E7E).ToBytesBE();
+ var model = ((uint)0x500).ToBytesBE();
+ var size = ((ulong)0x32000).ToBytesBE();
+ Array.Copy(csum, 0, fakeFirmware, 8, csum.Length);
+ Array.Copy(model, 0, fakeFirmware, 12, model.Length);
+ Array.Copy(size, 0, fakeFirmware, 40, size.Length);
+ var magic = Encoding.ASCII.GetBytes("PTIXPTIX");
+ Array.Copy(magic, 0, fakeFirmware, 0, magic.Length);
+ var resp = printer.FlashUpdateTarget(BinaryFile.From(fakeFirmware));
+ Assert.Equal(ReturnCodes.Okay, resp);
+ }
+ }
+ }
+
[Fact]
public void TestFlashUpdateInvalidFile()
{
diff --git a/PTIRelianceLib.Tests/Resources/black_bitmap.bmp b/PTIRelianceLib.Tests/Resources/black_bitmap.bmp
new file mode 100644
index 0000000..c7c95a0
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/black_bitmap.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/blue_bitmap.bmp b/PTIRelianceLib.Tests/Resources/blue_bitmap.bmp
new file mode 100644
index 0000000..cdf2539
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/blue_bitmap.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_atkinson.bmp b/PTIRelianceLib.Tests/Resources/gray_atkinson.bmp
new file mode 100644
index 0000000..23cc8f6
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_atkinson.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_bitmap.bmp b/PTIRelianceLib.Tests/Resources/gray_bitmap.bmp
new file mode 100644
index 0000000..71dd982
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_bitmap.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_burkes.bmp b/PTIRelianceLib.Tests/Resources/gray_burkes.bmp
new file mode 100644
index 0000000..221446b
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_burkes.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_floydsteinbergs.bmp b/PTIRelianceLib.Tests/Resources/gray_floydsteinbergs.bmp
new file mode 100644
index 0000000..a97c2fd
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_floydsteinbergs.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_jjn.bmp b/PTIRelianceLib.Tests/Resources/gray_jjn.bmp
new file mode 100644
index 0000000..9b34999
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_jjn.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_sierra.bmp b/PTIRelianceLib.Tests/Resources/gray_sierra.bmp
new file mode 100644
index 0000000..56160a3
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_sierra.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_sierra2.bmp b/PTIRelianceLib.Tests/Resources/gray_sierra2.bmp
new file mode 100644
index 0000000..34cc7fc
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_sierra2.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_sierralite.bmp b/PTIRelianceLib.Tests/Resources/gray_sierralite.bmp
new file mode 100644
index 0000000..13c326e
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_sierralite.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/gray_stucki.bmp b/PTIRelianceLib.Tests/Resources/gray_stucki.bmp
new file mode 100644
index 0000000..74f66de
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/gray_stucki.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/green_bitmap.bmp b/PTIRelianceLib.Tests/Resources/green_bitmap.bmp
new file mode 100644
index 0000000..df59e41
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/green_bitmap.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/red_bitmap.bmp b/PTIRelianceLib.Tests/Resources/red_bitmap.bmp
new file mode 100644
index 0000000..478b946
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/red_bitmap.bmp differ
diff --git a/PTIRelianceLib.Tests/Resources/white_bitmap.bmp b/PTIRelianceLib.Tests/Resources/white_bitmap.bmp
new file mode 100644
index 0000000..d54feea
Binary files /dev/null and b/PTIRelianceLib.Tests/Resources/white_bitmap.bmp differ
diff --git a/PTIRelianceLib.Tests/Transport/ReliancePacketTests.cs b/PTIRelianceLib.Tests/Transport/ReliancePacketTests.cs
index 6be499b..4af2a1c 100644
--- a/PTIRelianceLib.Tests/Transport/ReliancePacketTests.cs
+++ b/PTIRelianceLib.Tests/Transport/ReliancePacketTests.cs
@@ -4,6 +4,7 @@
namespace PTIRelianceLib.Tests.Transport
{
+ using System.Collections.Generic;
using Protocol;
public class ReliancePacketTests
@@ -136,5 +137,74 @@ public void TestIngressUnpackage()
Assert.Equal("48, 65, 6C, 6C, 6F", extracted.ToString());
}
+
+ [Fact()]
+ public void TestValidateTooShort()
+ {
+ var packet = new ReliancePacket(1,2);
+ var payload = packet.ExtractPayload();
+ Assert.Equal(new byte[]{1,2}, payload.GetBytes());
+ }
+
+ [Fact()]
+ public void TestValidateNak()
+ {
+ var codes = new Dictionary
+ {
+ { 0xAC, PacketTypes.NegativeAck},
+ { 0xAB, PacketTypes.SequenceError },
+ { 0xFF, PacketTypes.Timeout },
+ { 0xF1, PacketTypes.Normal },
+ };
+ foreach (var c in codes)
+ {
+ var payload = new [] {c.Key, (byte) 'H', (byte) 'e', (byte) 'l', (byte) 'l', (byte) 'o'};
+
+ var packet = new ReliancePacket(payload);
+ packet.Package();
+
+ var extracted = packet.ExtractPayload();
+
+ Assert.Equal(payload.Length, extracted.Count);
+ Assert.Equal(c.Value, extracted.GetPacketType());
+
+ var str = string.Format("{0:X2}, 48, 65, 6C, 6C, 6F", c.Key);
+ Assert.Equal(str, extracted.ToString());
+ }
+ }
+
+ [Fact]
+ public void TestZeroFill()
+ {
+ var payload = new byte[] { 0xAA, (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
+ var packet = new MutableReliancePacket(payload);
+ packet.Package();
+
+ packet.Mutate(packet.Count, 0, 0, 0, 0, 0, 0, 0 ,0 ,0 ,0);
+
+ var extracted = packet.ExtractPayload();
+
+ // ACK byte gets stripped
+ Assert.Equal(payload.Length - 1, extracted.Count);
+ Assert.Equal(PacketTypes.PositiveAck, extracted.GetPacketType());
+
+ Assert.Equal("48, 65, 6C, 6C, 6F", extracted.ToString());
+ }
+
+ [Fact]
+ public void TestBadChecksumIngress()
+ {
+ var payload = new byte[] { 0xAA, (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
+ var packet = new MutableReliancePacket(payload);
+ packet.Package();
+
+ packet.BreakChecksum();
+
+ var extracted = packet.ExtractPayload();
+
+ // Malformed packet does not get stripped
+ Assert.Equal(payload.Length + 2, extracted.Count);
+ Assert.Equal(PacketTypes.PositiveAck, extracted.GetPacketType());
+ }
}
}
diff --git a/PTIRelianceLib/Firmware/Internal/IMemoryMap.cs b/PTIRelianceLib/Firmware/IMemoryMap.cs
similarity index 96%
rename from PTIRelianceLib/Firmware/Internal/IMemoryMap.cs
rename to PTIRelianceLib/Firmware/IMemoryMap.cs
index 4b41c0a..f4a7f97 100644
--- a/PTIRelianceLib/Firmware/Internal/IMemoryMap.cs
+++ b/PTIRelianceLib/Firmware/IMemoryMap.cs
@@ -6,7 +6,7 @@
// 9:02 AM
#endregion
-namespace PTIRelianceLib.Firmware.Internal
+namespace PTIRelianceLib.Firmware
{
internal interface IMemoryMap
{
diff --git a/PTIRelianceLib/Firmware/Internal/RELFwHeader.cs b/PTIRelianceLib/Firmware/RELFwHeader.cs
similarity index 98%
rename from PTIRelianceLib/Firmware/Internal/RELFwHeader.cs
rename to PTIRelianceLib/Firmware/RELFwHeader.cs
index afc8d4a..6573e55 100644
--- a/PTIRelianceLib/Firmware/Internal/RELFwHeader.cs
+++ b/PTIRelianceLib/Firmware/RELFwHeader.cs
@@ -6,7 +6,7 @@
// 9:15 AM
#endregion
-namespace PTIRelianceLib.Firmware.Internal
+namespace PTIRelianceLib.Firmware
{
using System;
diff --git a/PTIRelianceLib/Firmware/Internal/RELFwParser.cs b/PTIRelianceLib/Firmware/RELFwParser.cs
similarity index 99%
rename from PTIRelianceLib/Firmware/Internal/RELFwParser.cs
rename to PTIRelianceLib/Firmware/RELFwParser.cs
index 01f6f25..ff71729 100644
--- a/PTIRelianceLib/Firmware/Internal/RELFwParser.cs
+++ b/PTIRelianceLib/Firmware/RELFwParser.cs
@@ -6,7 +6,7 @@
// 9:11 AM
#endregion
-namespace PTIRelianceLib.Firmware.Internal
+namespace PTIRelianceLib.Firmware
{
using System;
using System.IO;
diff --git a/PTIRelianceLib/Firmware/Internal/RELFwUpdater.cs b/PTIRelianceLib/Firmware/RELFwUpdater.cs
similarity index 98%
rename from PTIRelianceLib/Firmware/Internal/RELFwUpdater.cs
rename to PTIRelianceLib/Firmware/RELFwUpdater.cs
index af63ce5..8172b48 100644
--- a/PTIRelianceLib/Firmware/Internal/RELFwUpdater.cs
+++ b/PTIRelianceLib/Firmware/RELFwUpdater.cs
@@ -6,12 +6,13 @@
// 9:04 AM
#endregion
-namespace PTIRelianceLib.Firmware.Internal
+namespace PTIRelianceLib.Firmware
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
+ using Flash;
using Protocol;
using Transport;
@@ -98,7 +99,7 @@ public virtual ReturnCodes ExecuteUpdate()
}
// Transmit to target
- var streamer = new RELFwStreamer(Reporter, _mPort);
+ var streamer = new RELStreamer(Reporter, _mPort);
result = streamer.StreamFlashData(packets);
diff --git a/PTIRelianceLib/Firmware/FlashEventMessageArgs.cs b/PTIRelianceLib/Flash/FlashEventMessageArgs.cs
similarity index 95%
rename from PTIRelianceLib/Firmware/FlashEventMessageArgs.cs
rename to PTIRelianceLib/Flash/FlashEventMessageArgs.cs
index 1d2e612..39af9aa 100644
--- a/PTIRelianceLib/Firmware/FlashEventMessageArgs.cs
+++ b/PTIRelianceLib/Flash/FlashEventMessageArgs.cs
@@ -6,7 +6,7 @@
// 9:49 AM
#endregion
-namespace PTIRelianceLib.Firmware
+namespace PTIRelianceLib.Flash
{
///
///
diff --git a/PTIRelianceLib/Firmware/FlashProgressEventArgs.cs b/PTIRelianceLib/Flash/FlashProgressEventArgs.cs
similarity index 95%
rename from PTIRelianceLib/Firmware/FlashProgressEventArgs.cs
rename to PTIRelianceLib/Flash/FlashProgressEventArgs.cs
index 2d14e80..5846fa8 100644
--- a/PTIRelianceLib/Firmware/FlashProgressEventArgs.cs
+++ b/PTIRelianceLib/Flash/FlashProgressEventArgs.cs
@@ -6,7 +6,7 @@
// 9:48 AM
#endregion
-namespace PTIRelianceLib.Firmware
+namespace PTIRelianceLib.Flash
{
///
///
diff --git a/PTIRelianceLib/Firmware/IDataStreamer.cs b/PTIRelianceLib/Flash/IDataStreamer.cs
similarity index 91%
rename from PTIRelianceLib/Firmware/IDataStreamer.cs
rename to PTIRelianceLib/Flash/IDataStreamer.cs
index 1ab5900..e8ddb1d 100644
--- a/PTIRelianceLib/Firmware/IDataStreamer.cs
+++ b/PTIRelianceLib/Flash/IDataStreamer.cs
@@ -6,14 +6,14 @@
// 9:40 AM
#endregion
-namespace PTIRelianceLib.Firmware
+namespace PTIRelianceLib.Flash
{
using System.Collections.Generic;
using Transport;
///
/// This contract describes a class that, given a PacketList and device, can
- /// perform an event-driven flash update.
+ /// perform an event-driven flash write.
///
internal interface IDataStreamer
{
diff --git a/PTIRelianceLib/Firmware/IFlashUpdater.cs b/PTIRelianceLib/Flash/IFlashUpdater.cs
similarity index 75%
rename from PTIRelianceLib/Firmware/IFlashUpdater.cs
rename to PTIRelianceLib/Flash/IFlashUpdater.cs
index 5690c8d..e69573f 100644
--- a/PTIRelianceLib/Firmware/IFlashUpdater.cs
+++ b/PTIRelianceLib/Flash/IFlashUpdater.cs
@@ -6,12 +6,12 @@
// 8:58 AM
#endregion
-namespace PTIRelianceLib.Firmware
+namespace PTIRelianceLib.Flash
{
internal interface IFlashUpdater
{
///
- /// Perform the flash update and return result
+ /// Perform the flash data write and return result
///
/// Result
ReturnCodes ExecuteUpdate();
diff --git a/PTIRelianceLib/Firmware/Internal/RELFwStreamer.cs b/PTIRelianceLib/Flash/RELStreamer.cs
similarity index 84%
rename from PTIRelianceLib/Firmware/Internal/RELFwStreamer.cs
rename to PTIRelianceLib/Flash/RELStreamer.cs
index eec62bb..fcfda40 100644
--- a/PTIRelianceLib/Firmware/Internal/RELFwStreamer.cs
+++ b/PTIRelianceLib/Flash/RELStreamer.cs
@@ -6,7 +6,7 @@
// 9:42 AM
#endregion
-namespace PTIRelianceLib.Firmware.Internal
+namespace PTIRelianceLib.Flash
{
using System;
using System.Collections.Generic;
@@ -19,9 +19,9 @@ namespace PTIRelianceLib.Firmware.Internal
///
/// Handles the low-level protocol for flash updating a Reliance thermal printer
///
- internal class RELFwStreamer : IDataStreamer
+ internal class RELStreamer : IDataStreamer
{
- public RELFwStreamer(IProgressMonitor reporter, IPort port)
+ public RELStreamer(IProgressMonitor reporter, IPort port)
{
Reporter = reporter;
Port = port;
@@ -65,7 +65,7 @@ IPacket Write(IPacket data)
var payload = packetQ.Dequeue();
- Reporter.ReportMessage("Starting flash update process");
+ Reporter.ReportMessage("Starting stream writer update process");
do
{
var resp = Write(payload);
@@ -88,7 +88,7 @@ IPacket Write(IPacket data)
case PacketTypes.Malformed:
case PacketTypes.NegativeAck:
case PacketTypes.SequenceError:
- Reporter.ReportMessage("Flash Error: {0}", resp.GetPacketType());
+ Reporter.ReportMessage("Error: {0}", resp.GetPacketType());
tracker.Status = FlashState.Retry;
++tracker.RetryCount;
break;
@@ -97,7 +97,7 @@ IPacket Write(IPacket data)
tracker.Status = FlashState.Retry;
++tracker.Timeouts;
++tracker.RetryCount;
- Reporter.ReportMessage("Flash Timeout {0}/{1}", tracker.Timeouts, tracker.RetryLimit);
+ Reporter.ReportMessage("Timeout {0}/{1}", tracker.Timeouts, tracker.RetryLimit);
break;
default:
@@ -140,7 +140,7 @@ IPacket Write(IPacket data)
{
// If this happens then that means the file parser is broken.
Reporter.ReportMessage(
- "Error flashing because of invalid payload, contact PTI and report this message");
+ "Error streaming because of invalid payload, contact PTI and report this message");
status = ReturnCodes.OperationAborted;
break;
}
@@ -161,7 +161,7 @@ IPacket Write(IPacket data)
continue;
}
- Reporter.ReportFailure("Too many retries. Aborting flash operation");
+ Reporter.ReportFailure("Too many retries. Aborting operation");
tracker.Status = FlashState.Giveup;
status = ReturnCodes.OperationAborted;
@@ -170,27 +170,7 @@ IPacket Write(IPacket data)
#if DEBUG
Reporter.ReportMessage(tracker.ToString());
#endif
- return status == ReturnCodes.Okay ? CheckChecksum(Write) : status;
- }
-
- ///
- /// Check checksum of target device
- ///
- /// Device transmit function
- /// Return code
- private ReturnCodes CheckChecksum(Func write)
- {
- // Get expected checksum
- var cmd = Port.Package(0x85, 0x11);
- var raw = write(cmd);
- var expectedCsum = PacketParserFactory.Instance.Create().Parse(raw);
-
- // Check actual checksum
- cmd = Port.Package(0x95, 0x11);
- raw = write(cmd);
- var actualCsum = PacketParserFactory.Instance.Create().Parse(raw);
-
- return Equals(expectedCsum, actualCsum) ? ReturnCodes.Okay : ReturnCodes.FlashChecksumMismatch;
+ return status;
}
}
diff --git a/PTIRelianceLib/IO/HIDPort.cs b/PTIRelianceLib/IO/HIDPort.cs
index 1cd6d69..bae4319 100644
--- a/PTIRelianceLib/IO/HIDPort.cs
+++ b/PTIRelianceLib/IO/HIDPort.cs
@@ -9,6 +9,7 @@
namespace PTIRelianceLib.IO
{
+ using System.Data;
using Logging;
using Internal;
using Transport;
@@ -54,8 +55,13 @@ public bool Write(IPacket data)
data.Package();
}
- if (_mHidWrapper?.WriteData(data.GetBytes()) > 0)
+ var payload = data.GetBytes();
+ if (_mHidWrapper?.WriteData(payload) > 0)
{
+ if (Log.IsTraceEnabled())
+ {
+ Log.TraceFormat(">> {0}", payload.ByteArrayToHexString());
+ }
return true;
}
@@ -66,11 +72,18 @@ public bool Write(IPacket data)
public IPacket Read(int timeoutMs)
{
- if (_mHidWrapper == null) return PacketLanguage;
+ if (_mHidWrapper == null)
+ {
+ return PacketLanguage;
+ }
var read = _mHidWrapper.ReadData(timeoutMs);
if (read.Length > 0)
{
+ if (Log.IsTraceEnabled())
+ {
+ Log.TraceFormat("<< {0}", read.ByteArrayToHexString());
+ }
return Package(read);
}
diff --git a/PTIRelianceLib/Imaging/BasePrintLogo.cs b/PTIRelianceLib/Imaging/BasePrintLogo.cs
new file mode 100644
index 0000000..3d8196f
--- /dev/null
+++ b/PTIRelianceLib/Imaging/BasePrintLogo.cs
@@ -0,0 +1,234 @@
+#region Header
+// BasePrintLogo.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:21 PM
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ using System;
+ using System.Drawing;
+ using System.Drawing.Imaging;
+ using System.IO;
+ using Logging;
+
+ ///
+ ///
+ /// A ditherable and resizeable image
+ ///
+ internal class BasePrintLogo : IPrintLogo
+ {
+ private static readonly ILog Log = LogProvider.For();
+
+ ///
+ /// Construct a new logo from source image and scale to ratio.
+ /// Set maxWidthPixels to 0 for full size (no change). If only width or only height
+ /// are provided, the image will be scaled proportionall. If both width and height
+ /// are provided, the scale will done according to your parameters.
+ ///
+ /// File containing image data. Supports all image formats.
+ /// Maximum width in pixels to enforce. 0 to ignore.
+ /// Maximum height in pixels to engore. 0 to ignore.
+ /// Thrown if file is null
+ public BasePrintLogo(BinaryFile file, int maxWidth = 0, int maxHeight = 0)
+ {
+ if (file == null)
+ {
+ throw new ArgumentNullException(nameof(file));
+ }
+
+ // MaxWidth must always be byte aligned (units are in pixels)
+ MaxWidth = maxWidth == 0 ? 0 : maxWidth.RoundUp(8);
+ MaxHeight = maxHeight;
+
+ SetImageData(file);
+ }
+
+ #region Properties
+ ///
+ ///
+ /// Gets the raw image data
+ ///
+ /// Private access, use SetImageData
+ public Bitmap ImageData { get; private set; }
+
+ ///
+ ///
+ /// Gets the dimensions for the current state of the image
+ ///
+ public LogoSize Dimensions { get; private set; }
+
+ ///
+ ///
+ /// Gets the ideal width of this image. The ideal
+ /// width is the scaled width set at instantiation time.
+ ///
+ public int IdealWidth { get; private set; }
+
+ ///
+ ///
+ /// Gets the ideal height of this image. The ideal
+ /// height is the scaled height set at instantiation time.
+ ///
+ public int IdealHeight { get; private set; }
+
+ ///
+ ///
+ /// Gets the enforced max width. Set to 0 to ignore.
+ ///
+ public int MaxHeight { get; }
+
+ ///
+ ///
+ /// Gets the enforced max height. Set to 0 to ignore.
+ ///
+ public int MaxWidth { get; }
+ #endregion
+
+ ///
+ public void ApplyDithering(DitherAlgorithms ditherAlgorithm, byte threshhold = 128)
+ {
+ // Create an instance of the specified dithering ditherAlgorithm
+ var halftoneProcessor = DitherFactory.GetDitherer(ditherAlgorithm, threshhold);
+
+ var bitmap = ImageData;
+
+ // The big grind
+ var dithered = halftoneProcessor.GenerateDithered(bitmap);
+
+ // Update ImageData with dithered result
+ SetImageData(dithered);
+ }
+
+ ///
+ public byte[] ToBuffer()
+ {
+ if (ImageData == null)
+ {
+ return new byte[0];
+ }
+ using (var bitmap = ImageData)
+ {
+ return bitmap.ToLogoBuffer();
+ }
+ }
+
+ ///
+ /// Reads image data into internal image buffer. Scales image down to MaxWidth if required.
+ /// Images smaller than MaxWidth will not be scaled up. Result is stored in ImageData field.
+ /// Final result CRC is calculated and assigned to CRC32 field.
+ ///
+ /// Data to load into image buffer
+ private void SetImageData(BinaryFile file)
+ {
+ if (file.Empty)
+ {
+ return;
+ }
+
+ using(var stream = new MemoryStream(file.GetData()))
+ using (var bitmap = Image.FromStream(stream))
+ {
+ // extract dimensions
+ var actualWidth = bitmap.Width;
+ var actualHeight = bitmap.Height;
+
+
+ // Adjust width if needed
+ if (MaxWidth != 0 && MaxWidth < actualWidth)
+ {
+ IdealWidth = MaxWidth;
+ }
+ else
+ {
+ IdealWidth = actualWidth;
+ }
+
+
+ // Limit height if needed
+ if (MaxHeight != 0 && MaxHeight < actualHeight)
+ {
+ IdealHeight = MaxHeight;
+ }
+ else
+ {
+ IdealHeight = actualHeight;
+ }
+
+ // First, scale width to ideal size
+ if (actualWidth > IdealWidth)
+ {
+ // Scale down
+ var factor = IdealWidth / (float)actualWidth;
+ actualWidth = (int)(factor * actualWidth);
+ actualHeight = (int)(factor * actualHeight);
+ }
+ else if (actualWidth < IdealWidth)
+ {
+ // Scale up
+ var factor = IdealWidth / (float)actualWidth;
+ actualWidth = (int)(factor * actualWidth);
+ actualHeight = (int)(factor * actualHeight);
+ }
+ else
+ {
+ // Width need not be scaled
+ }
+
+
+ // Second scale height -- down only
+ // and don't touch the width, just cut it off
+ if (actualHeight > IdealHeight)
+ {
+ // Scale down
+ var factor = IdealHeight / (float)actualHeight;
+ actualHeight = (int)(factor * actualHeight);
+ }
+
+
+ // Ensure that whatever width we have is byte aligned
+ if (actualWidth % 8 != 0)
+ {
+ actualWidth = actualWidth.RoundUp(8);
+ }
+
+ // Ensure that our width property matches the final scaled width
+ IdealWidth = actualWidth;
+ IdealHeight = actualHeight;
+
+ Log.InfoFormat("Logo sized to: {0},{1}", IdealWidth, IdealHeight);
+
+ using (var resized = new Bitmap(bitmap, new Size(IdealWidth, IdealHeight)))
+ {
+ SetImageData(resized);
+ }
+ }
+ }
+
+ ///
+ /// Safely copy specified bitmap and use as the source data for this logo
+ ///
+ /// Bitmap data to copy and bind to the logo>
+ private void SetImageData(Image bitmap)
+ {
+ // Extract dimension info
+ Dimensions = new LogoSize
+ {
+ Height = bitmap.Height,
+ WidthDots = bitmap.Width
+ };
+ Dimensions.WidthBytes = (int)Math.Ceiling((double)Dimensions.WidthDots / 8);
+ Dimensions.SizeInBytes = bitmap.Height * Dimensions.WidthBytes;
+
+ using (var memory = new MemoryStream())
+ {
+ bitmap.Save(memory, ImageFormat.Png);
+ memory.Position = 0;
+
+ ImageData = new Bitmap(memory);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/Dither.cs b/PTIRelianceLib/Imaging/Dither.cs
new file mode 100644
index 0000000..3b49939
--- /dev/null
+++ b/PTIRelianceLib/Imaging/Dither.cs
@@ -0,0 +1,239 @@
+#region Header
+
+// Dither.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:22 PM
+
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Drawing;
+ using System.Linq;
+
+ ///
+ ///
+ /// Base dithering class
+ ///
+ internal class Dither : IDitherable
+ {
+ // We do not use jagged arrays because I feel that syntax of multi-dimensional
+ // arrays is easier to work with
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
+ "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Member")]
+ private readonly byte[,] _mMatrixPattern;
+
+ private readonly bool _mCanShift;
+ private readonly int _mMatrixOffset;
+
+ ///
+ /// Creates an instance of this dithering class
+ ///
+ /// algorithm in matrix form
+ /// algorithm divisor
+ /// threshhold threshold at which a pixel is considered 'black'
+ /// True to enable use of logical shift instead of multiply operation
+ /// Thrown if matrixPattern is null
+ /// Thrown if divisor is zero
+ public Dither(byte[,] matrixPattern, int divisor, byte threshold, bool shift = false)
+ {
+ if (divisor == 0)
+ {
+ throw new ArgumentException("divisor must be non-zero");
+ }
+
+ _mMatrixPattern = matrixPattern ?? throw new ArgumentNullException(nameof(matrixPattern));
+ RowCount = matrixPattern.GetUpperBound(0) + 1;
+ ColCount = matrixPattern.GetUpperBound(1) + 1;
+
+ Divisor = divisor;
+ Threshold = threshold;
+
+ _mCanShift = shift;
+
+ // Find first non-zero coefficient column in matrix. This value must
+ // always be in the first row of the matrix
+ for (var i = 0; i < ColCount; i++)
+ {
+ if (matrixPattern[0, i] == 0)
+ {
+ continue;
+ }
+
+ _mMatrixOffset = (byte) (i - 1);
+ break;
+ }
+ }
+
+ ///
+ public int RowCount { get; }
+
+ ///
+ public int ColCount { get; }
+
+ ///
+ public int Divisor { get; }
+
+ ///
+ public byte Threshold { get; }
+
+ ///
+ public virtual Bitmap GenerateDithered(Bitmap bitmap)
+ {
+ var bmpBuff = bitmap.ToBuffer();
+ var pixels = bmpBuff.Split(4).Select(pix => new Pixel(pix)).ToList();
+
+ // Convert all bytes into pixels
+
+ // Dither away
+ for (var x = 0; x < bitmap.Height; x++)
+ {
+ for (var y = 0; y < bitmap.Width; y++)
+ {
+ var index = x * bitmap.Width + y;
+ var colored = pixels[index];
+ var grayed = ApplyGrayscale(colored);
+ pixels[index] = grayed;
+
+ ApplySmoothing(pixels, colored, grayed, y, x, bitmap.Width, bitmap.Height);
+ }
+ }
+
+ // Dump results into output
+ var output = new byte[pixels.Count << 2];
+ var j = 0;
+ foreach (var p in pixels)
+ {
+ output[j++] = p.B;
+ output[j++] = p.G;
+ output[j++] = p.R;
+
+ // RT-15 - force alpha to be 0xFF because in optimized mode,
+ // the .NET client may send strange bitmap data.
+ output[j++] = 0xFF;
+ }
+
+ return output.AsBitmap(bitmap.Width, bitmap.Height);
+ }
+
+ ///
+ /// Apply grayscale to this pixel and return result
+ ///
+ /// Pixel to transform
+ /// color reduced (grayscale) pixel
+ protected virtual Pixel ApplyGrayscale(Pixel pix)
+ {
+ // Magic numbers for converting RGB to monochrome space. These achieve a balanced grayscale
+ var grayPoint = (byte) (0.299 * pix.R + 0.587 * pix.G + 0.114 * pix.B);
+
+ // Do not alter the alpha channel, otherwise the entire image may go opaque
+ Pixel grayed;
+ grayed.A = pix.A;
+
+ if (grayPoint < Threshold)
+ {
+ grayed.R = grayed.G = grayed.B = 0;
+ }
+ else
+ {
+ grayed.R = grayed.G = grayed.B = 255;
+ }
+
+ return grayed;
+ }
+
+ ///
+ /// Apply Dithering algorithm
+ ///
+ /// image in row-major order to dither against
+ /// Pixel source
+ /// Pixel source
+ /// column position of Pixel
+ /// y row position of Pixel
+ /// width of imageData
+ /// height of imageData
+ protected virtual void ApplySmoothing(
+ IList imageData,
+ Pixel colored,
+ Pixel grayed,
+ int x,
+ int y,
+ int width,
+ int height)
+ {
+ var redError = colored.R - grayed.R;
+ var blueError = colored.G - grayed.G;
+ var greenError = colored.B - grayed.B;
+
+ for (var row = 0; row < RowCount; row++)
+ {
+ // Convert row to row-major index
+ var ypos = y + row;
+
+ for (var col = 0; col < ColCount; col++)
+ {
+ int coefficient = _mMatrixPattern[row, col];
+
+ // Convert col to row-major index
+ var xpos = x + (col - _mMatrixOffset);
+
+ // Do not process outside of image, 1st row/col, or if pixel is 0
+ if (coefficient == 0 || xpos <= 0 || xpos >= width || ypos <= 0 || ypos >= height)
+ {
+ continue;
+ }
+
+ var offset = ypos * width + xpos;
+ var dithered = imageData[offset];
+
+ int newR, newG, newB;
+
+ // Calculate the dither effect on each color channel
+ if (_mCanShift)
+ {
+ newR = (redError * coefficient) >> Divisor;
+ newG = (greenError * coefficient) >> Divisor;
+ newB = (blueError * coefficient) >> Divisor;
+ }
+ else
+ {
+ newR = (redError * coefficient) / Divisor;
+ newG = (greenError * coefficient) / Divisor;
+ newB = (blueError * coefficient) / Divisor;
+ }
+
+ // Be sure not to overflow
+ dithered.R = SafeByteCast(dithered.R + newR);
+ dithered.G = SafeByteCast(dithered.G + newG);
+ dithered.B = SafeByteCast(dithered.B + newB);
+
+ // Apply new color
+ imageData[offset] = dithered;
+ }
+ }
+ }
+
+ ///
+ /// Returns a integer as a byte and handle any over/underflow
+ ///
+ /// int
+ /// byte
+ private static byte SafeByteCast(int val)
+ {
+ if (val < 0)
+ {
+ val = 0;
+ }
+ else if (val > 255)
+ {
+ val = 255;
+ }
+
+ return (byte) val;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/DitherAlgorithms.cs b/PTIRelianceLib/Imaging/DitherAlgorithms.cs
new file mode 100644
index 0000000..2bda4d2
--- /dev/null
+++ b/PTIRelianceLib/Imaging/DitherAlgorithms.cs
@@ -0,0 +1,69 @@
+#region Header
+// DitherAlgorithms.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:20 PM
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ using System.Runtime.Serialization;
+
+ ///
+ /// List of available dithering algorithms
+ ///
+ public enum DitherAlgorithms
+ {
+ ///
+ /// Dither algorithm reduces color depth but maintains detail accuracy
+ ///
+ [EnumMember(Value = "Dia")]
+ JarvisJudiceNinke,
+ ///
+ /// Better color deptch, lower accuracy
+ ///
+ [EnumMember(Value = "Metis")]
+ FloydSteinberg,
+ ///
+ /// Simple dither
+ ///
+ [EnumMember(Value = "Io")]
+ Atkinson,
+ ///
+ /// Simple dither
+ ///
+ [EnumMember(Value = "Leda")]
+ Stucki,
+ ///
+ /// Raw image
+ ///
+ [EnumMember(Value = "Adrastea")]
+ None,
+ ///
+ /// Simple dither
+ ///
+ [EnumMember(Value = "Carpo")]
+ FloydSteinbergFalse,
+ ///
+ /// Decent dithering
+ ///
+ [EnumMember(Value = "Europa")]
+ Sierra,
+ ///
+ /// Slightly better Sierra kernel
+ ///
+ [EnumMember(Value = "Thebe")]
+ Sierra2,
+ ///
+ /// Fast Sierra kernel
+ ///
+ [EnumMember(Value = "Ganymede")]
+ SierraLite,
+ ///
+ /// Decent dithering
+ ///
+ [EnumMember(Value = "Callisto")]
+ Burkes,
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/DitherFactory.cs b/PTIRelianceLib/Imaging/DitherFactory.cs
new file mode 100644
index 0000000..f31e01e
--- /dev/null
+++ b/PTIRelianceLib/Imaging/DitherFactory.cs
@@ -0,0 +1,112 @@
+#region Header
+
+// DitherFactory.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:26 PM
+
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ ///
+ /// Producer of ditherable data
+ ///
+ internal static class DitherFactory
+ {
+ ///
+ /// Returns the ditherable implementation for the specified ditherAlgorithm
+ ///
+ /// Algorithm to build
+ /// Threshold for greyscale
+ /// New ditherale instance
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
+ "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Body")]
+ public static IDitherable GetDitherer(DitherAlgorithms ditherAlgorithm, byte threshold = 128)
+ {
+ switch (ditherAlgorithm)
+ {
+ case DitherAlgorithms.Atkinson:
+ // 0,0,1,1,1,1,1,0,0,1,0,0
+ return new Dither(new byte[,]
+ {
+ {0, 0, 1, 1},
+ {1, 1, 1, 0},
+ {0, 1, 0, 0}
+ }, 3, threshold, true);
+
+ case DitherAlgorithms.Burkes:
+ // 0,0,0,8,4,2,4,8,4,2
+ return new Dither(new byte[,]
+ {
+ {0, 0, 0, 8, 4},
+ {2, 4, 8, 4, 2},
+ }, 5, threshold, true);
+
+ case DitherAlgorithms.FloydSteinberg:
+ // 0,0,7,3,5,1
+ return new Dither(new byte[,]
+ {
+ {0, 0, 7},
+ {3, 5, 1},
+ }, 4, threshold, true);
+
+ case DitherAlgorithms.FloydSteinbergFalse:
+ // 0,3,3,2
+ return new Dither(new byte[,]
+ {
+ {0, 3},
+ {3, 2},
+ }, 3, threshold, true);
+
+ case DitherAlgorithms.JarvisJudiceNinke:
+ // 0,0,0,7,5,3,5,7,5,3,1,3,5,3,1
+ return new Dither(new byte[,]
+ {
+ {0, 0, 0, 7, 5},
+ {3, 5, 7, 5, 3},
+ {1, 3, 5, 3, 1}
+ }, 48, threshold);
+
+ case DitherAlgorithms.Sierra:
+ // 0,0,0,5,3,2,4,5,4,2,0,2,3,2,0
+ return new Dither(new byte[,]
+ {
+ {0, 0, 0, 5, 3},
+ {2, 4, 5, 4, 2},
+ {0, 2, 3, 2, 0},
+ }, 5, threshold, true);
+
+ case DitherAlgorithms.Sierra2:
+ // 0,0,0,4,3,1,2,3,2,1
+ return new Dither(new byte[,]
+ {
+ {0, 0, 0, 4, 3},
+ {1, 2, 3, 2, 1},
+ }, 4, threshold, true);
+
+ case DitherAlgorithms.SierraLite:
+ // 0,0,2,1,1,0
+ return new Dither(new byte[,]
+ {
+ {0, 0, 2},
+ {1, 1, 0},
+ }, 2, threshold, true);
+
+ case DitherAlgorithms.Stucki:
+ // 0,0,0,8,4,2,4,8,4,2,1,2,4,2,1
+ return new Dither(new byte[,]
+ {
+ {0, 0, 0, 8, 4},
+ {2, 4, 8, 4, 2},
+ {1, 2, 4, 2, 1},
+ }, 42, threshold);
+
+ default:
+ // We need to at least make it 1bpp bitmap otherwise phoenix will have garbage.
+ return new OneBpp(threshold);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/IDitherable.cs b/PTIRelianceLib/Imaging/IDitherable.cs
new file mode 100644
index 0000000..220994e
--- /dev/null
+++ b/PTIRelianceLib/Imaging/IDitherable.cs
@@ -0,0 +1,38 @@
+namespace PTIRelianceLib.Imaging
+{
+ using System.Drawing;
+
+ ///
+ /// A datastructure that can be dithered
+ ///
+ internal interface IDitherable
+ {
+ ///
+ /// Number of rows in algorithm matrix
+ ///
+ int RowCount { get; }
+
+ ///
+ /// Numbers of columns in algorithm matrix
+ ///
+ int ColCount { get; }
+
+ ///
+ /// Algorithm's divisor
+ ///
+ int Divisor { get; }
+
+ ///
+ /// Black or white threshold limit
+ ///
+ byte Threshold { get; }
+
+ ///
+ /// Generates a new, dithered version of the input bitmap using the configured
+ /// algorithm parameters.
+ ///
+ /// Input bitmap
+ ///
+ Bitmap GenerateDithered(Bitmap input);
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/IPrintLogo.cs b/PTIRelianceLib/Imaging/IPrintLogo.cs
new file mode 100644
index 0000000..6301d1f
--- /dev/null
+++ b/PTIRelianceLib/Imaging/IPrintLogo.cs
@@ -0,0 +1,66 @@
+#region Header
+
+// IPrintLogo.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:18 PM
+
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ using System.Drawing;
+
+ ///
+ /// Constract defines an image format that supports standard
+ /// transforms and manipulations
+ ///
+ internal interface IPrintLogo
+ {
+ ///
+ /// Apply a dithering ditherAlgorithm
+ ///
+ /// Algorithm
+ /// Pixel on/off threshhold
+ void ApplyDithering(DitherAlgorithms ditherAlgorithm, byte threshhold);
+
+ ///
+ /// Returns image as raw array with correct bit ordering
+ /// for transmission to target. If data is not available
+ /// an empty buffer will be returned.
+ ///
+ /// byte[]
+ byte[] ToBuffer();
+
+ ///
+ /// Returns the dimensions of this image
+ ///
+ LogoSize Dimensions { get; }
+
+ ///
+ /// Returns the ideal height in pixels that was specified
+ ///
+ int IdealHeight { get; }
+
+ ///
+ /// Returnst the ideal width in pixels that was specified
+ ///
+ int IdealWidth { get; }
+
+ ///
+ /// Returns the maximum height that was specified when this image was created
+ ///
+ int MaxHeight { get; }
+
+ ///
+ /// Returns the maximum width that was specified when this image was crearted
+ ///
+ int MaxWidth { get; }
+
+ ///
+ /// Backing image data
+ ///
+ Bitmap ImageData { get; }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/ImageExt.cs b/PTIRelianceLib/Imaging/ImageExt.cs
new file mode 100644
index 0000000..0bc5718
--- /dev/null
+++ b/PTIRelianceLib/Imaging/ImageExt.cs
@@ -0,0 +1,252 @@
+#region Header
+
+// ImageExt.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:28 PM
+
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Drawing;
+ using System.Drawing.Imaging;
+ using System.Linq;
+ using System.Runtime.InteropServices;
+
+ ///
+ /// Helper extensions for working with images
+ ///
+ internal static class ImageExt
+ {
+ ///
+ /// Converts into a row-major byte array. If there is no image data
+ /// or the image is empty, the resulting array will be empty (zero length).
+ ///
+ /// byte[]
+ public static byte[] ToBuffer(this Bitmap bitmap)
+ {
+ if (bitmap == null || bitmap.Size.IsEmpty)
+ {
+ return new byte[0];
+ }
+
+ BitmapData bitmapData = null;
+
+ // This rectangle selects the entirety of the source bitmap for locking bits into memory
+ var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
+
+ try
+ {
+ // Acquire a lock on the image data so we can extra into our own byte stream
+ // Note: Currently only supports data as 32bit, 4 channel 8-bit color
+ bitmapData = bitmap.LockBits(
+ rect,
+ ImageLockMode.ReadOnly,
+ PixelFormat.Format32bppPArgb);
+
+ // Create the output buffer
+ var length = Math.Abs(bitmapData.Stride) * bitmapData.Height;
+ var results = new byte[length];
+
+ // Copy from unmanaged to managed memory
+ var ptr = bitmapData.Scan0;
+ Marshal.Copy(ptr, results, 0, length);
+
+ return results;
+ }
+ finally
+ {
+ if (bitmapData != null)
+ {
+ bitmap.UnlockBits(bitmapData);
+ }
+ }
+ }
+
+ ///
+ /// Converts this buffer into a 32bpp ARGB bitmap. The width and
+ /// height parameters must be the sum product of the imageData length.
+ /// You specifcy a buffer of length 1000, and a width of 10, the height
+ /// must be 100.
+ ///
+ ///
+ /// Width of resulting bitmap in bytes
+ /// Height of resulting bitmap in bytes
+ /// Bitmap instance. Be sure to dispose of it when you're done.
+ public static Bitmap AsBitmap(this byte[] imageData, int width, int height)
+ {
+ var result = new Bitmap(width, height, PixelFormat.Format32bppArgb);
+
+ // Since our data is provided in byte-expanded pixel mode, adjust the width to 4x
+ width = result.Width << 2;
+
+
+ // Timing benchmarks show that safe vs. unsafe has zero impact on our use case
+ // Both safe and unsafe, when tested through logo write, get a 13.2Kbps throughput
+ // This bitmap operation is negligable compared to how slowly the flash data is written.
+#if SAFE
+ // Iterate through rows and columns
+ for (var row = 0; row < height; row++)
+ {
+ // Source is ARGB format but dest is 1bpp so user two different indexers
+ for (int col = 0, pixCol=0; col < width; col += 4, ++pixCol)
+ {
+ var index = row * width + col;
+ var color = imageData[index++] |
+ imageData[index++] << 8 |
+ imageData[index++] << 16 |
+ imageData[index] << 24;
+
+ // Set pixel
+ result.SetPixel(pixCol, row, Color.FromArgb(color));
+ }
+ }
+
+#else
+ // Lock the entire bitmap
+ var bitmapData = result.LockBits(
+ new Rectangle(0, 0, width, height),
+ ImageLockMode.ReadWrite,
+ PixelFormat.Format32bppArgb);
+
+ unsafe
+ {
+ // Get a pointer to the beginning of the pixel data region
+ // The upper-left corner
+ var pixelPtr = (int*)bitmapData.Scan0;
+
+ // Iterate through rows and columns
+ for (var row = 0; row < height; row++)
+ {
+ for (var col = 0; col < width; col += 4)
+ {
+ var index = row * width + col;
+ var color = imageData[index++] |
+ imageData[index++] << 8 |
+ imageData[index++] << 16 |
+ imageData[index] << 24;
+
+ *pixelPtr++ = color;
+ }
+ }
+ }
+
+ // Unlock the bitmap
+ result.UnlockBits(bitmapData);
+
+#endif
+ return result;
+ }
+
+ ///
+ /// Inverts the pixels of this bitmap in place. Ignores alpha channel.
+ ///
+ ///
+ public static void InvertColorChannels(this Bitmap bitmapImage)
+ {
+ var rect = new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height);
+
+ var bmpRo = bitmapImage.LockBits(
+ rect,
+ ImageLockMode.ReadOnly,
+ PixelFormat.Format32bppPArgb);
+
+ var bmpLen = bmpRo.Stride * bmpRo.Height;
+ var bitmapBgra = new byte[bmpLen];
+ Marshal.Copy(bmpRo.Scan0, bitmapBgra, 0, bmpLen);
+ bitmapImage.UnlockBits(bmpRo);
+
+ // Copy ONLY the color channels and invert - black->white, white->black
+ for (var i = 0; i < bmpLen; i += 4)
+ {
+ bitmapBgra[i] = (byte) (255 - bitmapBgra[i]);
+ bitmapBgra[i + 1] = (byte) (255 - bitmapBgra[i + 1]);
+ bitmapBgra[i + 2] = (byte) (255 - bitmapBgra[i + 2]);
+ }
+
+ var bmpWo = bitmapImage.LockBits(
+ rect,
+ ImageLockMode.WriteOnly,
+ PixelFormat.Format32bppPArgb);
+
+ Marshal.Copy(bitmapBgra, 0, bmpWo.Scan0, bmpLen);
+ bitmapImage.UnlockBits(bmpWo);
+ }
+
+ ///
+ /// Creates an MSB ordered, bit-reversed buffer of the logo data located in this bitmap.
+ /// The input data's pixels are reduced into an 8bpp image. That means that 8 PC bitmap
+ /// pixels are reduced into 8 bits in the resulting buffer. These bits are delivered in
+ /// reverse order (0x80: 0b10000000 -> 0x00000001).
+ /// If ANY of the pixel's RGB values are non-zero, the corresponding bit index will be set
+ /// in the output buffer. The alpha channel has no effect on the output buffer.
+ /// The bitmap is read from the top left of the bitmap to the bottom right.
+ ///
+ /// Bitmap
+ /// MSB ordered, bit-reversed Buffer
+ public static byte[] ToLogoBuffer(this Bitmap bitmapImage)
+ {
+ // Define an area in which the bitmap bits will be locked in managed memory
+ var rect = new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height);
+ var bmpReadOnly = bitmapImage.LockBits(
+ rect,
+ ImageLockMode.ReadOnly,
+ PixelFormat.Format32bppPArgb);
+
+
+ var bmpLen = bmpReadOnly.Stride * bmpReadOnly.Height;
+ var bmpChannels = new byte[bmpLen];
+ Marshal.Copy(bmpReadOnly.Scan0, bmpChannels, 0, bmpLen);
+ bitmapImage.UnlockBits(bmpReadOnly);
+
+
+ // Split into bitmap into N number of rows where each row is
+ // as wide as the input bitmap's pixel count.
+ var rowWidth = bmpReadOnly.Width;
+ var pixels = bmpChannels.Split(bmpReadOnly.Stride);
+ var byteWidth = (int) Math.Ceiling((double) rowWidth / 8);
+ var tmpBuff = new List();
+
+ // Result buffer - Use array because we use | operator in byte reversal
+ var outBuffer = new byte[byteWidth * bmpReadOnly.Height];
+ var outIndex = 0;
+
+ // Read 1 row (aka stride) or 4-byte pixels at a time
+ foreach (var row in pixels)
+ {
+ // Read 1st of every 4 bytes from source[colorIndex] in order into temp buffer
+ tmpBuff.AddRange(row.Split(4).Select(pix => new Pixel(pix)));
+
+ // Reverse the pixel byte, 0b10000010 -> 0x01000001
+ for (var set = 0; set < byteWidth; set++)
+ {
+ // Max bit tells us what bit to start shifting from
+ var maxBit = Math.Min(7, rowWidth - (set * 8) - 1);
+
+ // Read up to 8 bytes at a time in LSB->MSB so they are transmitted MSB->LSB to printer
+ // set offset groups into bytes
+ for (int b = maxBit, bb = 0; b >= 0; b--, bb++)
+ {
+ // Read rows right to left
+ var px = tmpBuff[b + (set * 8)];
+
+ // Firmware black == 1, White == 0
+ outBuffer[outIndex] |= (byte) ((px.IsNotWhite() ? 1 : 0) << bb);
+ }
+
+ // Increments after every byte
+ outIndex++;
+ }
+
+ tmpBuff.Clear();
+ }
+
+
+ return outBuffer;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/LogoSize.cs b/PTIRelianceLib/Imaging/LogoSize.cs
new file mode 100644
index 0000000..80e17fd
--- /dev/null
+++ b/PTIRelianceLib/Imaging/LogoSize.cs
@@ -0,0 +1,47 @@
+#region Header
+
+// LogoSize.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:20 PM
+
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ ///
+ /// Describes the dimensions of a logo in both dots (pixels) and bytes
+ ///
+ internal class LogoSize
+ {
+ ///
+ /// Width of image in bytes
+ ///
+ public int WidthBytes { get; set; }
+
+ ///
+ /// Width of image in thermal printer dots (def. 203 DPI)
+ ///
+ public int WidthDots { get; set; }
+
+ ///
+ /// Height of image in dots (and bytes, they're the same metric here)
+ ///
+ public int Height { get; set; }
+
+ ///
+ /// Gets or sets the size in bytes for this logo
+ ///
+ public int SizeInBytes { get; set; }
+
+ ///
+ /// Returns dimension string as WidthxHeight
+ ///
+ ///
+ public object GetBitmapSizeString()
+ {
+ return string.Format("{0}x{1}", WidthDots, Height);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/LogoStorageConfig.cs b/PTIRelianceLib/Imaging/LogoStorageConfig.cs
new file mode 100644
index 0000000..970adf8
--- /dev/null
+++ b/PTIRelianceLib/Imaging/LogoStorageConfig.cs
@@ -0,0 +1,47 @@
+#region Header
+// LogoStorageConfig.cs
+// PTIRelianceLib
+// Cory Todd
+// 14-06-2018
+// 10:05 AM
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ ///
+ /// Storage configuration for Reliance logos
+ ///
+ public class LogoStorageConfig
+ {
+ ///
+ /// Gets or Sets the maximum width in pixels for images being stored.
+ /// Any images with a width greater than this value will be scaled down
+ /// proportionally. For best result, it is recommended that you provide
+ /// images at your desired size so you have better control over scaling.
+ /// Default value: 640 pixels (80mm)
+ ///
+ /// Maximum width in pixels of image to allow
+ public int MaxPixelWidth { get; set; } = 640;
+
+ ///
+ /// Dithering threshold value determines at which magitude a pixel
+ /// swithes from black to white or white to black.
+ /// Default: 127 (gray)
+ ///
+ /// Dithering cutoff value
+ public byte Threshold { get; set; } = 127;
+
+ ///
+ /// Dithering implementation to utilize for image.
+ /// Default: None, no dithering performed
+ ///
+ /// Algorithm to apply
+ public DitherAlgorithms Algorithm { get; set; } = DitherAlgorithms.None;
+
+ ///
+ /// Returns default logo storage options
+ ///
+ /// Default configuration
+ public static LogoStorageConfig Default => new LogoStorageConfig();
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/OneBPP.cs b/PTIRelianceLib/Imaging/OneBPP.cs
new file mode 100644
index 0000000..628dddb
--- /dev/null
+++ b/PTIRelianceLib/Imaging/OneBPP.cs
@@ -0,0 +1,27 @@
+namespace PTIRelianceLib.Imaging
+{
+ using System.Drawing;
+ using System.Drawing.Imaging;
+
+ ///
+ ///
+ /// One bpp converts the image to a 1 bit per pixel image
+ ///
+ internal class OneBpp : Dither
+ {
+ public OneBpp(byte threshold)
+ : base(new byte[,] { { 0 } }, 1, threshold)
+ { }
+
+ ///
+ ///
+ /// Override returns a 1bpp copy of the input bitmap
+ ///
+ ///
+ ///
+ public override Bitmap GenerateDithered(Bitmap bitmap)
+ {
+ return new Bitmap(bitmap);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Imaging/Pixel.cs b/PTIRelianceLib/Imaging/Pixel.cs
new file mode 100644
index 0000000..e7f0332
--- /dev/null
+++ b/PTIRelianceLib/Imaging/Pixel.cs
@@ -0,0 +1,69 @@
+#region Header
+// Pixel.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:19 PM
+#endregion
+
+namespace PTIRelianceLib.Imaging
+{
+ ///
+ /// An ARGB pixel
+ ///
+ internal struct Pixel
+ {
+ public byte B; //< Blue
+ public byte G; //< Green
+ public byte R; //< Red
+ public byte A; //< Alpha
+
+ ///
+ ///
+ /// Unchecked constructor expects a 4-byte input buffer
+ /// Order: ARGB
+ ///
+ ///
+ internal Pixel(byte[] slice4)
+ : this(slice4[3], slice4[2], slice4[1], slice4[0])
+ { }
+
+ ///
+ ///
+ /// Ordered constructor: ARGB
+ ///
+ /// Alpha
+ /// Red
+ /// Green
+ /// Blue
+ public Pixel(int a, int r, int g, int b)
+ : this((byte)a, (byte)r, (byte)g, (byte)b)
+ { }
+
+ ///
+ /// Ordered constructor: ARGB
+ ///
+ /// Alpha
+ /// Red
+ /// Green
+ /// Blue
+ public Pixel(byte a, byte r, byte g, byte b)
+ {
+ A = a;
+ R = r;
+ G = g;
+ B = b;
+ }
+
+ ///
+ /// White is defined as 255,255,255 on the RGB pallete. Returns
+ /// true if any RGB value is not 255.
+ ///
+ /// True if this pixel is non-white
+ internal bool IsNotWhite()
+ {
+ var m = (A + R + G) / 3;
+ return m != 255;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Internal/Extensions.cs b/PTIRelianceLib/Internal/Extensions.cs
index 829344f..66864d8 100644
--- a/PTIRelianceLib/Internal/Extensions.cs
+++ b/PTIRelianceLib/Internal/Extensions.cs
@@ -250,5 +250,60 @@ public static string GetEnumName(this Enum enumVal)
? ((EnumMemberAttribute)attributes[0]).Value
: enumVal.ToString();
}
+
+ ///
+ /// Rounds this integer to the nearest positive multiple of N
+ ///
+ /// Value to round
+ /// Multiple to round to
+ ///
+ public static int RoundUp(this int i, int N)
+ {
+ return (int)RoundUp(i, (uint)N);
+ }
+
+ ///
+ /// Rounds this integer to the nearest positive multiple of N
+ ///
+ /// Value to round
+ /// Multiple to round to
+ ///
+ public static long RoundUp(this long i, int N)
+ {
+ return RoundUp(i, (uint)N);
+ }
+
+ ///
+ /// Rounds this integer to the nearest positive multiple of N
+ ///
+ /// Value to round
+ /// Multiple to round to
+ ///
+ public static uint RoundUp(this uint i, int N)
+ {
+ return (uint)RoundUp(i, (uint)N);
+ }
+
+
+ ///
+ /// Rounds this integer to the nearest positive multiple of N
+ ///
+ /// Value to round
+ /// Multiple to round to
+ ///
+ private static long RoundUp(long i, uint N)
+ {
+ if (N == 0)
+ {
+ return 0;
+ }
+
+ if (i == 0)
+ {
+ return N;
+ }
+
+ return (long)(Math.Ceiling(Math.Abs(i) / (double)N) * N);
+ }
}
}
diff --git a/PTIRelianceLib/Library.cs b/PTIRelianceLib/Library.cs
index 6de88db..0814e2b 100644
--- a/PTIRelianceLib/Library.cs
+++ b/PTIRelianceLib/Library.cs
@@ -20,6 +20,7 @@ public static class Library
/// application. Altering this property or the contents of this property
/// during runtime may result in undefined behavior.
///
+ /// Current Library Options
public static LibraryOptions Options = LibraryOptions.Default;
}
}
diff --git a/PTIRelianceLib/LibraryOptions.cs b/PTIRelianceLib/LibraryOptions.cs
index a6b2747..3d6a01d 100644
--- a/PTIRelianceLib/LibraryOptions.cs
+++ b/PTIRelianceLib/LibraryOptions.cs
@@ -29,6 +29,7 @@ public class LibraryOptions
/// that fresh HID devices are getting discovered on enumeration.
/// Default: 1000
///
+ /// Hid reconnect delay is ms
public int HidReconnectDelayMs { get; set; }
///
@@ -40,11 +41,13 @@ public class LibraryOptions
/// during the flash update process.
/// Default: false
///
+ /// Flag controls if Hid structures are cleaned on error
public bool HidFlushStructuresOnEnumError { get; set; }
///
/// Returns the default library options for this library.
///
+ /// Default library options
public static LibraryOptions Default => new LibraryOptions
{
HidCleanupDelayMs = 0,
@@ -55,6 +58,7 @@ public class LibraryOptions
///
/// Returns the library options that work well for Debian Stretch Docker images
///
+ /// Options comptaible with Linux Stretch Docker containers
public static LibraryOptions DockerLinuxStretch => new LibraryOptions
{
HidCleanupDelayMs = 250,
diff --git a/PTIRelianceLib/Logo/RELLogoBank.cs b/PTIRelianceLib/Logo/RELLogoBank.cs
new file mode 100644
index 0000000..a8a28da
--- /dev/null
+++ b/PTIRelianceLib/Logo/RELLogoBank.cs
@@ -0,0 +1,77 @@
+#region Header
+// RELLogoBank.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:11 PM
+#endregion
+
+namespace PTIRelianceLib.Logo
+{
+ using System.Collections.Generic;
+ using Imaging;
+
+ ///
+ /// The logo bank describes where the actual logos can be found and how large they are
+ ///
+ internal class RELLogoBank
+ {
+ ///
+ /// See logos.h in firmware
+ /// #define LOGO_EXTERNAL_FLASH_START_0 (1966080)
+ /// Logo 0 Start addr. Needs to be flash sector size aligned
+ ///
+ private const uint StartAddress = 0x001E0000;
+
+ ///
+ /// TODO make dynamic for reliance logo size
+ ///
+ private const int BankSize = 131072;
+
+ ///
+ /// Returns the total size in bytes of the logo bank
+ ///
+ ///
+ public int TotalBankSize => BankSize;
+
+ ///
+ /// Gets the BankSize in KB (1024 bytes)
+ ///
+ public int TotalBankSizeKb => BankSize / 1024;
+
+ ///
+ /// Builds and returns a logo header based on the current state of this bank. The current
+ /// state of the logos will be stored, do not modify logos after calling this method else
+ /// your changes will not be applied.
+ ///
+ /// Ordered list of logos to add
+ /// Liust of logo headers in same order as logos list
+ internal IList MakeHeaders(IList logos)
+ {
+ var nextAddr = StartAddress;
+
+ var result = new List();
+
+ byte index = 0;
+ foreach (var logo in logos)
+ {
+ var header = new RELLogoHeader()
+ {
+ Index = index++,
+ LeftMargin = 0,
+ Name = "".PadRight(10, ' '),
+ Size = (uint) logo.Dimensions.SizeInBytes,
+ HeightDots = (ushort) logo.Dimensions.Height,
+ WidthBytes = (ushort) logo.Dimensions.WidthBytes,
+ StartAddr = nextAddr,
+ LogoData = logo.ToBuffer(),
+ };
+
+ result.Add(header);
+ nextAddr = (nextAddr + header.Size).RoundUp(RELLogoHeader.Alignment);
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Logo/RELLogoHeader.cs b/PTIRelianceLib/Logo/RELLogoHeader.cs
new file mode 100644
index 0000000..1a8da55
--- /dev/null
+++ b/PTIRelianceLib/Logo/RELLogoHeader.cs
@@ -0,0 +1,117 @@
+#region Header
+// RELLogoHeader.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 1:11 PM
+#endregion
+
+namespace PTIRelianceLib.Logo
+{
+ using System.Collections.Generic;
+ using Transport;
+
+ ///
+ ///
+ /// The logo header describes the location of a single logo
+ ///
+ internal class RELLogoHeader : IParseable
+ {
+ ///
+ /// External flash chip alignment
+ /// 4096 bytes (4K)
+ ///
+ public static int Alignment = 0x1000;
+
+ ///
+ /// The index the is stored in the bank
+ ///
+ public byte Index;
+
+ public RELLogoHeader()
+ {
+ Padding2 = 0;
+ Padding1 = 0;
+ }
+
+ ///
+ /// Name of logo that can be up to 10 bytes in length
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Address that start of the logo is. Start = top left of image.
+ ///
+ public uint StartAddr { get; set; }
+
+ ///
+ /// Size in bytes of the logo
+ ///
+ public uint Size { get; set; }
+
+ ///
+ /// Width in bytes of the logo
+ ///
+ public ushort WidthBytes { get; set; }
+
+ ///
+ /// Height of the logo
+ ///
+ public ushort HeightDots { get; set; }
+
+ ///
+ /// Number of bytes for the left margin.
+ ///
+ public byte LeftMargin { get; set; }
+
+ ///
+ /// Struct packing
+ ///
+ private byte Padding1 { get; }
+
+ ///
+ /// Struct packing
+ ///
+ private ushort Padding2 { get; }
+
+ ///
+ /// For convenience, store a copy of the data this header
+ /// is describing. This is not used in serialization.
+ ///
+ public byte[] LogoData { get; set; }
+
+ ///
+ public byte[] Serialize()
+ {
+ var dst = new List {Index};
+
+ if (string.IsNullOrEmpty(Name))
+ {
+ Name = new string(' ', 10);
+ }
+
+ // Enforce 10 byte length, space pad, and set null terminator on the end
+ if (Name.Length > 10)
+ {
+ Name = Name.Substring(0, 10);
+ }
+
+ Name = Name.PadRight(10, ' ');
+ var buff = Name.ToCharArray();
+ buff[9] = '\0';
+ Name = new string(buff);
+ dst.AddRange(System.Text.Encoding.ASCII.GetBytes(Name));
+
+ dst.Add(0);
+ dst.AddRange(StartAddr.ToBytesBE());
+ dst.AddRange(Size.ToBytesBE());
+ dst.AddRange(WidthBytes.ToBytesBE());
+ dst.AddRange(HeightDots.ToBytesBE());
+ dst.Add(LeftMargin);
+ dst.Add(Padding1);
+ dst.AddRange(Padding2.ToBytesBE());
+
+ return dst.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Logo/RELLogoParser.cs b/PTIRelianceLib/Logo/RELLogoParser.cs
new file mode 100644
index 0000000..2e1cd0d
--- /dev/null
+++ b/PTIRelianceLib/Logo/RELLogoParser.cs
@@ -0,0 +1,140 @@
+#region Header
+
+// RELLogoParser.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 12:47 PM
+
+#endregion
+
+namespace PTIRelianceLib.Logo
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Security.Cryptography;
+ using Protocol;
+
+ ///
+ /// Reads in an image and processes data into a packet list
+ ///
+ internal class RELLogoParser
+ {
+ ///
+ /// Logo data type code
+ ///
+ private const byte LogoType = 0x03;
+
+ ///
+ /// External flash chip alignment
+ /// 4096 bytes (4K)
+ ///
+ private const int Alignment = 0x1000;
+
+ ///
+ /// USB HID max packet size is controlled by firmware in Reliance
+ ///
+ private const int MaxPacketSize = 0x20;
+
+ ///
+ /// Parse file as a logo
+ ///
+ /// Logo data to parse
+ /// Packetized list or null on error
+ public IList Parse(uint startAddress, byte[] data)
+ {
+ if (data == null || data.Length == 0)
+ {
+ return null;
+ }
+
+ var repacked = new List();
+ var flashCmd = new[] {(byte) RelianceCommands.DataWriteRequest, LogoType};
+
+ // data portion of the packet. There are 4 bytes of packaging
+ const int dataLen = MaxPacketSize - 4;
+
+ // Start requesting flash permission at this address
+ var addr = startAddress;
+
+ // There is a chance we will not have an even count of FLASH_BLOCK_SIZE
+ // byte in the stream. Count the bytes and check for this condition. If
+ // detected, we must random-fill the block to round out the data.
+
+ // read into a block that will be crc'd
+ using (var stream = new MemoryStream(data))
+ using (var reader = new BinaryReader(stream))
+ {
+ while (stream.Position != stream.Length)
+ {
+ var crcBlock = new byte[Alignment];
+ var readThisMuch = Alignment;
+
+ // Detect if there is enough left to read. Use the stream since
+ // it is shared with the reader.
+ if (stream.Length - stream.Position < Alignment)
+ {
+ readThisMuch = (int) (stream.Length - stream.Position);
+ }
+
+ // Random fill if needed
+ if (reader.Read(crcBlock, 0, readThisMuch) != Alignment)
+ {
+ var fillMe = new byte[Alignment - readThisMuch];
+ using (var rnd = new RNGCryptoServiceProvider())
+ {
+ rnd.GetBytes(fillMe);
+ }
+
+ Array.Copy(fillMe, 0, crcBlock, readThisMuch, fillMe.Length);
+ }
+
+
+ // Checksum that whole thing
+ var csum = Crc32.ComputeChecksum(crcBlock);
+
+ // Buffer for the flash permission request packet
+ // Hold however many bytes are in flash command
+ // and the adress + checksum (4 bytes each)
+ using (var req = new MemoryStream(flashCmd.Length + 8))
+ {
+ req.Write(flashCmd, 0, flashCmd.Length);
+
+ // Create the flash permission request by injecting address and then checksum buffers
+ var tmp = addr.ToBytesBE();
+ req.Write(tmp, 0, tmp.Length);
+
+ tmp = csum.ToBytesBE();
+ req.Write(tmp, 0, tmp.Length);
+
+ // req packet is now complete.
+ req.Position = 0;
+ repacked.Add(req.GetBuffer());
+ }
+
+ // Now split the crcBlock into HID compatible packet lengths.
+ // The protocol specifies that we must provide the sequence number
+ // inside the packet. This must never exceed 255 sequences per block.
+ // Meaning, we cannot use less than 9 bytes in a flash payload packet.
+ byte seq = 0;
+ foreach (var segment in crcBlock.Split(dataLen))
+ {
+ // We're inserting 2 bytes in front of the segment payload
+ var payload = new byte[dataLen + 2];
+ payload[0] = (byte) RelianceCommands.FlashDo;
+ payload[1] = seq++;
+
+ Array.Copy(segment, 0, payload, 2, segment.Length);
+ repacked.Add(payload);
+ }
+
+ // Move address to next block
+ addr += Alignment;
+ }
+ }
+
+ return repacked;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Logo/RELLogoUpdater.cs b/PTIRelianceLib/Logo/RELLogoUpdater.cs
new file mode 100644
index 0000000..368803e
--- /dev/null
+++ b/PTIRelianceLib/Logo/RELLogoUpdater.cs
@@ -0,0 +1,59 @@
+#region Header
+// RELLogoUpdater.cs
+// PTIRelianceLib
+// Cory Todd
+// 13-06-2018
+// 12:47 PM
+#endregion
+
+namespace PTIRelianceLib.Logo
+{
+ using System;
+ using System.Linq;
+ using Flash;
+ using Transport;
+
+ ///
+ /// Used to write a set of logos to external flash
+ ///
+ internal class RELLogoUpdater : IFlashUpdater
+ {
+ private readonly RELLogoHeader _mLogoHeader;
+
+ private readonly IPort _mPort;
+
+ ///
+ /// Constructs a new flasher
+ ///
+ /// Port to write to
+ /// Data to write
+ /// Thrown if port or fileToFlash are null
+ public RELLogoUpdater(IPort port, RELLogoHeader header)
+ {
+ _mPort = port ?? throw new ArgumentNullException(nameof(port));
+ _mLogoHeader = header ?? throw new ArgumentNullException(nameof(header));
+ }
+
+ ///
+ /// Gets or Sets reporter
+ ///
+ public IProgressMonitor Reporter { get; set; }
+
+ ///
+ public ReturnCodes ExecuteUpdate()
+ {
+ var logoParser = new RELLogoParser();
+ var parsed = logoParser.Parse(_mLogoHeader.StartAddr, _mLogoHeader.LogoData);
+ if (parsed == null)
+ {
+ return ReturnCodes.FlashFileInvalid;
+ }
+
+ // Repack data into packets
+ var packets = parsed.Select(p => _mPort.Package(p)).ToList();
+
+ var updater = new RELStreamer(Reporter, _mPort);
+ return updater.StreamFlashData(packets);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/PTIRelianceLib.csproj b/PTIRelianceLib/PTIRelianceLib.csproj
index f15baf1..61c9921 100644
--- a/PTIRelianceLib/PTIRelianceLib.csproj
+++ b/PTIRelianceLib/PTIRelianceLib.csproj
@@ -11,16 +11,16 @@
2018 Pyramid Technologies
https://pti.mit-license.org/
Supports flash updating, confguration read, and configuration write
- 0.3.0.0
+ 0.3.1.0
https://github.com/PyramidTechnologies/PTI.Reliance.Tools
https://github.com/PyramidTechnologies/PTI.Reliance.Tools
git
dotnet-core thermal-printer hid
https://pyramidacceptors.com/favicon.ico
- 0.3.0.0
+ 0.3.1.0
true
false
- 0.3.0.0
+ 0.3.1.0
true
true
@@ -29,12 +29,15 @@
bin\x64\Release\netcoreapp2.0\PTIRelianceLib.xml
0
1701;1702;NU5100
+ DEBUG;TRACE;RELEASE;NETCOREAPP;NETCOREAPP2_0;SAFE;RELEASE;NETCOREAPP;NETCOREAPP2_0;RELEASE;NETCOREAPP;NETCOREAPP2_0
1701;1702;NU5100
embedded
true
+ TRACE;DEBUG;NETCOREAPP;NETCOREAPP2_0;NETCOREAPP;NETCOREAPP2_0;NETCOREAPP;NETCOREAPP2_0;SAFE;NETCOREAPP;NETCOREAPP2_0;NETCOREAPP;NETCOREAPP2_0
+ false
@@ -59,6 +62,7 @@
+
diff --git a/PTIRelianceLib/ProgressMonitor.cs b/PTIRelianceLib/ProgressMonitor.cs
index 5e1dd9c..f80b750 100644
--- a/PTIRelianceLib/ProgressMonitor.cs
+++ b/PTIRelianceLib/ProgressMonitor.cs
@@ -9,7 +9,7 @@
namespace PTIRelianceLib
{
using System;
- using Firmware;
+ using Flash;
///
///
@@ -30,21 +30,39 @@ public class ProgressMonitor : IProgressMonitor
/// Contains information about non-critical messages
public event EventHandler OnFlashMessage;
-
///
+ ///
+ /// Called when a worker class encounters an unrecoverable error. The
+ /// details of the error will be packed into a formatted message.
+ ///
+ /// Format string
+ /// Args to printf
public virtual void ReportFailure(string format, params object[] args)
{
ReportMessage(format, args);
}
///
+ ///
+ /// Called for general worker messages that may indicate specific
+ /// points in a process have been reached. Errors and warnings will
+ /// not be sent through this method.
+ ///
+ /// Format string
+ /// Args to printf
public virtual void ReportMessage(string format, params object[] args)
{
var handler = OnFlashMessage;
handler?.Invoke(this, new FlashEventMessageArgs(string.Format(format, args)));
}
-
+
///
+ ///
+ /// Called when an iota of progress has been made. For instance, during
+ /// flash updating there are a set number of packets to transmit so
+ /// each successful packet transmission will call this method.
+ ///
+ /// progress range is (0.0, 1.0)
public virtual void ReportProgress(double progress)
{
var handler = OnFlashProgressUpdated;
@@ -54,6 +72,7 @@ public virtual void ReportProgress(double progress)
///
///
+ /// A silent progress monitor for flash update and logo writers.
/// As the name implies, this consumes and ignores all data
/// passed to it. This is useful for silent console flash updaters,
/// particularly on systems where constance console drawing may be expensive.
diff --git a/PTIRelianceLib/Firmware/Internal/FileTypes.cs b/PTIRelianceLib/Protocol/FileTypes.cs
similarity index 81%
rename from PTIRelianceLib/Firmware/Internal/FileTypes.cs
rename to PTIRelianceLib/Protocol/FileTypes.cs
index 8c03016..7312c85 100644
--- a/PTIRelianceLib/Firmware/Internal/FileTypes.cs
+++ b/PTIRelianceLib/Protocol/FileTypes.cs
@@ -6,7 +6,7 @@
// 9:07 AM
#endregion
-namespace PTIRelianceLib.Firmware.Internal
+namespace PTIRelianceLib.Protocol
{
internal enum FileTypes
{
diff --git a/PTIRelianceLib/Protocol/RelianceCommands.cs b/PTIRelianceLib/Protocol/RelianceCommands.cs
index 45e0132..93b7e33 100644
--- a/PTIRelianceLib/Protocol/RelianceCommands.cs
+++ b/PTIRelianceLib/Protocol/RelianceCommands.cs
@@ -1,9 +1,11 @@
#region Header
+
// RelianceCommands.cs
// PTIRelianceLib
// Cory Todd
// 16-05-2018
// 10:06 AM
+
#endregion
namespace PTIRelianceLib.Protocol
@@ -14,220 +16,273 @@ internal enum RelianceCommands
/// Request permission to flash update
///
FlashRequest = 0x35,
+
///
/// Execute flash update process
///
FlashDo = 0x55,
+
///
/// Ping target for ACK
///
Ping = 0x75,
+
///
/// Request expected checksum
///
GetExpectedCsum = 0x85,
+
///
/// Request actual checksum
///
GetActualCsum = 0x95,
+
///
/// Request version string of target
///
GetRevlev = 0x15,
+
///
/// Hard reset target
///
Reboot = 0x25,
+
///
/// Get the unique ID for target (hardware based)
///
GetUniqueId = 0x66,
+
///
/// Get the ID of the current bootlaoder
///
GetBootId = 0x57,
+
///
/// Get the CRC Ranges that should be checked
///
GetCRCRanges = 0x37,
+
///
/// Get the communication timeout for target. This is the PC communication
/// timeout over HID.
///
GetCommTimeout = 0x67,
+
///
/// Set RAM flags to boot into target application
///
SetBootMode = 0x59,
+
///
/// Returns the RS-232 serial configuration
///
GetSerialConfig = 0x40,
+
///
/// Sets the RS-232 serial configuration
///
- SetSerialConfig = 0x41,
+ SetSerialConfig = 0x41,
+
///
/// Returns the print quality encoding
///
- GetPrintQuality = 0x42,
+ GetPrintQuality = 0x42,
+
///
/// Sets the print quality encoding
///
- SetPrintQuality = 0x43,
+ SetPrintQuality = 0x43,
+
///
/// Returns the retraction enabled state
///
- GetRetractEnabled = 0x44,
+ GetRetractEnabled = 0x44,
+
///
/// Sets the retraction enabled state
///
- SetRetractEnabled = 0x62,
+ SetRetractEnabled = 0x62,
+
///
/// Returns the ejector mode
///
- GetEjectorMode = 0x46,
+ GetEjectorMode = 0x46,
+
///
/// Sets the ejector mode
///
- SetEjectorMode = 0x47,
+ SetEjectorMode = 0x47,
+
///
/// Returns the present length in mm
///
- GetPresentLen = 0x48,
+ GetPresentLen = 0x48,
+
///
/// Sets the present length in mm
///
- SetPresentLen = 0x49,
+ SetPresentLen = 0x49,
+
///
/// Returns the CRLF (carriage return) config
///
- GetCRLFConf = 0x4A,
+ GetCRLFConf = 0x4A,
+
///
/// Sets the CRLF (carriage return) config
///
- SetCRLFConf = 0x4B,
+ SetCRLFConf = 0x4B,
+
///
/// Returns the action taken when a ticket timeout occurs
///
- GetTimeoutAction = 0x4C,
+ GetTimeoutAction = 0x4C,
+
///
/// Sets the action taken when a ticket timeout occurs
///
- SetTimeoutAction = 0x4D,
+ SetTimeoutAction = 0x4D,
+
///
/// Returns the action taken when a new ticket arrives
///
- GetNewTicketAction = 0x4E,
+ GetNewTicketAction = 0x4E,
+
///
/// Sets the action taken when a new ticket arrives
///
- SetNewTicketAction = 0x4F,
+ SetNewTicketAction = 0x4F,
+
///
/// Returns the serial number of the target
///
- GetSerialNumber = 0x50,
+ GetSerialNumber = 0x50,
+
///
/// Sets the serial number of the target
///
- SetSerialNumber = 0x51,
+ SetSerialNumber = 0x51,
+
///
/// Saves targets current configuraton to nvflash
///
- SaveConfig = 0x52,
+ SaveConfig = 0x52,
+
///
/// Returns the current print density
///
- GetPrintDensity = 0x53,
+ GetPrintDensity = 0x53,
+
///
/// Sets the current print density
///
- SetPrintDensity = 0x54,
+ SetPrintDensity = 0x54,
+
///
/// Returns the ticket timeout period
///
- GetTicketTimeoutPeriod = 0x60,
+ GetTicketTimeoutPeriod = 0x60,
+
///
/// Sets the ticket timeout period
///
- SetTicketTimeoutPeriod = 0x61,
+ SetTicketTimeoutPeriod = 0x61,
+
///
/// Sets the friendly name of the printer
///
- SetFriendlyName = 0x63,
+ SetFriendlyName = 0x63,
+
///
/// Gets the friendly name of the printer
///
- GetFriendlyName = 0x64,
+ GetFriendlyName = 0x64,
+
///
/// Reserved
///
- Reserved1 = 0x69,
+ Reserved1 = 0x69,
+
///
/// Reserved
///
Reserved2 = 0x70,
+
///
/// Returns the printers real-time status
///
- GetPrinterStatus = 0x71,
+ GetPrinterStatus = 0x71,
+
///
/// Subcommand for RTC operations
///
- RTCSub = 0x73,
+ RTCSub = 0x73,
+
///
/// Subcmmand for PCB operations
///
- PCBSub = 0x78,
+ PCBSub = 0x78,
+
///
/// Request to write general data to flash (not firmware image)
///
- DataWriteRequest = 0x79,
+ DataWriteRequest = 0x79,
+
///
/// Subcommand for logos
///
- LogoSub = 0x80,
+ LogoSub = 0x80,
+
///
/// Print a page containing current codepage
///
- PrintCodepage = 0x81,
+ PrintCodepage = 0x81,
+
///
/// Subcommand for font config
///
- FontSub = 0x82,
+ FontSub = 0x82,
+
///
/// Subcommand for paper size
///
- PaperSizeSub = 0x83,
+ PaperSizeSub = 0x83,
+
///
- /// Subcommand for Logs
+ /// Subcommand for Data Logs
///
- LogSub = 0x84,
+ DataLogSub = 0x84,
+
///
/// Subcommand for bezel
///
- BezelSub = 0x85,
+ BezelSub = 0x85,
+
///
/// Subcommand for autocut settings
///
- AutocutSub = 0x88,
+ AutocutSub = 0x88,
+
///
/// Perform general configuration
///
///
GeneralConfigSub = 0x91,
+
///
/// Prints an empty (no-text) ticket
///
PrintBlankTicket = 0x92,
+
///
/// Returns the length of paper moved
///
GetPaperMoved = 0x93,
+
///
/// No command set (reserved)
///
- None = 0xFF
+ None = 0xFF
}
internal enum GeneralConfigCodes
@@ -241,74 +296,91 @@ internal enum GeneralConfigCodes
/// be exposed as a configurable.
///
SetStepperMotor = 0x00,
+
///
/// Returns the stepper motor code
///
GetStepperMotor = 0x01,
+
///
/// Sets the paper slack compensation flag
///
SetPaperSlackComp = 0x02,
+
///
/// Gets the paper slack compenstation flag
///
GetPaperSlackComp = 0x03,
+
///
/// Sets CDC enabled or disabled
///
SetCDCEnable = 0x04,
+
///
/// Get sthe CDC enabled state
///
GetCDCEnable = 0x05,
+
///
/// Set the USB unique serial number feature
///
SetUSBSerialUnique = 0x06,
+
///
/// Gets the USB unique serial numnber feature
///
GetUSBSerialUnique = 0x07,
+
///
/// Sets the stepper motor current
/// 1.15
///
SetStepperMotorCurrent = 0x08,
+
///
/// Gets the stepper motor current
/// 1.15
///
GetStepperMotorCurrent = 0x09,
+
///
/// Sets the XonXoff control code
///
SetXonXoffCodes = 0x0A,
+
///
/// Gets the XonXoff control codes
///
GetXonXoffCodes = 0x0B,
+
///
/// Sets the startup ticket enabled feature
///
SetStartupTicketEnabled = 0x0C,
+
///
/// Gets the startup ticket enabled feature
///
GetStartupTicketEnabled = 0x0D,
+
///
/// Sets the configuration file revision
/// 1.21
///
SetConfigFileRev = 0x0E,
+
///
/// Sets the configuration file revision
/// 1.21
///
GetConfigFileRev = 0x0F,
+
///
/// Sets the truncation enabled feature state
///
SetTruncationEnabled = 0x10,
+
///
/// Gets the truncation enabled feature state
///
diff --git a/PTIRelianceLib/ReliancePrinter.cs b/PTIRelianceLib/ReliancePrinter.cs
index 813c349..41150b8 100644
--- a/PTIRelianceLib/ReliancePrinter.cs
+++ b/PTIRelianceLib/ReliancePrinter.cs
@@ -1,9 +1,11 @@
#region Header
+
// HidWrapper.cs
// PTIRelianceLib
// Cory Todd
// 16-05-2018
// 11:30 AM
+
#endregion
namespace PTIRelianceLib
@@ -14,10 +16,12 @@ namespace PTIRelianceLib
using System.Linq;
using System.Threading;
using Configuration;
- using Firmware.Internal;
+ using Firmware;
+ using Imaging;
using IO;
using IO.Internal;
using Logging;
+ using Logo;
using Protocol;
using Transport;
@@ -43,6 +47,7 @@ public class ReliancePrinter : IPyramidDevice
///
/// USB VID
public const int VendorId = 0x0425;
+
///
/// USB product id for all Reliance USB interfaces
///
@@ -106,7 +111,7 @@ private IPort AcquireHidPort()
{
try
{
- _mPort?.Close();
+ _mPort?.Close();
_mPort = new HidPort(_mPortConfig);
}
catch (DllNotFoundException ex)
@@ -131,6 +136,7 @@ public ReturnCodes SendConfiguration(BinaryFile config)
{
return ReturnCodes.DeviceNotConnected;
}
+
var configWriter = new RElConfigUpdater(this);
return configWriter.WriteConfiguration(config);
}
@@ -139,7 +145,7 @@ public ReturnCodes SendConfiguration(BinaryFile config)
public BinaryFile ReadConfiguration()
{
if (!_mPort.IsOpen)
- {
+ {
return BinaryFile.From(new byte[0]);
}
@@ -193,7 +199,7 @@ public ReturnCodes FlashUpdateTarget(BinaryFile firmware, ProgressMonitor report
FileType = FileTypes.Base,
RecoverConnection = AcquireHidPort,
RunBefore = new List>(),
- RunAfter = new List> { Reboot }
+ RunAfter = new List> {Reboot, CheckChecksum}
};
// If not already in bootloader mode, do so before flash update
@@ -237,6 +243,8 @@ public Revlev GetFirmwareRevision()
///
/// Returns the for the attached printer. If there
/// is no device connected, null will be returned.
+ /// You can use this method to read the ticket status. See ,
+ /// which are a member of .
///
/// Status object or null if no connection or error
public Status GetStatus()
@@ -281,8 +289,7 @@ public ReturnCodes Ping()
var cmd = new ReliancePacket(RelianceCommands.Ping);
var resp = Write(cmd);
- return resp.GetPacketType() == PacketTypes.PositiveAck ?
- ReturnCodes.Okay : ReturnCodes.ExecutionFailure;
+ return resp.GetPacketType() == PacketTypes.PositiveAck ? ReturnCodes.Okay : ReturnCodes.ExecutionFailure;
}
///
@@ -335,7 +342,6 @@ public ReturnCodes Reboot()
Log.Warn("Failing to reboot indicates that Reliance is having trouble reconnecting to your hardware." +
"It is recommended to check PTIRelianceLib.Library.Options for configuration options.");
return ReturnCodes.RebootFailure;
-
}
catch (Exception e)
{
@@ -369,6 +375,7 @@ public IEnumerable GetInstalledCodepages()
{
return result;
}
+
if (raw.Count < 3)
{
return result;
@@ -379,14 +386,120 @@ public IEnumerable GetInstalledCodepages()
{
return result;
}
+
for (var i = 1; i < (count * 2); i += 2)
{
- result.Add((ushort)(raw[i + 1] << 8 | raw[i]));
+ result.Add((ushort) (raw[i + 1] << 8 | raw[i]));
+ }
+
+ return result;
+ }
+
+ ///
+ /// Writes the provided logos to internal logo bank. These logos can be accessed
+ /// by their index in the list using . Logos are stored in
+ /// non-volatile memory so these only need to be written once. It is not recommended
+ /// to call this method excessively (e.g. on every print job) as this is writing to
+ /// flash and you will degrade the flash chip.
+ ///
+ /// myLogos = { logoA, logoB, logoC };
+ /// StoreLogos(myLogos, monitor, storageConfig);
+ /// printer.PrintLogo(0); // Print logo A
+ /// printer.PrintLogo(2); // Print logo C
+ ///
+ /// LogoStorageConfig can be used to alter the dithering algorithm and logo scaling behavior.
+ ///
+ /// List of raw logo image data
+ /// Progress reporter
+ /// Logo alteration and storage options.
+ /// Uses if storageConfig is set to null.
+ /// You probably want to configure this yourself.
+ /// Return Code
+ /// Thrown if logodata is null
+ public ReturnCodes StoreLogos(IList logoData, IProgressMonitor monitor, LogoStorageConfig storageConfig)
+ {
+ if (logoData == null)
+ {
+ throw new ArgumentNullException(nameof(logoData));
+ }
+
+ if (storageConfig == null)
+ {
+ storageConfig = LogoStorageConfig.Default;
+ }
+
+ var result = ReturnCodes.InvalidRequest;
+
+ if (logoData.Count == 0)
+ {
+ return result;
+ }
+
+ var logoBank = new RELLogoBank();
+ var ditheredLogos = logoData.Select(logo =>
+ new BasePrintLogo(logo, maxWidth: storageConfig.MaxPixelWidth)).Cast().ToList();
+ ditheredLogos.ForEach(x => x.ApplyDithering(storageConfig.Algorithm, storageConfig.Threshold));
+
+ // MakeHeaders locks in our scaling and dithering options
+ foreach(var header in logoBank.MakeHeaders(ditheredLogos))
+ {
+ // sub command 2: Set Logo header
+ var cmd = _mPort.Package((byte) RelianceCommands.LogoSub, 0x02);
+ cmd.Add(header.Serialize());
+
+ // Send the data and parse response
+ var resp = Write(cmd);
+ if (resp.GetPacketType() != PacketTypes.PositiveAck)
+ {
+ Log.ErrorFormat("Failed to write logo header: {0}", resp.GetPacketType());
+ return ReturnCodes.ExecutionFailure;
+ }
+
+ // Specify start address to that when the big logo buffer is built,
+ // the correct address is injected in the flash permission command
+ var logoFlasher = new RELLogoUpdater(_mPort, header)
+ {
+ Reporter = monitor,
+ };
+
+ result = logoFlasher.ExecuteUpdate();
+ if (result == ReturnCodes.Okay)
+ {
+ continue;
+ }
+
+ Log.ErrorFormat("Failed to write logo: {0}", result.GetEnumName());
+ return result;
+ }
+
+ if (result == ReturnCodes.Okay)
+ {
+ // Store logo header data
+ Write(RelianceCommands.SaveConfig);
}
return result;
}
+ ///
+ /// Prints logo from internal bank specified by index. If the specified
+ /// logo index does not exist, the first logo in the bank will instead be printed.
+ /// Negative indicies and indicies larger than 255 will generate a
+ /// return code.
+ ///
+ /// Logo index, zero based
+ /// Return Code
+ public ReturnCodes PrintLogo(int index)
+ {
+ if (index < 0 || index > byte.MaxValue)
+ {
+ return ReturnCodes.InvalidRequestPayload;
+ }
+ // 7 == print logo sub command
+ Write(RelianceCommands.LogoSub, 7, (byte) index);
+ return ReturnCodes.Okay;
+ }
+
///
/// Returns firmware application id string or empty on error
///
@@ -398,7 +511,7 @@ internal string GetAppId()
return string.Empty;
}
- var cmd = new ReliancePacket((byte)RelianceCommands.GetBootId, 0x10);
+ var cmd = new ReliancePacket((byte) RelianceCommands.GetBootId, 0x10);
var resp = Write(cmd);
return PacketParserFactory.Instance.Create().Parse(resp).Value;
}
@@ -419,6 +532,26 @@ internal ReturnCodes EnterBootloader()
return resp.GetPacketType() != PacketTypes.PositiveAck ? ReturnCodes.FailedBootloaderEntry : Reboot();
}
+
+ ///
+ /// Check checksum of target device. If checksum is okay,
+ /// return is . Otherwise
+ /// is returned.
+ ///
+ /// Return code
+ internal ReturnCodes CheckChecksum()
+ {
+ // Get expected checksum
+ var resp = Write(RelianceCommands.GetExpectedCsum, 0x11);
+ var expectedCsum = PacketParserFactory.Instance.Create().Parse(resp);
+
+ // Check actual checksum
+ resp = Write(RelianceCommands.GetActualCsum, 0x11);
+ var actualCsum = PacketParserFactory.Instance.Create().Parse(resp);
+
+ return Equals(expectedCsum, actualCsum) ? ReturnCodes.Okay : ReturnCodes.FlashChecksumMismatch;
+ }
+
///
/// Write wrapper handle the write-wait-read process. The data returned
/// from this method will be unpackaged for your.
@@ -428,7 +561,7 @@ internal ReturnCodes EnterBootloader()
/// Response or empty if no response
internal IPacket Write(RelianceCommands cmd, params byte[] data)
{
- var packet = new ReliancePacket(cmd);
+ var packet = new ReliancePacket(cmd);
packet.Add(data);
return Write(packet);
}
@@ -456,4 +589,4 @@ public void Dispose()
_mPort?.Close();
}
}
-}
+}
\ No newline at end of file
diff --git a/PTIRelianceLib/Transport/BasePacket.cs b/PTIRelianceLib/Transport/BasePacket.cs
index f5357f5..92c35a1 100644
--- a/PTIRelianceLib/Transport/BasePacket.cs
+++ b/PTIRelianceLib/Transport/BasePacket.cs
@@ -35,6 +35,12 @@ protected BasePacket(byte[] data)
_mData = data;
}
+ // Clear all data in this packet
+ protected void Clear()
+ {
+ _mData = new byte[0];
+ }
+
#region Methods
///
diff --git a/PTIRelianceLib/Transport/ReliancePacket.cs b/PTIRelianceLib/Transport/ReliancePacket.cs
index 1cbd2f0..61fc255 100644
--- a/PTIRelianceLib/Transport/ReliancePacket.cs
+++ b/PTIRelianceLib/Transport/ReliancePacket.cs
@@ -1,9 +1,11 @@
#region Header
+
// ReliancePacket.cs
// PTIRelianceLib
// Cory Todd
// 16-05-2018
// 10:00 AM
+
#endregion
namespace PTIRelianceLib.Transport
@@ -18,17 +20,19 @@ internal class ReliancePacket : BasePacket
private PacketTypes _mPacketType = PacketTypes.Unset;
public ReliancePacket()
- { }
+ {
+ }
public ReliancePacket(params byte[] cmd)
: base(cmd)
- { }
+ {
+ }
public ReliancePacket(params RelianceCommands[] cmd)
{
foreach (var c in cmd)
{
- Add((byte)c);
+ Add((byte) c);
}
}
@@ -39,7 +43,7 @@ public override IPacket Package()
return this;
}
- var length = (byte)(Count + 1);
+ var length = (byte) (Count + 1);
byte checksum = 0;
Prepend(length);
@@ -79,7 +83,6 @@ public override IPacket ExtractPayload()
if (totalLen <= 0)
{
-
// If this is an ACK packet, there is no payload
// to extract
result = new ReliancePacket();
@@ -96,7 +99,6 @@ public override IPacket ExtractPayload()
}
-
result.IsValid = true;
result.IsPackaged = false;
@@ -241,16 +243,16 @@ public override PacketTypes GetPacketType()
// What kind of response is this?
switch (local[ackIndex])
{
- case (byte)ControlCodes.Ack:
+ case (byte) ControlCodes.Ack:
_mPacketType = PacketTypes.PositiveAck;
break;
- case (byte)ControlCodes.Nak:
+ case (byte) ControlCodes.Nak:
_mPacketType = PacketTypes.NegativeAck;
break;
- case (byte)ControlCodes.Ser:
+ case (byte) ControlCodes.Ser:
_mPacketType = PacketTypes.SequenceError;
break;
- case (byte)ControlCodes.Timeout:
+ case (byte) ControlCodes.Timeout:
_mPacketType = PacketTypes.Timeout;
break;
default:
diff --git a/README.md b/README.md
index 4f1bc37..ff864b9 100644
--- a/README.md
+++ b/README.md
@@ -3,11 +3,7 @@
# Reliance Tools API
This is a collection of tools for flash updating, configuring, and maintaining your Reliance Thermal Printer.
-# Installation
-### Package Manager
-
- Install-Package PTIRelianceLib
-
+# Installation
### Dotnet CLI
dotnet add package PTIRelianceLib
@@ -16,7 +12,8 @@ This is a collection of tools for flash updating, configuring, and maintaining y
See [the quick-start guide](http://developers.pyramidacceptors.com/PTI.Reliance.Tools/)
## Examples
-See [RelianceCLI](RelianceCLI)
-
+- [RelianceCLI](RelianceCLI) is a traditional command line tool
+- [ASP.NET Core](samples/reliance-asp-core-docker) is a bare-bones ASP.NET example that can be run inside a Docker container
+- [Docker](samples/reliance--docker) is an example of a non-ASP.NET core application running inside a container. This example also includes the scripts and VS Code tasks required for debugging inside your container.
## Third Party Licenses
See [Third Party Licenses](third-party-license-readme.md)
diff --git a/RelianceCLI/Program.cs b/RelianceCLI/Program.cs
index a12641a..5c87828 100644
--- a/RelianceCLI/Program.cs
+++ b/RelianceCLI/Program.cs
@@ -5,7 +5,7 @@
namespace RelianceCLI
{
using System.IO;
- using System.Threading;
+ using PTIRelianceLib.Imaging;
using PTIRelianceLib.Protocol;
internal class Program
@@ -124,6 +124,26 @@ private static void Run(Options opts)
Console.WriteLine("Read and saved printer configuration to {0}", opts.SaveConfigPath);
}
+ if (!string.IsNullOrEmpty(opts.LogoFilePath))
+ {
+ var logo = BinaryFile.From(opts.LogoFilePath);
+ if (logo.Empty)
+ {
+ Console.WriteLine("Logo file cannot be read. Does it exist?");
+ }
+ else
+ {
+ var logos = new List {logo};
+ var config = LogoStorageConfig.Default;
+ config.Algorithm = DitherAlgorithms.Atkinson;
+
+ var result = printer.StoreLogos(logos, new ConsoleProgressBar(), config);
+ Console.WriteLine("Store Result: {0}", result);
+
+ printer.PrintLogo(0);
+ }
+ }
+
if (opts.GetStatus)
{
var status = printer.GetStatus();
@@ -144,6 +164,8 @@ private struct Options
public string ConfigFilePath;
+ public string LogoFilePath;
+
public bool Reboot;
public bool GetRevlev;
@@ -188,6 +210,11 @@ public static Options Parse(IEnumerable args)
opts.GetRevlev = true;
break;
+ case "-l":
+ case "--logo":
+ nextCapture = s => opts.LogoFilePath = s;
+ break;
+
case "-s":
case "--status":
opts.GetStatus = true;
@@ -226,6 +253,7 @@ public static string Usage()
"\t-f,--firmware\t\tPath to firmware to flash update with\n" +
"\t-c,--config\t\tPath to configuration to apply\n" +
"\t-g,--get-config\t\tPath to file current config will be written to\n" +
+ "\t-l,--set-logo\t\tPath to image file to store as logo\n" +
"FLAGS\n" +
"\t-r,--revision\t\tRead and display printer firmware revision\n" +
"\t-s,--status\t\tRead and display printer status\n" +
diff --git a/RelianceCLI/Properties/launchSettings.json b/RelianceCLI/Properties/launchSettings.json
index 6bf891d..fb3f001 100644
--- a/RelianceCLI/Properties/launchSettings.json
+++ b/RelianceCLI/Properties/launchSettings.json
@@ -1,8 +1,7 @@
{
"profiles": {
"RelianceCLI": {
- "commandName": "Project",
- "commandLineArgs": "-r"
+ "commandName": "Project"
}
}
}
\ No newline at end of file
diff --git a/docsource/filterConfig.yml b/docsource/filterConfig.yml
index fe80bfd..d6d6873 100644
--- a/docsource/filterConfig.yml
+++ b/docsource/filterConfig.yml
@@ -1,5 +1,9 @@
apiRules:
- exclude:
uidRegex: ^PTIRelianceLib\.Transport\.IParseable$
-- exclude:
- uidRegex: ^PTIRelianceLib\.IProgressMonitor$
\ No newline at end of file
+- exclude:
+ uidRegex: ^PTIRelianceLib\.IProgressMonitor$
+- exclude:
+ uidRegex: ^PTIRelianceLib\.Logging$
+- exclude:
+ uidRegex: ^PTIRelianceLib\.Logging\.LogProviders$
\ No newline at end of file
diff --git a/docsource/index.md b/docsource/index.md
index f6356e9..284bf0e 100644
--- a/docsource/index.md
+++ b/docsource/index.md
@@ -1,10 +1,12 @@
![Banner](images/banner.png)
# PTI.Reliance.Tools
-A cross-platform .NET Core 2.0 API for updating and maintaining your Reliance Thermal printer.
+A cross-platform .NET Core 2.0 API for updating and maintaining your [Reliance Thermal Printer](https://pyramidacceptors.com/products/reliance-thermal-printer).
[![NuGet](https://img.shields.io/nuget/v/PTIRelianceLib.svg?style=flat-square)](https://www.nuget.org/packages/PTIRelianceLib/)
+[![Build status](https://ci.appveyor.com/api/projects/status/jjwwqnue2al1j5c5/branch/master?svg=true)](https://ci.appveyor.com/project/catodd/pti-reliance-tools/branch/master)
+
Source code available on [Github](https://github.com/PyramidTechnologies/PTI.Reliance.Tools)
# Quick Start
diff --git a/docsource/tutorials/99-hid.rules.sample b/docsource/tutorials/99-hid.rules.sample
new file mode 100644
index 0000000..2b8ebdf
--- /dev/null
+++ b/docsource/tutorials/99-hid.rules.sample
@@ -0,0 +1,2 @@
+# Reliance Thermal Printer HID interface
+SUBSYSTEM=="usb", ATTRS{idVendor}=="0425", ATTRS{idProduct}=="8147", MODE="0666"
\ No newline at end of file
diff --git a/docsource/tutorials/NLog.config.sample b/docsource/tutorials/NLog.config.sample
new file mode 100644
index 0000000..02cd231
--- /dev/null
+++ b/docsource/tutorials/NLog.config.sample
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docsource/tutorials/Sample_04.cs b/docsource/tutorials/Sample_04.cs
index eac48d0..697bc82 100644
--- a/docsource/tutorials/Sample_04.cs
+++ b/docsource/tutorials/Sample_04.cs
@@ -26,6 +26,10 @@ static void Main(string[] args)
var hasPaper = status.SensorStatus.HasFlag(SensorStatuses.Path);
Console.WriteLine("Has Paper? :{0}", hasPaper);
+
+ // You can also check the status of the Ticket (e.g. Ticket Pull)
+ var ticketState = status.TicketStatus;
+ Console.WriteLine("Ticket State :{0}", ticketState);
}
}
}
diff --git a/docsource/tutorials/Sample_05.cs b/docsource/tutorials/Sample_05.cs
new file mode 100644
index 0000000..cd20d11
--- /dev/null
+++ b/docsource/tutorials/Sample_05.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Threading;
+using PTIRelianceLib;
+using PTIRelianceLib.Imaging;
+
+namespace reliance_sample
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+
+ // Wrap our printer in using so it gets disposed on properly
+ using (var printer = new ReliancePrinter())
+ {
+ var logos = new List
+ {
+ BinaryFile.From("my_logo_a.jpg"),
+ BinaryFile.From("my_logo_b.png"),
+ BinaryFile.From("another.bmp"),
+ };
+
+ var config = LogoStorageConfig.Default;
+ config.Algorithm = DitherAlgorithms.Atkinson;
+
+ var result = printer.StoreLogos(logos, new ConsoleProgressBar(), config);
+ Console.WriteLine("Write Result: {0}", result);
+
+ // use logo index to recall and print
+ for(int i = 0 ; i < logos.Count; ++i)
+ {
+ printer.PrintLogo(i);
+
+ // Give ~3 seconds to print
+ Thread.Sleep(3 * 1000);
+ }
+
+ }
+ }
+ }
+}
diff --git a/docsource/tutorials/configuration.md b/docsource/tutorials/configuration.md
index 1c3a4f6..71ccbfb 100644
--- a/docsource/tutorials/configuration.md
+++ b/docsource/tutorials/configuration.md
@@ -1,3 +1,6 @@
+---
+uid: tut_configuration
+---
# Device Configuration
Configuration can be managed through this API with use of Reliance RFG
diff --git a/docsource/tutorials/customizations.md b/docsource/tutorials/customization.md
similarity index 97%
rename from docsource/tutorials/customizations.md
rename to docsource/tutorials/customization.md
index 180796b..2eb9d6d 100644
--- a/docsource/tutorials/customizations.md
+++ b/docsource/tutorials/customization.md
@@ -1,4 +1,7 @@
-# Customizations
+---
+uid: tut_customization
+---
+# Library Customization
PTIRelianceLib is designed to support as many operating systems as possible. We try to support as much as possible with defaults but in some cases, there are outliers that we need to make special accommodations for without affecting other systems.
diff --git a/docsource/tutorials/docker.md b/docsource/tutorials/docker.md
new file mode 100644
index 0000000..5b190a7
--- /dev/null
+++ b/docsource/tutorials/docker.md
@@ -0,0 +1,29 @@
+---
+uid: tut_docker
+---
+# Docker Support
+The abstraction mechanisms of Docker make certain types of hardware integration quite challenging in Docker. As long as your device is not removed and reinserted during the lifetime of your container you will likely not have any issues. However, certain operations like our flash update process generate USB reset event that will confound Docker. This tutorial explains solutions for this particular problem.
+
+> [!IMPORTANT]
+> Only Linux hosts are supported for Docker containers
+
+## Privileged
+The most reliable implementation we've tested leverages the ```--privileged``` flag and the mounting of ```/dev/bus/usb``` volume from host to container. Your results may vary and you may find this to be unnecessary for your environment.
+```sh
+sudo docker run --rm -it \
+ -v /dev/bus/usb:/dev/bus/usb \
+ --privileged \
+ :
+```
+
+## Host Permissions
+Your host system may require additional permissions or even udev rules in order to reliably attach to your Reliance Thermal Printer. This is typically achieved by adding your user to the dialout group or equivalent for your environment. Another option is to create a udev rule, ```/etc/udev/rules.d/99-udev.rules``` with the following contents.
+[!code[Main](99-hid.rules.sample)]
+
+# Special Configurations
+A pre-made configuration for the Debian Stretch Docker image can be configured with the following in your application start up code.
+```cs
+PTIRelianceLib.Library.Options = PTIRelianceLib.LibraryOptions.DockerLinuxStretch;
+```
+
+[!include[]()]
diff --git a/docsource/tutorials/exceptions.md b/docsource/tutorials/exceptions.md
index b3eac5d..b357599 100644
--- a/docsource/tutorials/exceptions.md
+++ b/docsource/tutorials/exceptions.md
@@ -1,3 +1,7 @@
+---
+uid: tut_exceptions
+---
+
> [!Warning]
> ## Exceptions
> For clarity, exception handling has been elided. It is advisable to wrap any ReliancePrinter
diff --git a/docsource/tutorials/flash_update.md b/docsource/tutorials/flash_update.md
index a66c5a7..1e3c480 100644
--- a/docsource/tutorials/flash_update.md
+++ b/docsource/tutorials/flash_update.md
@@ -1,3 +1,6 @@
+---
+uid: tut_flashupdate
+---
# Firmware Flash Updates
This library is designed promote natural use of good design practices. One of the most
@@ -9,4 +12,7 @@ maintain gives you more time to focus on the features that bring value to your p
## Code Sample
[!code-csharp[Main](Sample_02.cs)]
-[!include[]()]
\ No newline at end of file
+[!include[]()]
+
+> [!TIP]
+> Flash udpating may be unreliable on Docker. See the [Docker](xref:tut_docker) section for more details.
\ No newline at end of file
diff --git a/docsource/tutorials/intro.md b/docsource/tutorials/intro.md
index a8f8184..d10c41f 100644
--- a/docsource/tutorials/intro.md
+++ b/docsource/tutorials/intro.md
@@ -1,8 +1,11 @@
+---
+uid: tut_intro
+---
# Getting Started
1. Install [.NET Core SDK](https://www.microsoft.com/net/download/windows) for your operating system
2. Open your favorite terminal
-3. Execute
+3. Setup Project and install library
```
dotnet new console -o reliance_sample
cd reliance_sample
@@ -15,7 +18,7 @@ dotnet add package PTIRelianceLib
> Linux users, please make sure libusb is installed before proceeding
5. Execute
-```
+```sh
dotnet run
```
diff --git a/docsource/tutorials/logging.md b/docsource/tutorials/logging.md
new file mode 100644
index 0000000..a5c5ae0
--- /dev/null
+++ b/docsource/tutorials/logging.md
@@ -0,0 +1,15 @@
+# Logging
+PTIRelianceLig uses a the framework agnostic logging library [LibLog]([https://github.com/damianh/LibLog). Integrating with your application is simple and we use NLog throughout the sample projects.
+
+## NLog Setup
+Install NLog
+```sh
+ dotnet add package NLog.Config
+```
+
+Add a config file named ```NLog.config``` to the root of **your** application. This is typically next to your csproj file. Here is a sample config file:
+[!code-xml[Main](NLog.config.sample)]
+
+## Other Frameworks
+See [LibLog's sample directory](https://github.com/damianh/LibLog/tree/master/src)
+
diff --git a/docsource/tutorials/logos.md b/docsource/tutorials/logos.md
new file mode 100644
index 0000000..2488298
--- /dev/null
+++ b/docsource/tutorials/logos.md
@@ -0,0 +1,9 @@
+# Logos
+This API makes storing logos in your internal flash very easy. With this API, you can dither, resize, and store any image into internal flash for fast printing. There are a number of dithering algorithms ready for you to try. Use the one that makes your image look the best!
+
+See the list and for more details.
+
+## Code Sample
+[!code-csharp[Main](Sample_05.cs)]
+
+[!include[]()]
\ No newline at end of file
diff --git a/docsource/tutorials/toc.yml b/docsource/tutorials/toc.yml
index c0884f5..c1fe4aa 100644
--- a/docsource/tutorials/toc.yml
+++ b/docsource/tutorials/toc.yml
@@ -4,7 +4,13 @@
href: flash_update.md
- name: Configuration
href: configuration.md
+- name: Logos
+ href: logos.md
- name: Status Check
href: status_check.md
-- name: Customizations
- href: customizations.md
+- name: Library Logging
+ href: logging.md
+- name: Library Customization
+ href: customization.md
+- name: Docker Support
+ href: docker.md