SmartPort(All) 안드로이드 앱

SmartPort(All) — USB ↔ TCP 브릿지 기능 문서

이 문서는 SmartPort 앱에 추가된 로컬 TCP 브릿지 기능의 설계, 구현, 사용법을 정리합니다. 기존 앱은 USB 시리얼 장치를 네이티브 PTY(가상 시리얼 포트) 한 곳에만 노출했는데, 여기에 127.0.0.1:8899(기본값) TCP 소켓으로도 동일한 장치를 노출하는 기능을 병행 추가했습니다.


1. 개요

항목내용
목적연결된 USB 시리얼 장치를 로컬 TCP 소켓으로 변환하여, PTY를 열 수 없는 앱/프로세스(예: PC에서 adb forward로 접근)도 시리얼 장치를 사용할 수 있게 함
바인딩 주소127.0.0.1 (루프백 전용, 외부 네트워크에는 노출되지 않음)
기본 포트8899
동시 연결클라이언트 1개만 허용 (물리 시리얼 포트처럼 단일 소유자)
데이터 처리바이트를 그대로 양방향 중계 (프로토콜/프레이밍 없음)
활성화 여부MainPreferences.tcpBridgeEnabled (기본값 true)

기존 PTY 브릿지(/data/data/com.clipman.smartport/files/serialpipe)는 그대로 유지되며, TCP 브릿지는 완전히 독립적으로 병행 동작합니다. 둘 중 하나만 써도 되고 동시에 켜둬도 됩니다.


2. 아키텍처

                ┌───────────────────────────┐
                │        USB 시리얼 장치       │
                └──────────────┬────────────┘
                               │
                 UsbManager / UsbSerialPort
                               │
        ┌──────────────────────┴──────────────────────┐
        │                                              │
┌───────▼────────┐                          ┌──────────▼─────────┐
│ VirtualSerialDriver │                      │   TcpBridgeServer    │
│ (기존, PTY 담당)     │                      │   (신규, TCP 담당)     │
│                     │                      │                     │
│ native PTY ↔ USB     │                      │ TCP 소켓 ↔ USB        │
└──────────┬──────────┘                      └──────────┬──────────┘
           │                                            │
   /data/.../serialpipe                        127.0.0.1:8899 (기본)
   (다른 프로세스가 open)                          (TCP 클라이언트가 connect)
  • VirtualSerialDriverTcpBridgeServer는 서로 다른 UsbDeviceConnection / UsbSerialPort
    인스턴스를 각자 열어서 사용합니다 (독립적인 세션). 즉 PTY 클라이언트와 TCP 클라이언트가
    동시에 USB 장치에 접근하면 두 세션이 번갈아 포트를 열고 닫는 형태로 동작하므로,
    운영 환경에서는 되도록 둘 중 하나만 실제로 사용하는 것을 권장합니다.
  • TcpBridgeServerVirtualSerialDriver.selectedDevice(현재 선택된 USB 드라이버)를
    그대로 참조하여, 사용자가 앱 화면에서 고른 드라이버/장치를 그대로 사용합니다.

3. 신규/변경 파일 목록

3.1 신규 파일

app/src/main/java/com/clipman/smartport/serial/TcpBridgeServer.kt

TCP 브릿지의 핵심 로직을 담당하는 클래스입니다.

멤버설명
companion object.DEFAULT_PORT기본 포트, 8899
companion object.DEFAULT_BAUD_RATEUSB 포트를 열 때 기본 보드레이트, 115200
start(listenPort: Int = DEFAULT_PORT)127.0.0.1:listenPortServerSocket을 열고 accept 루프를 별도 스레드에서 시작
stop()서버 소켓·클라이언트 소켓·USB 포트를 모두 정리하고 중단
isRunning현재 리스닝 중인지 여부
tcpPort실제로 바인딩된 포트 (읽기 전용)
setSerialParameters(baudRate, dataBits, stopBits, parity)USB 포트를 열 때 사용할 시리얼 파라미터를 변경

