본문 바로가기
작디 작은 나만의 라이브러리/EPSON 써멀 프린터 라이브러리

[신입 개발자의 '0' 번째 라이브러리] 프린터 연결 방식별 구현. Network, Serial, Android SDK - EPSON 써멀 프린터 라이브러리 제작기(2)

by 시니성 2024. 12. 15.

들어가며

지난 글에서는 EPSON 써멀 프린터 라이브러리의 전체적인 설계와 PrinterDialect, Adapter 패턴의 활용에 대해 살펴보았습니다.
이번 글에서는 각 연결 방식별 구체적인 구현을 자세히 분석해보겠습니다.

각 연결 방식별 어댑터는 다음과 같은 공통점과 차이점을 가지고 있습니다:

  • 공통점: T83PrinterAdaptor 추상 클래스를 구현하여 기본적인 프린터 명령어 전송 기능을 제공
  • 차이점: 실제 프린터와의 연결 방식과 통신 방법이 다름

1. NetworkPrinterAdaptor 구현 분석

NetworkPrinterAdaptor는 TCP/IP 소켓을 통해 프린터와 통신하는 구현체입니다.

핵심 특징

internal object NetworkPrinterAdaptor : T83PrinterAdaptor() {
    internal var tcpIp: String = ""
    internal var port: Int = 9100
    internal var timeout: Int = 1000

    @Volatile
    private var socket: Socket? = null
    @Volatile
    override var outputStream: OutputStream? = null
}
  1. Singleton 패턴: object 키워드를 사용하여 싱글톤으로 구현
  2. 상태 관리: socket과 outputStream을 @Volatile로 관리하여 스레드 안전성 확보(Volatile 어노테이션에 대한 자세한 내용은 아래 관련 포스팅 링크 참조)
  3. 설정 유연성: IP, 포트, 타임아웃을 설정 가능

2024.12.15 - [Language/Java] - [@Volatile] CPU 캐시와 메인 메모리 사이, @Volatile의 동작 원리 심층 탐구

