POS Printing with an Epson Printer in VB.NET

11,067

This is hard since it is not easy to find good articles. If you look for information you will notice how epson is not really happy sharing their knowleage. bot here are some articles that helped me with this:

  • this article is really good if you want to print images Images
  • This one is from epson Epson

** Update **

this is what makes the magic:

Public Class EscPOS

    Private Shared PrintNam As String = "POS"

    Public Shared Property PrinterName
        Set(value)
            PrintNam = value
        End Set
        Get
            Return PrintNam
        End Get
    End Property

    ' Structure and API declarions:
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
    Structure DOCINFOW
        <MarshalAs(UnmanagedType.LPWStr)> Public pDocName As String
        <MarshalAs(UnmanagedType.LPWStr)> Public pOutputFile As String
        <MarshalAs(UnmanagedType.LPWStr)> Public pDataType As String
    End Structure

    <DllImport("winspool.Drv", EntryPoint:="OpenPrinterW", _
       SetLastError:=True, CharSet:=CharSet.Unicode, _
       ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function OpenPrinter(ByVal src As String, ByRef hPrinter As IntPtr, ByVal pd As Long) As Boolean
    End Function
    <DllImport("winspool.Drv", EntryPoint:="ClosePrinter", _
       SetLastError:=True, CharSet:=CharSet.Unicode, _
       ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function ClosePrinter(ByVal hPrinter As IntPtr) As Boolean
    End Function
    <DllImport("winspool.Drv", EntryPoint:="StartDocPrinterW", _
       SetLastError:=True, CharSet:=CharSet.Unicode, _
       ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function StartDocPrinter(ByVal hPrinter As IntPtr, ByVal level As Int32, ByRef pDI As DOCINFOW) As Boolean
    End Function
    <DllImport("winspool.Drv", EntryPoint:="EndDocPrinter", _
       SetLastError:=True, CharSet:=CharSet.Unicode, _
       ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function EndDocPrinter(ByVal hPrinter As IntPtr) As Boolean
    End Function
    <DllImport("winspool.Drv", EntryPoint:="StartPagePrinter", _
       SetLastError:=True, CharSet:=CharSet.Unicode, _
       ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function StartPagePrinter(ByVal hPrinter As IntPtr) As Boolean
    End Function
    <DllImport("winspool.Drv", EntryPoint:="EndPagePrinter", _
       SetLastError:=True, CharSet:=CharSet.Unicode, _
       ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function EndPagePrinter(ByVal hPrinter As IntPtr) As Boolean
    End Function
    <DllImport("winspool.Drv", EntryPoint:="WritePrinter", _
       SetLastError:=True, CharSet:=CharSet.Unicode, _
       ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function WritePrinter(ByVal hPrinter As IntPtr, ByVal pBytes As IntPtr, ByVal dwCount As Int32, ByRef dwWritten As Int32) As Boolean
    End Function

    Public Shared Function PrintImage(BM As Bitmap) As Boolean

        Dim b As Byte() = ConvertImagetoBytes(BM)
        Dim bSuccess As Boolean
        Dim pUnmanagedBytes As IntPtr

        ' Allocate some unmanaged memory for those bytes.
        pUnmanagedBytes = Marshal.AllocCoTaskMem(b.Count)
        ' Copy the managed byte array into the unmanaged array.
        Marshal.Copy(b, 0, pUnmanagedBytes, b.Count)
        ' Send the unmanaged bytes to the printer.
        bSuccess = EscPOS.PrintBytes(b)

        Return bSuccess
    End Function
    Public Shared Function PrintBytes(Document As Byte()) As Boolean
        Dim hPrinter As IntPtr      ' The printer handle.
        Dim dwError As Int32        ' Last error - in case there was trouble.
        Dim di As DOCINFOW       ' Describes your document (name, port, data type).
        Dim dwWritten As Int32      ' The number of bytes written by WritePrinter().
        Dim bSuccess As Boolean     ' Your success code.

        ' Set up the DOCINFO structure.

        di = New DOCINFOW
        di.pDocName = "RAW LOGO"
        di.pDataType = "RAW"

        hPrinter = New IntPtr(0)

        bSuccess = False
        If OpenPrinter(PrinterName.Normalize(), hPrinter, 0) Then
            If StartDocPrinter(hPrinter, 1, di) Then
                Dim managedData As Byte()
                Dim unmanagedData As IntPtr

                managedData = Document
                unmanagedData = Marshal.AllocCoTaskMem(managedData.Length)
                Marshal.Copy(managedData, 0, unmanagedData, managedData.Length)

                If StartPagePrinter(hPrinter) Then
                    bSuccess = WritePrinter(hPrinter, unmanagedData, managedData.Length, dwWritten)
                    EndPagePrinter(hPrinter)
                End If
                Marshal.FreeCoTaskMem(unmanagedData)
                EndDocPrinter(hPrinter)
            End If
            ClosePrinter(hPrinter)
        End If
        If bSuccess = False Then
            dwError = Marshal.GetLastWin32Error()
        End If
        Return bSuccess
    End Function
    Public Shared Function ConvertImagetoBytes(BM As Bitmap) As Byte()
        Dim Data As BitMapData = GetBitmapData(BM)
        Dim Op As New MemoryStream
        Dim bw As New BinaryWriter(Op)

        bw.Write(Chr(Keys.Escape))
        bw.Write("@"c)

        ' So we have our bitmap data sitting in a bit array called "dots."
        ' This is one long array of 1s (black) and 0s (white) pixels arranged
        ' as if we had scanned the bitmap from top to bottom, left to right.
        ' The printer wants to see these arranged in bytes stacked three high.
        ' So, essentially, we need to read 24 bits for x = 0, generate those
        ' bytes, and send them to the printer, then keep increasing x. If our
        ' image is more than 24 dots high, we have to send a second bit image
        ' command to draw the next slice of 24 dots in the image.

        ' Set the line spacing to 24 dots, the height of each "stripe" of the
        ' image that we're drawing. If we don't do this, and we need to
        ' draw the bitmap in multiple passes, then we'll end up with some
        ' whitespace between slices of the image since the default line
        ' height--how much the printer moves on a newline--is 30 dots.
        bw.Write(Chr(Keys.Escape))
        bw.Write("3"c)
        ' '3' just means 'change line height command'
        bw.Write(CByte(24))

        ' OK. So, starting from x = 0, read 24 bits down and send that data
        ' to the printer. The offset variable keeps track of our global 'y'
        ' position in the image. For example, if we were drawing a bitmap
        ' that is 48 pixels high, then this while loop will execute twice,
        ' once for each pass of 24 dots. On the first pass, the offset is
        ' 0, and on the second pass, the offset is 24. We keep making
        ' these 24-dot stripes until we've run past the height of the
        ' bitmap.
        Dim offset As Integer = 0
        Dim width As Byte()

        While offset < Data.Height
            ' The third and fourth parameters to the bit image command are
            ' 'nL' and 'nH'. The 'L' and the 'H' refer to 'low' and 'high', respectively.
            ' All 'n' really is is the width of the image that we're about to draw.
            ' Since the width can be greater than 255 dots, the parameter has to
            ' be split across two bytes, which is why the documentation says the
            ' width is 'nL' + ('nH' * 256).
            bw.Write(Chr(Keys.Escape))
            bw.Write("*"c)
            ' bit-image mode
            bw.Write(CByte(33))
            ' 24-dot double-density
            width = BitConverter.GetBytes(Data.Width)
            bw.Write(width(0))
            ' width low byte
            bw.Write(width(1))
            ' width high byte
            For x As Integer = 0 To Data.Width - 1
                ' Remember, 24 dots = 24 bits = 3 bytes.
                ' The 'k' variable keeps track of which of those
                ' three bytes that we're currently scribbling into.
                For k As Integer = 0 To 2
                    Dim slice As Byte = 0
                    ' A byte is 8 bits. The 'b' variable keeps track
                    ' of which bit in the byte we're recording.
                    For b As Integer = 0 To 7
                        ' Calculate the y position that we're currently
                        ' trying to draw. We take our offset, divide it
                        ' by 8 so we're talking about the y offset in
                        ' terms of bytes, add our current 'k' byte
                        ' offset to that, multiple by 8 to get it in terms
                        ' of bits again, and add our bit offset to it.
                        Dim y As Integer = (((offset \ 8) + k) * 8) + b
                        ' Calculate the location of the pixel we want in the bit array.
                        ' It'll be at (y * width) + x.
                        Dim i As Integer = (y * Data.Width) + x
                        ' If the image (or this stripe of the image)
                        ' is shorter than 24 dots, pad with zero.
                        Dim v As Boolean = False
                        If i < Data.Dots.Length Then
                            v = Data.Dots(i)
                        End If
                        ' Finally, store our bit in the byte that we're currently
                        ' scribbling to. Our current 'b' is actually the exact
                        ' opposite of where we want it to be in the byte, so
                        ' subtract it from 7, shift our bit into place in a temp
                        ' byte, and OR it with the target byte to get it into there.
                        slice = slice Or CByte((If(v, 1, 0)) << (7 - b))
                    Next
                    ' Phew! Write the damn byte to the buffer
                    bw.Write(slice)
                Next
            Next
            ' We're done with this 24-dot high pass. Render a newline
            ' to bump the print head down to the next line
            ' and keep on trucking.
            offset = offset + 24
            bw.Write(vbCrLf.ToCharArray)
        End While

        ' Restore the line spacing to the default of 30 dots.
        bw.Write(Chr(Keys.Escape))
        bw.Write("3"c)
        bw.Write(CByte(30))

        bw.Flush()

        Return Op.ToArray
    End Function
    Private Shared Function GetBitmapData(BM As Bitmap) As BitMapData
        Dim threshold = 127
        Dim index As Integer = 0
        Dim dimensions As Integer = BM.Width * BM.Height
        Dim dots As BitArray = New BitArray(dimensions)
        Dim res As New BitMapData
        Dim a As Integer

        For y = 0 To BM.Height - 1
            For x = 0 To BM.Width - 1
                Dim col As Color = BM.GetPixel(x, y)
                Dim luminance = CInt(col.R * 0.3 + col.G * 0.59 + col.B * 0.11)
                If (luminance < threshold) = True Then
                    a = 1
                End If
                dots(index) = (luminance < threshold)
                index = index + 1
            Next
        Next
        res.Dots = dots : res.Height = BM.Height : res.Width = BM.Width
        Return res
    End Function
    Private Class BitMapData
        Public Dots As BitArray
        Public Height As Int16
        Public Width As Int16
    End Class

    ' When the function is given a printer name and an unmanaged array of  
    ' bytes, the function sends those bytes to the print queue.
    ' Returns True on success or False on failure.
    Private Shared Function PrintEsto(ByVal pBytes As IntPtr, ByVal dwCount As Int32) As Boolean
        Dim hPrinter As IntPtr      ' The printer handle.
        Dim dwError As Int32        ' Last error - in case there was trouble.
        Dim di As DOCINFOW = Nothing         ' Describes your document (name, port, data type).
        Dim dwWritten As Int32      ' The number of bytes written by WritePrinter().
        Dim bSuccess As Boolean     ' Your success code.

        ' Set up the DOCINFO structure.
        With di
            .pDocName = "RAW Document"
            .pDataType = "RAW"
        End With
        ' Assume failure unless you specifically succeed.
        bSuccess = False
        If OpenPrinter(PrinterName, hPrinter, 0) Then
            If StartDocPrinter(hPrinter, 1, di) Then
                If StartPagePrinter(hPrinter) Then
                    ' Write your printer-specific bytes to the printer.
                    bSuccess = WritePrinter(hPrinter, pBytes, dwCount, dwWritten)
                    EndPagePrinter(hPrinter)
                End If
                EndDocPrinter(hPrinter)
            End If
            ClosePrinter(hPrinter)
        End If
        ' If you did not succeed, GetLastError may give more information
        ' about why not.
        If bSuccess = False Then
            dwError = Marshal.GetLastWin32Error()
        End If
        Return bSuccess
    End Function

    ' SendFileToPrinter()
    ' When the function is given a file name and a printer name, 
    ' the function reads the contents of the file and sends the
    ' contents to the printer.
    ' Presumes that the file contains printer-ready data.
    ' Shows how to use the SendBytesToPrinter function.
    ' Returns True on success or False on failure.
    Public Shared Function PrintFile(ByVal szFileName As String) As Boolean
        ' Open the file.
        Try
            Dim fs As New FileStream(szFileName, FileMode.Open)
            ' Create a BinaryReader on the file.
            Dim br As New BinaryReader(fs)
            ' Dim an array of bytes large enough to hold the file's contents.
            Dim bytes(fs.Length) As Byte
            Dim bSuccess As Boolean
            ' Your unmanaged pointer.
            Dim pUnmanagedBytes As IntPtr

            ' Read the contents of the file into the array.
            bytes = br.ReadBytes(fs.Length)
            ' Allocate some unmanaged memory for those bytes.
            pUnmanagedBytes = Marshal.AllocCoTaskMem(fs.Length)
            ' Copy the managed byte array into the unmanaged array.
            Marshal.Copy(bytes, 0, pUnmanagedBytes, fs.Length)
            ' Send the unmanaged bytes to the printer.
            bSuccess = PrintEsto(pUnmanagedBytes, fs.Length)
            ' Free the unmanaged memory that you allocated earlier.
            Marshal.FreeCoTaskMem(pUnmanagedBytes)
            fs.Close()
            Return bSuccess
        Catch ex As Exception
            MsgBox(ex.Message)
            Return False
        End Try
    End Function

    ' When the function is given a string and a printer name,
    ' the function sends the string to the printer as raw bytes.
    Public Shared Function PrintString(ByVal szString As String)
        Dim pBytes As IntPtr
        Dim dwCount As Int32
        Dim Res As Boolean
        ' How many characters are in the string?
        dwCount = szString.Length()
        ' Assume that the printer is expecting ANSI text, and then convert
        ' the string to ANSI text.
        pBytes = Marshal.StringToCoTaskMemAnsi(szString)
        ' Send the converted ANSI string to the printer.
        Res = PrintEsto(pBytes, dwCount)
        Marshal.FreeCoTaskMem(pBytes)
        Return Res
    End Function

End Class

And this is how I call whit class:

 If EsImpresionTermica Then
            If File.Exists(My.Application.Info.DirectoryPath & "\Settings.{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0}\Logo.conf") Then _
                RawPrinting.EscPOS.PrintBytes(File.ReadAllBytes(My.Application.Info.DirectoryPath & "\Settings.{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0}\Logo.conf"))
        Else
            Dim MSe As New MemoryStream
            Dim BWe As New BinaryWriter(MSe)
            BWe.Write(Chr(&H1B))
            BWe.Write("@"c) 'Inicia Imresora
            BWe.Write(Chr(&H1B))
            BWe.Write(CByte(3))
            BWe.Write(Chr(18)) 'Establece interlineado
            BWe.Write(Chr(&H1B))
            BWe.Write("U"c)
            BWe.Write(Chr(1)) 'Impresión unidireccional
            BWe.Write(Chr(&H1B))
            BWe.Write("a"c)
            BWe.Write(Chr(1)) 'Centra Impresión
            BWe.Write(Chr(&H1B))
            BWe.Write(vbCrLf.ToCharArray)
            BWe.Write(Encoding.ASCII.GetBytes(Encabezado))
            BWe.Write(Chr(10))
            BWe.Write(Chr(10))
            BWe.Flush()
            BWe.Close()
            RawPrinting.EscPOS.PrintBytes(MSe.ToArray)
        End If
    End Sub
Share:
11,067
Ryan Fitzgerald
Author by

Ryan Fitzgerald

I hate people that downvote. No question is ever a bad question.

Updated on August 02, 2022

Comments

  • Ryan Fitzgerald
    Ryan Fitzgerald almost 2 years

    I'm working in VB.NET and having some issues with a POS Printer I just connected (Epson TM-T88V). I recently downloaded a sample code from something I found on YouTube that used an Epson TM-88II, an older model of the V. This code would work perfectly fine if I could just open the serial port! What's happening is that the printer is on port USB-001 (the virtual printer port) and you can't just set that as your COM Port in VB to write to it. I downloaded the Microsoft POS for .NET and the OPOSN from Epson. The only question I have so far is where do I even start? Can anyone help me with connecting to the printer with VB and then maybe move into sending a basic "Hello World" to the printer followed by an autocut?