동작 흐름

  1. start() 호출 시 accept 전용 스레드가 ServerSocket(port, 50, InetAddress.getByName("127.0.0.1"))을 생성.
  2. 클라이언트가 접속하면 handleClient(socket) 실행:
    • 이미 다른 클라이언트가 연결되어 있으면 즉시 거부(소켓 닫음).
    • VirtualSerialDriver.selectedDevice가 없으면(=USB 장치 미선택) 접속을 거부.
    • UsbManager.openDevice()UsbSerialPort.open()setParameters(baudRate, dataBits, stopBits, parity)dtr/rts = true 순으로 USB 포트 오픈.
    • USB → TCP: SerialInputOutputManager.Listener.onNewData()에서 수신한 바이트를 그대로 socket.getOutputStream()에 write.
    • TCP → USB: 별도 스레드에서 socket.getInputStream()을 블로킹 read하여 port.write()로 전달.
  3. 클라이언트 연결 종료(EOF/예외)나 onRunError 발생 시 disconnectClient()가 호출되어 소켓과 USB 포트를 모두 정리하고, 다음 클라이언트를 받을 수 있는 상태로 복귀.
  4. stop() 호출 시 서버 소켓을 닫아 accept 루프를 빠져나오게 하고, 열려 있던 클라이언트/USB 포트를 정리.

3.2 수정 파일

파일변경 내용
utils/preferences/MainPreferences.kttcpBridgeEnabled: Boolean(기본 true), tcpBridgePort: Int(기본 TcpBridgeServer.DEFAULT_PORT = 8899) 프리퍼런스 추가
AppModule.ktKoin DI에 single<TcpBridgeServer> 등록 (VirtualSerialDriver, UsbManager, LoggerRepository 주입)
service/UsbBridgeService.ktonCreate()에서 prefs.tcpBridgeEnabled가 true면 tcpBridgeServer.start(prefs.tcpBridgePort) 호출, onDestroy()에서 tcpBridgeServer.stop() 호출. TCP_BRIDGE_HOST = "127.0.0.1" 상수 추가
ui/MainActivity.ktUSB 장치가 선택된 상태일 때 화면에 TCP bridge address: 127.0.0.1:8899 형태로 표시하는 로직 추가
res/layout/activity_main.xmlPTY 경로 표시용 bridgePathText 아래에 TCP 주소 표시용 bridgeTcpText TextView 추가
res/values/strings.xmlbridge_tcp_label = "TCP bridge address" 문자열 추가
AndroidManifest.xmlandroid.permission.INTERNET 권한 추가 (루프백 소켓이라도 안드로이드에서 필수)

4. 설정값

MainPreferences (SharedPreferences 기반)에 아래 값이 추가되었습니다.

var tcpBridgeEnabled by booleanPref(defaultValue = true)
var tcpBridgePort by intPref(defaultValue = TcpBridgeServer.DEFAULT_PORT) // 8899
  • tcpBridgeEnabled = false로 두면 UsbBridgeService가 TCP 브릿지를 아예 시작하지 않습니다.
  • tcpBridgePort 값을 바꾸면 다음 서비스 시작 시 해당 포트로 리슨합니다.
  • 현재는 두 값을 코드/설정으로만 바꿀 수 있고, 앱 UI에 별도 설정 화면은 아직 없습니다.
    (필요 시 MainActivity에 설정 다이얼로그를 추가해 tcpBridgeServer.start(newPort)
    다시 호출하도록 확장 가능)

5. 사용 방법

5.1 같은 기기 내 다른 앱/프로세스에서 접근

Socket socket = new Socket("127.0.0.1", 8899);

5.2 PC에서 접근 (adb 포트 포워딩)

adb forward tcp:8899 tcp:8899

이후 PC의 127.0.0.1:8899로 접속하면 안드로이드 기기의 USB 시리얼 장치와 통신할 수 있습니다. (예: 파이썬 pyserial이 아닌 순수 socket 모듈, 또는 nc 127.0.0.1 8899)