[[@Volatile] CPU 캐시와 메인 메모리 사이, @Volatile의 동작 원리 심층 탐구

들어가며멀티코어 프로세서가 당연해진 현대의 애플리케이션 개발에서, 동시성 제어는 필수적인 고려사항이 되었습니다.그 중에서도 @Volatile 키워드는 자바의 동시성 제어에서 가장 기본적인

shin-e-dog.tistory.com](https://shin-e-dog.tistory.com/126)

연결 관리

override fun connect() {
    disconnect()
    try {
        if (socket?.isConnected == true) return
        val newSocket = Socket()
        val address = InetSocketAddress(tcpIp, port)
        newSocket.connect(address, timeout)
        socket = newSocket
        outputStream = socket!!.getOutputStream()
    } catch (e: Exception) {
        logger.error("connect error", e)
        disconnect()
        throw e
    }
}

override fun disconnect() {
    try {
        socket?.close()
        outputStream?.close()
    } catch (e: Exception) {
        logger.error("disconnect error", e)
    } finally {
        outputStream = null
        socket = null
    }
}
  • 연결 전 기존 연결을 항상 해제
  • 예외 발생 시 적절한 로깅과 리소스 정리
  • finally 블록을 통한 안전한 리소스 해제

2. SerialPrinterAdaptor 구현 분석

SerialPrinterAdaptor는 RS-232 시리얼 포트를 통해 프린터와 통신하는 구현체입니다.

핵심 특징

object SerialPrinterAdaptor : T83PrinterAdaptor() {
    internal var baudRate: Int = 38400
    internal var dataBits = 8
    internal var stopBits = SerialPort.ONE_STOP_BIT
    internal var parityBits = SerialPort.NO_PARITY
    internal var port: String = "COM2"

    override var outputStream: OutputStream? = null
    private var commPort: SerialPort? = null
}
  1. 시리얼 통신 설정: baudRate, dataBits 등 시리얼 통신에 필요한 설정 제공
  2. jSerialComm 라이브러리 활용: SerialPort 클래스를 통한 시리얼 통신 구현

연결 관리

override fun connect() {
    disconnect()
    commPort = SerialPort.getCommPort(port)
    commPort?.openPort()
}

override fun outputStream(): OutputStream {
    if (outputStream == null) {
        commPort?.baudRate = baudRate
        commPort?.numDataBits = dataBits
        commPort?.numStopBits = stopBits
        commPort?.parity = parityBits
        outputStream = commPort?.outputStream
    }
    return outputStream ?: throw IOException("outputStream을 가져올 수 없습니다.")
}
  • 연결 시점과 OutputStream 생성 시점 분리
  • 필요한 시점에 시리얼 포트 설정 적용

3. Android SDK 어댑터 구현 분석

안드로이드용 구현은 두 가지 어댑터를 제공합니다:

  1. AndroidSerialPrinterApiAdapter: EPSON SDK의 high-level API 활용
  2. AndroidSerialPrinterByteArrayAdapter: raw byte array 전송 방식 활용

AndroidSerialPrinterApiAdapter

class AndroidSerialPrinterApiAdapter(
    private val printer: Printer,
) {
    internal var connectionType: AndroidSerialConnectionType = AndroidSerialConnectionType.USB
    internal var address: String = ""
    internal var deviceId: String = DEFAULT_DEVICE_ID
    internal var timeout: Int = Printer.PARAM_DEFAULT
    private var target: String = when (connectionType) {
        AndroidSerialConnectionType.USB -> "$connectionType:$address"
        AndroidSerialConnectionType.BLUETOOTH -> "$connectionType:$address"
        AndroidSerialConnectionType.TCP -> "$connectionType:$address[$deviceId]"
        AndroidSerialConnectionType.TCP_SSL -> "$connectionType:$address[$deviceId]"
    }
}
  1. 다양한 연결 방식: USB, Bluetooth, TCP, TCP_SSL 지원
  2. EPSON SDK 활용: Printer 객체를 통한 고수준 API 사용
  3. 연결 문자열 동적 생성: 연결 방식에 따른 target 문자열 구성

AndroidSerialPrinterByteArrayAdapter

class AndroidSerialPrinterByteArrayAdapter(
    private val printer: Printer,
) : T83PrinterAdaptor() {
    override fun output(data: ByteArray) = ePos2ExceptionWrapper {
        printer.addCommand(data)
    }

    fun sendData() {
        printer.sendData(timeout)
    }

    fun clearBuffer() {
        printer.clearCommandBuffer()
    }
}
  1. 저수준 제어: raw byte array를 직접 전송
  2. 버퍼 관리: 명령어 버퍼 관리 기능 제공
  3. 예외 처리: ePos2ExceptionWrapper를 통한 통합적인 예외 처리

연결 방식별 특징과 차이점

1. NetworkPrinterAdaptor

  • 장점
    • 네트워크를 통한 원격 프린팅 가능
    • TCP/IP 프로토콜의 안정성 활용
  • 단점
    • 네트워크 지연 가능성
    • 네트워크 보안 고려 필요

2. SerialPrinterAdaptor

  • 장점
    • 안정적인 직접 연결
    • 낮은 지연시간
  • 단점
    • 물리적 연결 필요
    • 거리 제약 존재

3. Android SDK 어댑터

  • 장점
    • 다양한 연결 방식 지원
    • EPSON SDK의 안정성 활용
  • 단점
    • 안드로이드 플랫폼 의존성
    • SDK 버전 제약

마치며

각 연결 방식별 어댑터는 그들만의 고유한 특징과 장단점을 가지고 있습니다. 이러한 다양한 구현체를 통해:

  1. 사용 환경에 따른 최적의 연결 방식 선택 가능
  2. 동일한 인터페이스로 다양한 연결 방식 활용
  3. 새로운 연결 방식 추가가 용이한 확장성

각 연결 방식별 구현은 해당 통신 방식의 특성을 반영하면서도, 공통된 인터페이스를 통해 일관된 사용성을 제공합니다.
이는 앞서 설계한 추상화의 효과를 잘 보여주는 예시입니다.

다음 글에서는 각각의 출력 기능(텍스트, 바코드, QR 코드, 이미지)의 구현에 대해 자세히 살펴보도록 하겠습니다.

728x90