Bondruck aus SwiftUI App

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Bevor du dich mit NSConnection rumschlägst und graue Haare bekommst…

    Quellcode

    1. class Connection: NSObject, GCDAsyncSocketDelegate {
    2. private var asyncSocket: GCDAsyncSocket? = nil
    3. private let delegateQueue = DispatchQueue(label: "delegateQueue")
    4. private let timeout: TimeInterval = 40.0
    5. init() {
    6. super.init()
    7. }
    8. func connect (ipAdress: String, port: Int16) {
    9. self.asyncSocket = GCDAsyncSocket.init(delegate: self, delegateQueue: self.delegateQueue)
    10. self.asyncSocket?.isIPv6Enabled = false
    11. do
    12. {
    13. try self.asyncSocket?.connect(toHost: ipAdress, onPort: UInt16(port), withTimeout: 30)
    14. }
    15. catch {
    16. LogInfo("error: \(error)")
    17. }
    18. }
    19. func disconnect() {
    20. self.asyncSocket?.disconnect()
    21. self.asyncSocket = nil
    22. }
    23. func writeData(data: Data, tag: Int = 10) {
    24. self.asyncSocket?.write(data, withTimeout: timeout, tag: tag)
    25. LogInfo("Write")
    26. }
    27. // MARK: - GCDAsyncSocketDelegate
    28. /**
    29. * Called when a socket connects and is ready for reading and writing.
    30. * The host parameter will be an IP address, not a DNS name.
    31. **/
    32. func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) {
    33. print("connected to host: \(host)")
    34. self.asyncSocket?.readData(withTimeout: timeout, tag: 1)
    35. }
    36. /**
    37. * Called when a socket has completed reading the requested data into memory.
    38. * Not called if there is an error.
    39. **/
    40. func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
    41. LogInfo("Read")
    42. logHexDumpForBytes(bytes: Array<UInt8>(data))
    43. self.asyncSocket?.readData(withTimeout: timeout, tag: 1)
    44. }
    45. /**
    46. * Called when a socket has completed writing the requested data. Not called if there is an error.
    47. **/
    48. func socket(_ sock: GCDAsyncSocket, didWriteDataWithTag tag: Int) {
    49. }
    50. /**
    51. * Called if a read operation has reached its timeout without completing.
    52. * This method allows you to optionally extend the timeout.
    53. * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount.
    54. * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual.
    55. *
    56. * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
    57. * The length parameter is the number of bytes that have been read so far for the read operation.
    58. *
    59. * Note that this method may be called multiple times for a single read if you return positive numbers.
    60. **/
    61. func socket(_ sock: GCDAsyncSocket, shouldTimeoutReadWithTag tag: Int, elapsed: TimeInterval, bytesDone length: UInt) -> TimeInterval {
    62. print("Read timeout")
    63. return timeout
    64. }
    65. /**
    66. * Called if a write operation has reached its timeout without completing.
    67. * This method allows you to optionally extend the timeout.
    68. * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount.
    69. * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual.
    70. *
    71. * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
    72. * The length parameter is the number of bytes that have been written so far for the write operation.
    73. *
    74. * Note that this method may be called multiple times for a single write if you return positive numbers.
    75. **/
    76. func socket(_ sock: GCDAsyncSocket, shouldTimeoutWriteWithTag tag: Int, elapsed: TimeInterval, bytesDone length: UInt) -> TimeInterval {
    77. print("Write timeout")
    78. return timeout
    79. }
    80. /**
    81. * Called when a socket disconnects with or without error.
    82. *
    83. * If you call the disconnect method, and the socket wasn't already disconnected,
    84. * then an invocation of this delegate method will be enqueued on the delegateQueue
    85. * before the disconnect method returns.
    86. *
    87. * Note: If the GCDAsyncSocket instance is deallocated while it is still connected,
    88. * and the delegate is not also deallocated, then this method will be invoked,
    89. * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.)
    90. * This is a generally rare, but is possible if one writes code like this:
    91. *
    92. * asyncSocket = nil; // I'm implicitly disconnecting the socket
    93. *
    94. * In this case it may preferrable to nil the delegate beforehand, like this:
    95. *
    96. * asyncSocket.delegate = nil; // Don't invoke my delegate method
    97. * asyncSocket = nil; // I'm implicitly disconnecting the socket
    98. *
    99. * Of course, this depends on how your state machine is configured.
    100. **/
    101. func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) {
    102. LogInfo("disconnected: \(String(describing: err))")
    103. }
    104. }
    Alles anzeigen
    Man macht einfach solange irgendwelche Dinge, bis man tot ist.
    Und dann bekommen die anderen Kuchen.
  • @Chris kannst du mir sagen was ich falsch mache?

    Ich habe mir diesen escpos Printer Emulator gestartet:
    github.com/dacduong/escpos-printer-simulator

    Ich habe mir eine Vorlage gebaut nach diesen PHP Vorbild:

    PHP-Quellcode

    1. <?php
    2. /* ASCII constants */
    3. const ESC = "\x1b";
    4. const GS="\x1d";
    5. const NUL="\x00";
    6. /* Output an example receipt */
    7. echo ESC."@"; // Reset to defaults
    8. echo ESC."E".chr(1); // Bold
    9. echo "FOO CORP Ltd.\n"; // Company
    10. echo ESC."E".chr(0); // Not Bold
    11. echo ESC."d".chr(1); // Blank line
    12. echo "Receipt for whatever\n"; // Print text
    13. echo ESC."d".chr(4); // 4 Blank lines
    14. /* Bar-code at the end */
    15. echo ESC."a".chr(1); // Centered printing
    16. echo GS."k".chr(4)."987654321".NUL; // Print barcode
    17. echo ESC."d".chr(1); // Blank line
    18. echo "987654321\n"; // Print number
    19. echo GS."V\x41".chr(3); // Cut
    20. exit(0);
    Alles anzeigen


    und in Swift Übersetzt:



    Quellcode

    1. let ESC = "\\x1b"
    2. let GS = "\\x1d"
    3. let chr0 = String(UnicodeScalar(0))
    4. let chr1 = String(UnicodeScalar(1))
    5. let chr3 = String(UnicodeScalar(3))
    6. let message1 = "\(ESC)@\(ESC)a\(chr1)\(ESC)E\(chr1)Tisch: \(table), Platz: \(seat)\\n\(ESC)E\(chr0)\(ESC)d\(chr1)\(lastname)\\n\(ESC)d\(chr1)\(specialDiet)\\nMenue: \(menu) - Groesse: \(menuSizeDecoder(from: size))\\n\(GS)V\\x41\(chr3)"


    Das ganze sieht dann so aus:


    Quellcode

    1. public func printBon(id:Int32, table: Int, seat: Int, lastname: String, menu: Int, size: Int, dessert: Bool, specialDiet: String){
    2. let host:NWEndpoint.Host = "192.168.110.115"
    3. let port:NWEndpoint.Port = 9100
    4. let ESC = "\\x1b"
    5. let GS = "\\x1d"
    6. let chr0 = String(UnicodeScalar(0))
    7. let chr1 = String(UnicodeScalar(1))
    8. let chr3 = String(UnicodeScalar(3))
    9. let message1 = "\(ESC)@\(ESC)a\(chr1)\(ESC)E\(chr1)Tisch: \(table), Platz: \(seat)\\n\(ESC)E\(chr0)\(ESC)d\(chr1)\(lastname)\\n\(ESC)d\(chr1)\(specialDiet)\\nMenue: \(menu) - Groesse: \(menuSizeDecoder(from: size))\\n\(GS)V\\x41\(chr3)"
    10. let tcp = NWProtocolTCP.Options.init()
    11. tcp.noDelay = true
    12. let params = NWParameters.init(tls: nil, tcp: tcp)
    13. let connection = NWConnection(to: NWEndpoint.hostPort(host: host, port: port), using: params)
    14. connection.stateUpdateHandler = { newState in
    15. switch newState {
    16. case .ready:
    17. print("[MyDebug] Socket State: Ready")
    18. UserDefaults.standard.set(true, forKey: "isConnected")
    19. print("[MyDebug] send data")
    20. let content: Data = message1.data(using: .utf8)!
    21. connection.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ NWError in
    22. if (NWError == nil) {
    23. print("[MyDebug] Data was sent to TCP destination ")
    24. connection.cancel()
    25. } else {
    26. print("[MyDebug] ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
    27. }
    28. })))
    29. case .failed(let error):
    30. print("[MyDebug] Fatal connection error", error.localizedDescription)
    31. default:
    32. UserDefaults.standard.set(false, forKey: "isConnected")
    33. break
    34. }
    35. }
    36. connection.start(queue: DispatchQueue.main)
    37. markCheckInAsPrinted(id: id)
    38. }
    Alles anzeigen


    Wenn ich einen BReakpoint setzte und mir "message1" im Debugger anzeigen lasse bekomme ich:

    "\\x1b@\\x1ba\u{01}\\x1bE\u{01}Tisch: 1, Platz: 1\\n\\x1bE\0\\x1bd\u{01}maier\\n\\x1bd\u{01}Foetz\\nMenue: 1 - Groesse: normal\\n\\x1dV\\x41\u{03}"

    ausgegeben. Nehme ich diesen String und sende den per "nc" über das Terminal an den Printer zeigt der Drucker sowas an:
    Bildschirmfoto 2023-07-07 um 10.04.14.png
    Das selbe über die iOS App gesendet führt aber hierzu:

    Bildschirmfoto 2023-07-07 um 10.01.47.png

    Was mache ich denn da falsch?

    Grüße

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Qvex23 ()

  • Es gibt zwei Standards bei den besseren Druckern. EPL und ZPL (ZPL ist Zebra). Die Drucker haben eine eigene Scriptsprache, über die sie angesteuert werden können. Für den Rechner gibt es entsprechende Design Programme, die einen viel Zeit- und Mühe ersparen.

    esto.de/epl-vs-zpl-drucker

    zebra.com/de/de/products/softw…alink/zebra-designer.html

    Für Entwickler gibt es entsprechende Designer Software Lösungen.

    zebra.com/us/en/software/printer-software/zebradesigner.html

    Das ist zwar eine Java Lösung, aber vielleicht kann man daraus eine Methode für Swirft UI entwickeln.

    youtu.be/zc-Tx1DHqCY

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Thorsten Kreutz ()

  • Chris schrieb:

    Bevor du dich mit NSConnection rumschlägst und graue Haare bekommst…

    Quellcode

    1. class Connection: NSObject, GCDAsyncSocketDelegate {
    2. private var asyncSocket: GCDAsyncSocket? = nil
    3. private let delegateQueue = DispatchQueue(label: "delegateQueue")
    4. private let timeout: TimeInterval = 40.0
    5. init() {
    6. super.init()
    7. }
    8. func connect (ipAdress: String, port: Int16) {
    9. self.asyncSocket = GCDAsyncSocket.init(delegate: self, delegateQueue: self.delegateQueue)
    10. self.asyncSocket?.isIPv6Enabled = false
    11. do
    12. {
    13. try self.asyncSocket?.connect(toHost: ipAdress, onPort: UInt16(port), withTimeout: 30)
    14. }
    15. catch {
    16. LogInfo("error: \(error)")
    17. }
    18. }
    19. func disconnect() {
    20. self.asyncSocket?.disconnect()
    21. self.asyncSocket = nil
    22. }
    23. func writeData(data: Data, tag: Int = 10) {
    24. self.asyncSocket?.write(data, withTimeout: timeout, tag: tag)
    25. LogInfo("Write")
    26. }
    27. // MARK: - GCDAsyncSocketDelegate
    28. /**
    29. * Called when a socket connects and is ready for reading and writing.
    30. * The host parameter will be an IP address, not a DNS name.
    31. **/
    32. func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) {
    33. print("connected to host: \(host)")
    34. self.asyncSocket?.readData(withTimeout: timeout, tag: 1)
    35. }
    36. /**
    37. * Called when a socket has completed reading the requested data into memory.
    38. * Not called if there is an error.
    39. **/
    40. func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
    41. LogInfo("Read")
    42. logHexDumpForBytes(bytes: Array<UInt8>(data))
    43. self.asyncSocket?.readData(withTimeout: timeout, tag: 1)
    44. }
    45. /**
    46. * Called when a socket has completed writing the requested data. Not called if there is an error.
    47. **/
    48. func socket(_ sock: GCDAsyncSocket, didWriteDataWithTag tag: Int) {
    49. }
    50. /**
    51. * Called if a read operation has reached its timeout without completing.
    52. * This method allows you to optionally extend the timeout.
    53. * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount.
    54. * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual.
    55. *
    56. * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
    57. * The length parameter is the number of bytes that have been read so far for the read operation.
    58. *
    59. * Note that this method may be called multiple times for a single read if you return positive numbers.
    60. **/
    61. func socket(_ sock: GCDAsyncSocket, shouldTimeoutReadWithTag tag: Int, elapsed: TimeInterval, bytesDone length: UInt) -> TimeInterval {
    62. print("Read timeout")
    63. return timeout
    64. }
    65. /**
    66. * Called if a write operation has reached its timeout without completing.
    67. * This method allows you to optionally extend the timeout.
    68. * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount.
    69. * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual.
    70. *
    71. * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
    72. * The length parameter is the number of bytes that have been written so far for the write operation.
    73. *
    74. * Note that this method may be called multiple times for a single write if you return positive numbers.
    75. **/
    76. func socket(_ sock: GCDAsyncSocket, shouldTimeoutWriteWithTag tag: Int, elapsed: TimeInterval, bytesDone length: UInt) -> TimeInterval {
    77. print("Write timeout")
    78. return timeout
    79. }
    80. /**
    81. * Called when a socket disconnects with or without error.
    82. *
    83. * If you call the disconnect method, and the socket wasn't already disconnected,
    84. * then an invocation of this delegate method will be enqueued on the delegateQueue
    85. * before the disconnect method returns.
    86. *
    87. * Note: If the GCDAsyncSocket instance is deallocated while it is still connected,
    88. * and the delegate is not also deallocated, then this method will be invoked,
    89. * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.)
    90. * This is a generally rare, but is possible if one writes code like this:
    91. *
    92. * asyncSocket = nil; // I'm implicitly disconnecting the socket
    93. *
    94. * In this case it may preferrable to nil the delegate beforehand, like this:
    95. *
    96. * asyncSocket.delegate = nil; // Don't invoke my delegate method
    97. * asyncSocket = nil; // I'm implicitly disconnecting the socket
    98. *
    99. * Of course, this depends on how your state machine is configured.
    100. **/
    101. func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) {
    102. LogInfo("disconnected: \(String(describing: err))")
    103. }
    104. }
    Alles anzeigen
    Hab ich völlig übersehen. Danke! ich baue es mal ein und teste. Allerdings bleibt immer noch die Frage wie ich diese Commands sende wie "CUT" oder Text Fett formatieren, zentriert Drucken usw.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Qvex23 ()

  • Netzwerk geht ja schon mal.
    Und wenn du dir ansiehst was das Terminal ausgibt sollte die auffallen das das escapen nicht klappt.



    Aus docs.swift.org/swift-book/docu…acters-in-String-Literals
    Special Characters in String Literals


    String literals can include the following special characters:
    • The escaped special characters \0 (null character), \\ (backslash), \t (horizontal tab), \n (line feed), \r (carriage return), \" (double quotation mark) and \' (single quotation mark)
    • An arbitrary Unicode scalar value, written as \u{n}, where n is a 1–8 digit hexadecimal number (Unicode is discussed in Unicode below)


    Macht man das dann so

    Quellcode

    1. let ESC = "\u{1b}"
    2. let GS = "\u{1d}"
    sieht die Sache schon besser aus.
    Dann noch aus \\n ein \n dann klappt auch mit dem Umbruch. Ist aber nicht nötig weil du schon ESCd1 sendest (Print and feed n lines).
    Man macht einfach solange irgendwelche Dinge, bis man tot ist.
    Und dann bekommen die anderen Kuchen.
  • @Chris

    Ich habe mir einen Drucker bestellt, ich befürchte das diese Software Variante nicht so funktioniert wie dann später der Drucker funktioniert. Ich habe das bisher so gemacht allerdings wurden die Steuerbefehle wie Fett usw. nicht angenommen. Wie gesagt, vermutlich kann diese Druckersimulation das auch nicht, k.a.

    hier mein Code:

    Quellcode

    1. let GS = Character(UnicodeScalar(29))
    2. let ESC = Character(UnicodeScalar(27))
    3. let char48 = Character(UnicodeScalar(48))
    4. let char1 = Character(UnicodeScalar(1))
    5. let char0 = Character(UnicodeScalar(0))
    6. let bon = "\(ESC)@\(ESC)E\(char1)\(date)\nTisch: \(table), Platz: \(seat)\(ESC)E\(char0)\n\(lastname)\n\(specialDiet)\nMenue: \(menu) \(menuSizeDecoder(from: size))\n\(ESC)@\(GS)V\(char48)"
    Ich werde es gleich mal mit deinen ESC und GS probieren und schauen wie der Drucker Simulator das umsetzt.

    Danke auf alle fälle für all deine Hilfe!

    Grüße

    Dirk
  • Der Simulator macht keinerlei Sondersachen wie fett oder so. Dazu reich ein kurzer Blick in die Sourcen.
    Beim echten Drucker könnte der erste Fallstrick schon beim Initialisieren liegen. Nach ESC@ brauchen manche Epson Drucker eine kleine Erholung. Besonders beim Logo Hohladen.

    Chris
    Man macht einfach solange irgendwelche Dinge, bis man tot ist.
    Und dann bekommen die anderen Kuchen.
  • Hallo,

    der Drucker ist nun endlich da und es funktioniert nahezu alles problemlos. Nur 2 Dinge würde ich gerne noch anpassen, aber keine Idee wie:

    1. Umlaute drucken statt Kauderwelsch. Sollte heißen "Müsterintolleranz" - "Test Notizä" - "Mößtermann"
    bon.jpg
    2. Schriftgröße ändern

    Weist du was da zu tun ist @Chris ?

    Grüße

    Dirk

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Qvex23 ()

  • Steht alles ESC/POS programming manual Seite 89 ff.

    ESC t Character code table. Wir nehmen 19
    ESC R Natürlich den deutschen Zeichensatz
    ESC M Character font
    GS ! ist die Character Size

    PS:
    Wir verwenden ISOLatin1 String Codierung.
    Und denk dran dass die Drucker, zumindest die die ich kenne, nur 2k Buffer haben.
    Hier mal unsere Printmethode

    Quellcode

    1. - (BOOL) printString:(NSString*)printString error:(NSError**)error
    2. {
    3. NSData* data = [printString dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
    4. if (data.length < 2048)
    5. {
    6. if ( [self writeData:data error:error] == NO )
    7. return NO;
    8. }
    9. else
    10. {
    11. NSUInteger remaining = data.length;
    12. NSUInteger start = 0;
    13. while (remaining > 0)
    14. {
    15. NSUInteger len = (remaining > 2048) ? 2048 : remaining;
    16. NSRange range = NSMakeRange(start, len);
    17. start += len;
    18. remaining -= len;
    19. if ([self writeData: [data subdataWithRange: range] error:error] == NO)
    20. return NO;
    21. if (remaining > 0)
    22. [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 3]];
    23. }
    24. }
    25. return YES;
    26. }
    Alles anzeigen
    Man macht einfach solange irgendwelche Dinge, bis man tot ist.
    Und dann bekommen die anderen Kuchen.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Chris ()

  • Danke @Chris . Ich habe das tatsächlich alles im Manual auch schon gesehen, aber ich weis nicht so recht wie ich das richtig sende.

    Bei mir sieht der Code im Moment so aus:

    Quellcode

    1. let ESC = "\u{1b}"
    2. let GS = "\u{1d}"
    3. let char0 = Character(UnicodeScalar(0))
    4. let char1 = Character(UnicodeScalar(1))
    5. let char48 = Character(UnicodeScalar(48))
    6. let print = "\(ESC)@\(GS)!\(char1)\(ESC)a\(char1)\(ESC)E\(char1)\(ESC)d\(char1)\(ESC)d\(char1)\(date)\(ESC)d\(char1)\nMitarbeiter\n\(ESC)E\(char0)\(ESC)d\(char1)Menue: \(menu)\n\(ESC)d\(char1)\(specialDiet)\(ESC)d\(char1)\(note)\(ESC)d\(char1)\n\(lastname)\n\n\n\n\n\n\n\n\(ESC)d\(char1)\(GS)V\(char48)"
    Sendet man ESC t 19 dann so "\(ESC)t19" oder "\(ESC)t\(char19)"
    Und beim Font bin ich irgendwie ganz raus. Wie sende ich da 2 Werte?
    Bildschirmfoto 2023-07-18 um 06.41.28.png

    Danke für deine HIlfe!

    Grüße

    Dirk
  • Wenn ich das so einstelle:

    Quellcode

    1. bon = "\(ESC)@\(ESC)t\(char19)\(ESC)R\(char2)\(GS)!\(char1)\(ESC)a\(char1)\(ESC)E\(char1)\(ESC)d\(char1)\(ESC)d\(char1)\(date)\(ESC)d\(char1)\nMitarbeiter\n\(ESC)E\(char0)\(ESC)d\(char1)Menue: \(menu)\n\(ESC)d\(char1)\(specialDiet)\(ESC)d\(char1)\(note)\(ESC)d\(char1)\n\(lastname)\n\n\n\n\n\n\n\n\(ESC)d\(char1)\(GS)V\(char48)"

    Dann kommt mit

    Quellcode

    1. let content: Data = bon.data(using: .isoLatin1)!
    Das raus:
    bon.jpg

    Mit utf8 ist es genau so kaputt.

    Grüße

    Dirk