5.3 시리얼 파라미터

기본값은 **115200bps, 8 데이터비트, 1 정지비트, 패리티 없음(8N1)**입니다. TCP 자체에는 보드레이트 개념이 없으므로, USB 쪽 포트를 열 때만 이 값이 사용됩니다. 다른 값이 필요하면 TcpBridgeServer.setSerialParameters()를 호출해 변경할 수 있습니다.

tcpBridgeServer.setSerialParameters(
    baudRate = 9600,
    dataBits = UsbSerialPort.DATABITS_8,
    stopBits = UsbSerialPort.STOPBITS_1,
    parity = UsbSerialPort.PARITY_NONE
)

6. 동작/에러 처리 요약

상황동작
USB 장치 미선택 상태에서 TCP 클라이언트 접속접속 즉시 거부(소켓 close), 로그에 기록
이미 다른 TCP 클라이언트가 연결된 상태에서 추가 접속새 접속 즉시 거부
USB 포트 오픈 실패 (권한 없음 등)로그 기록 후 소켓 정리, 클라이언트 접속 실패로 종료
TCP 클라이언트가 연결을 끊음USB 포트를 닫고 다음 클라이언트를 받을 준비 상태로 복귀
USB 장치가 물리적으로 분리됨SerialInputOutputManager.onRunError 발생 → 클라이언트 연결 해제 및 정리
서비스 종료 (onDestroy)서버 소켓, 클라이언트 소켓, USB 포트 모두 정리 후 리슨 중단

7. 보안 관련 참고사항

  • TcpBridgeServer는 항상 127.0.0.1에만 바인딩됩니다. 즉 같은 기기 내부에서만 접속
    가능하며, Wi-Fi/모바일 네트워크를 통한 외부 접속은 불가능합니다.
  • 외부 네트워크에 노출하려면(권장하지 않음) InetAddress.getByName("127.0.0.1") 부분을
    InetAddress.getByName("0.0.0.0") 등으로 바꿔야 하며, 이 경우 인증 없이 USB 장치를 제어할
    수 있게 되므로 별도의 인증/접근 제어 로직이 필요합니다. 현재 구현에는 포함되어 있지 않습니다.

8. 빌드/테스트 관련 참고사항

  • 이번 변경은 Android SDK/Gradle 원격 저장소 접근이 없는 샌드박스 환경에서 작성되어
    실제 컴파일/실행 테스트는 수행하지 못했습니다. Android Studio에서 빌드 후 확인이 필요합니다.
  • 사용 중인 usb-serial-for-android 라이브러리 버전은 3.3.0이며, SerialInputOutputManager
    API(stop(), Listener.onNewData()/onRunError())는 기존 VirtualSerialDriver.kt
    동일한 방식으로 사용했습니다.
  • 빌드 후 확인 포인트
    • USB 장치 연결 → 앱 화면에 TCP bridge address: 127.0.0.1:8899 표시되는지
    • adb forward tcp:8899 tcp:8899 후 PC에서 접속 시 데이터 송수신이 정상인지
    • 클라이언트 연결 해제 후 재연결이 정상 동작하는지
    • 서비스 재시작(onDestroyonCreate) 시 포트 재바인딩이 정상인지 (Address already in use 에러 여부)

9. 향후 개선 아이디어 (선택 사항)

  • 앱 UI에 TCP 브릿지 on/off 스위치 및 포트 입력 필드 추가
  • TcpBridgeServer에 보드레이트 등 시리얼 파라미터를 UI에서 조정할 수 있는 화면 추가
  • 다중 클라이언트 지원이 필요하다면 브로드캐스트 방식(모든 클라이언트에 동일 데이터 전송)으로 확장
  • 외부 네트워크 노출이 필요한 경우를 위한 간단한 토큰 기반 인증 옵션 추가