2026년 7월 30일 목요일

AI는 어떻게 개발자들의 벽을 허물었나 3 - 운영체제·임베디드까지 넓어진 실험의 범위

시리즈 · AI는 어떻게 개발자들의 벽을 허물었나

연재글 · 연재 중

3회 · AI는 어떻게 개발자들의 벽을 허물었나 3 - 운영체제·임베디드까지 넓어진 실험의 범위

웹·게임·임베디드의 개발 장벽과 AI 도구가 이를 재구성하는 방식을 살펴보는 3부 연재의 마지막 글이다. 이 글에서는 ‘어떻게 구현하는지’를 코드와 명령어로 살펴보고 구체적인 기술장벽의 실체를 살짝이나마 엿본다. AI가 강하게 낮추는 벽은 바로 이런 종류다. 어떤 OS API가 시작점인지, 콜백이 어디로 돌아오는지, 어떤 로그를 열어야 하는지, 보드와 서버를 어떤 순서로 연결하는지를 한 번에 묶어 준다.

아래 예시는 소위 오토마우스 구현을 위한 자동화된 Windows UI 입력, 모바일 블루투스 장치 컨트롤을 위한 Android BLE, 하드웨어 임베디드를 위한 Raspberry Pi 기반 장난감 자동차라는 세 개의 작은 프로젝트의 기술장벽의 일부를 코드레벨로 살펴보자. 이 글에서는 실질적인 기술장벽의 실체에 대한 그 한 꼭지들을 다룬다. 더 깊은 이슈와 그 장벽들 예컨데 프로세스 메모리·후킹·커널 드라이버·안티치트 우회는 이 글의 범위가 아니다.

1. 오토 마우스의 UI 층: 좌표를 SendInput 이벤트로 바꾸기

Windows에서 ‘오토 마우스’의 가장 바깥쪽 구현은 의외로 단순하다. 화면에서 클릭할 픽셀 좌표를 정하고, 이를 0~65535의 절대 좌표로 정규화한 뒤, 이동·왼쪽 버튼 down·up을 INPUT 배열로 넣어 SendInput에 전달한다. Microsoft의 SendInput 문서는 이 함수가 키보드·마우스 이벤트를 입력 스트림에 순서대로 넣는다고 설명한다.

CPP
// Win32 / User32.lib. 지정한 가상 데스크톱 좌표를 한 번 클릭한다.
#include <windows.h>
#include <array>

bool clickAt(long pixelX, long pixelY) {
    const long left = GetSystemMetrics(SM_XVIRTUALSCREEN);
    const long top = GetSystemMetrics(SM_YVIRTUALSCREEN);
    const long width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    const long height = GetSystemMetrics(SM_CYVIRTUALSCREEN);

    const long normalizedX = (pixelX - left) * 65535L / (width - 1);
    const long normalizedY = (pixelY - top) * 65535L / (height - 1);

    std::array<INPUT, 3> input{};
    input[0].type = INPUT_MOUSE;
    input[0].mi.dx = normalizedX;
    input[0].mi.dy = normalizedY;
    input[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE |
                          MOUSEEVENTF_VIRTUALDESK;
    input[1].type = INPUT_MOUSE;
    input[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
    input[2].type = INPUT_MOUSE;
    input[2].mi.dwFlags = MOUSEEVENTF_LEFTUP;

    return SendInput(static_cast<UINT>(input.size()), input.data(), sizeof(INPUT))
           == input.size();
}

핵심은 MOUSEEVENTF_ABSOLUTE가 픽셀을 직접 받는 것이 아니라 정규화 좌표를 받는다는 점이다. 멀티 모니터에서는 MOUSEEVENTF_VIRTUALDESK와 가상 데스크톱의 left/top/width/height를 함께 써야 한다. 클릭 대상이 고정 좌표가 아니라 버튼이라면 다음 단계는 화면 캡처 → 템플릿 매칭 또는 OCR → 버튼 중심 좌표 산출 → clickAt() 호출이다. 이 흐름이 UI 자동화의 기술적 뼈대다.

POWERSHELL
# 빌드와 실행: Visual Studio Developer PowerShell에서
cl /std:c++20 /EHsc auto_click.cpp user32.lib
.\auto_click.exe

익숙한 개발자들에게는 "Hello World" 수준의 단순 빌드겠지만 타 분야의 개발자들은 이런 생소한 언어의 단순 컴파일 조차 허들이 되는 경우가 허다했다. SendInput은 User32 입력 계층의 API다. 이 글은 여기까지, 즉 OS가 제공하는 UI 이벤트 생성과 화면 좌표 처리만 다룬다. UIPI 제약과 앱별 입력 처리 차이는 실제 실행 때 확인할 운영체제 조건이다. 오토마우스 프로그램이 이렇게 허술하지는 않겠지만 자동화된 입력큐의 구현은 이 정도의 Input 트리거가 어떻게 보면 자동화된 마우스 입력의 핵심 허들 포인트라고 볼 수 있다.

A Windows input flow from screen pixels through normalized coordinates to SendInput events

<Windows UI 입력과 Android BLE의 시작 지점을 연결한 구현 흐름 3.1>

2. Android Bluetooth LE: 스캔 콜백부터 GATT notification까지

Bluetooth 이어폰이든 센서든 Android 앱의 BLE(블루투스) 제어는 세 클래스가 중심이다. BluetoothLeScanner는 주변 장치를 찾고 ScanCallback으로 결과를 받는다. 찾은 BluetoothDeviceconnectGatt()로 연결한다. 이후 연결·서비스 탐색·읽기·notification은 BluetoothGattCallback으로 돌아온다.

이것이 ‘블루투스 콜백 구현’ 뒤에 붙는 실질적인 허들 포인트이다.

Android 12 이상에서는 다음 권한을 manifest에 선언하고 런타임에도 요청한다. Android Bluetooth 권한 문서BLUETOOTH_SCANBLUETOOTH_CONNECT의 용도를 정리한다.

XML
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

아래 Kotlin 예시는 소유한 BLE 테스트 장치의 advertised name을 찾고, 연결 후 문서화된 service UUID와 characteristic UUID를 찾아 notification을 켠다. UUID는 이어폰마다 임의로 추측하는 값이 아니라 장치 제조사가 공개했거나 nRF Connect에서 확인한 GATT 계약 값으로 바꿔야 한다.

KOTLIN
private val serviceUuid = UUID.fromString("12345678-1234-1234-1234-1234567890ab")
private val notifyUuid = UUID.fromString("12345678-1234-1234-1234-1234567890ac")
private val cccdUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")

private val scanCallback = object : ScanCallback() {
    override fun onScanResult(type: Int, result: ScanResult) {
        if (result.device.name == "LAB_SENSOR") {
            bluetoothAdapter.bluetoothLeScanner.stopScan(this)
            result.device.connectGatt(this@MainActivity, false, gattCallback)
        }
    }
}

private val gattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, state: Int) {
        if (status == BluetoothGatt.GATT_SUCCESS && state == BluetoothProfile.STATE_CONNECTED) {
            gatt.discoverServices()
        } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
            gatt.close()
        }
    }

    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
        val characteristic = gatt.getService(serviceUuid)?.getCharacteristic(notifyUuid) ?: return
        gatt.setCharacteristicNotification(characteristic, true)
        val cccd = characteristic.getDescriptor(cccdUuid) ?: return
        cccd.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
        gatt.writeDescriptor(cccd)
    }

    override fun onCharacteristicChanged(
        gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
    ) {
        Log.d("BLE", "${characteristic.uuid}: ${value.joinToString { "%02x".format(it) }}")
    }
}

onConnectionStateChange()은 링크 상태를, onServicesDiscovered()는 GATT 테이블을, onCharacteristicChanged()는 장치가 push한 값을 받는다. Android API 33부터는 마지막 콜백이 ByteArray value를 직접 받는 새 시그니처를 쓰는 편이 안전하다. BluetoothGattCallback 레퍼런스가 이 변경과 각 콜백의 의미를 명시한다.

3. BLE가 안 붙을 때: nRF Connect, HCI, ATT, Wireshark를 어디에 쓰는가

BLE가 실패하는 경우도달하는 계층을 나눠 보면 failed point를 이해할 수 있다. nRF Connect는 휴대폰에서 주변 장치를 스캔하고 GATT service·characteristic을 직접 탐색·읽기·구독하는 테스트 앱이다. 즉 앱 코드의 UUID와 장치 GATT 계약이 맞는지 먼저 확인하는 도구다. **ATT(Attribute Protocol)**는 GATT가 characteristic 값을 읽고 쓰는 데이터 프로토콜이다. **HCI(Host Controller Interface)**는 Android Bluetooth host와 실제 Bluetooth controller 사이의 패킷 계층이다. Wireshark는 HCI snoop 파일을 열어 광고, 연결, ATT read/write/notification이 실제로 오갔는지 보는 분석기다. 이런 테스트가 까다로운 장비들을 테스트하는 도구, 장치, 방법등은 장치 디바이스 코딩의 실질적인 대표적 허들들이다.

먼저 디바이스를 수신하는 OS 레벨에서 앱과 서비스 상태는 다음 명령으로 본다.

BASH
# 연결 상태·adapter·profile 정보를 OS 서비스에서 덤프한다.
adb shell dumpsys bluetooth_manager

# 앱의 BluetoothGatt 콜백과 프레임워크 로그를 실시간으로 본다.
adb logcat -v time | grep -E "BluetoothGatt|BtGatt|Bluetooth|BLE"

그다음 운영체제의 Developer options에서 Enable Bluetooth HCI snoop log를 켜고 Bluetooth를 다시 시작한다. AOSP의 Bluetooth 검증·디버깅 문서dumpsys bluetooth_manager, bug report의 btsnooz.py BUG_REPORT.txt > BTSNOOP.log, 그리고 HCI snoop 로그 수집 절차를 제공한다.

BASH
# 재현 직후 bug report를 받아 HCI snoop을 추출한다.
adb bugreport ble-report.zip
unzip ble-report.zip -d ble-report
btsnooz.py ble-report/bugreport-*.txt > BTSNOOP.log
# BTSNOOP.log를 Wireshark로 열어 HCI와 ATT 패킷을 시간순으로 확인한다.

이 순서면 블루투스 신호의 어디가 문제인지 좁힐 수 있다. 주변에 수많은 장비들이 보내는 신호들을 마구잡이로 수신하는 OS레벨에서 내 장비의 신호를 구별해 내고 해당 신호만 정제하는 핵심 기술이 드라이버 기술이다. 만약 nRF Connect에서도 service가 안 보이면 장치 광고·연결·GATT 자체 문제다. nRF에서는 보이는데 앱만 안 되면 권한, UUID, 콜백 순서, descriptor write를 먼저 본다. HCI에는 write가 있는데 notification이 없으면 peripheral firmware나 characteristic subscription을 본다. 코드·운영체제·controller 로그가 하나의 시간축으로 이어지는 지점이 바로 AI가 개발자의 탐색 시간을 크게 줄이는 곳이다. 조금 어려운 말들이 나온다고 쫄 필요없다. 이제 우리는 이런 수많은 용어, 방법, 해석을 직접할 필요가 없지 않은가. 중요한것은 그 허들이 어디쯤인지를 인지하는 것 뿐이다.

4. 임베디드 장비를 붙여보기위한 Raspberry Pi 자동차: 모터, 센서, UART

코드를 보기 전에 부품이 맡는 층을 먼저 나눠야 한다. 이 자동차는 ‘Linux 컴퓨터가 판단하고, GPIO가 명령 신호를 내고, 모터 드라이버가 전류를 바꾸며, 센서·MCU가 입력을 돌려주는’ 구조다.

용어 이 구성에서 맡는 역할 연결에서 확인할 것
Raspberry Pi Linux·Python·네트워크를 실행하는 메인 컴퓨터 흔히 경량 리눅스 운영체제를 깔고 실행할수 있는 기판이라고 생각하면 쉽다 GPIO 핀맵, 카메라, /dev/tty*, Wi-Fi/Ethernet
GPIO Pi가 0/1·PWM 같은 저전압 논리 신호를 내고 받는 핀 BCM 번호와 물리 핀 번호, 3.3V 논리 레벨
H-bridge 모터 전류의 방향을 바꿔 전진·후진을 만드는 전력 스위치 Pi의 GPIO 입력(IN1/IN2/PWM)과 모터 전원 분리
TB6612FNG / L298N H-bridge를 모듈로 만든 대표 모터 드라이버 enable/STBY, 모터 전원, GND 공통 연결
UART TX/RX 두 선으로 바이트를 주고받는 직렬 통신 포트명, baud rate, TX와 RX를 교차 연결
MCU·센서 거리·속도·ADC 같은 현장 입력을 읽어 Pi로 전달하는 보조 장치 전송 포맷(JSON 등), 샘플 주기, 센서 전원

따라서 장난감 자동차의 최소 구성은 Raspberry Pi + H-bridge 모터 드라이버 + 거리 센서 + UART로 연결된 보조 MCU 또는 센서다. Raspberry Pi가 “앞으로 45% 출력”이라는 논리 명령을 GPIO로 내면, H-bridge가 별도 모터 전원으로 실제 바퀴를 돌린다. MCU는 센서값을 UART로 Pi에 돌려주고, Pi는 그 값을 다시 Python·네트워크·카메라 코드와 결합한다. 모터는 GPIO에 직접 꽂지 않고 TB6612FNG·L298N 같은 H-bridge 보드를 거친다. Raspberry Pi 문서도 모터를 GPIO에 직접 연결하지 말고 motor controller를 사용하라고 안내한다. GPIO 하드웨어 문서에서 핀맵은 pinout 명령으로 바로 확인할 수 있다.

여기서 중요한 질문은 “센서의 신호가 Pi에 들어오면 무엇이 실제로 깨어나는가”다. 답은 센서 종류에 따라 다르다. 라즈베리 파이에 센서를 꽂는다는 말은 아래 셋 중 하나의 경로를 고르는 일이다.

센서의 출력 Pi 안에서 지나는 길 프로그램에서 보이는 모습
버튼·PIR·센서의 INT/DRDY처럼 0V/3.3V가 바뀌는 디지털 선 GPIO 핀 → SoC GPIO 컨트롤러 → 상승/하강 엣지 → Linux GPIO 이벤트 when_pressed 같은 이벤트 콜백 또는 gpiomon 대기
I2C·SPI·UART로 바이트를 보내는 센서·MCU I2C/SPI/UART 주변장치 → FIFO → 하드웨어 인터럽트 → Linux 드라이버 버퍼 read()·readline()으로 블로킹 대기
가변저항처럼 연속 전압을 내는 아날로그 센서 Pi에는 ADC가 없으므로 외부 ADC나 MCU가 먼저 디지털 값으로 변환 SPI/I2C/UART로 변환된 값을 읽기

즉 전압이 변하면 SoC의 GPIO 또는 통신 주변장치가 먼저 감지한다. GPIO 입력은 상승·하강 엣지 또는 high/low level 인터럽트 원천으로 설정할 수 있다. Linux는 그 이벤트를 드라이버와 파일 디스크립터에 전달하고, 사용자 프로그램은 이벤트를 기다리거나 라이브러리 콜백을 등록한다. Raspberry Pi의 GPIO 문서도 각 GPIO 입력이 Arm의 인터럽트 원천이 될 수 있다고 설명한다. GPIO interrupt 문서를 보면 상승·하강 엣지와 level interrupt의 차이도 확인할 수 있다.

폴링, 블로킹 읽기, 콜백은 서로 다른 말이다. 이 차이를 먼저 잡아야 센서 코드를 제대로 읽을 수 있다.

코드 형태 실제 방식 자동차에서의 역할
while True: sensor.distance; sleep(0.05) 50ms마다 현재 값을 물어보는 폴링 HC-SR04처럼 Pi가 trigger를 먼저 보내야 하는 거리 측정
motor.forward(0.45) PWM 출력 바퀴를 돌리는 명령이며 입력 수신은 아님
uart.readline() UART 드라이버 버퍼에서 데이터가 올 때까지 잠드는 블로킹 읽기 MCU telemetry를 CPU 낭비 없이 받기
sensor.when_pressed = handler GPIO 엣지를 기다렸다가 라이브러리가 사용자 함수를 실행 INT·DATA_READY 핀이 있는 센서의 새 데이터 알림

엄밀히 말하면 Linux 커널이 Python 함수를 직접 호출하는 것은 아니다. 전압 변화 → GPIO 하드웨어 인터럽트 → 커널 드라이버 이벤트 → 기다리던 사용자 프로세스 또는 라이브러리 스레드가 깨어남 → handler 실행 순서다. gpiozerowhen_pressed는 이 마지막 사용자 공간 콜백을 편하게 만든 API다. gpiozero 입력 이벤트 문서is_pressed 폴링, wait_for_press() 블로킹 대기, when_pressed 콜백을 각각 구분한다.

1단계: 핀과 장치 파일을 먼저 확인한다

BASH
pinout
ls -l /dev/serial0 /dev/ttyACM* /dev/ttyUSB* 2>/dev/null
sudo usermod -a -G gpio "$USER"
python3 -m pip show gpiozero pyserial requests

pinout은 물리 핀과 BCM 번호를 대조할 때 쓰고, /dev/ttyACM0·/dev/ttyUSB0은 USB/UART MCU가 Linux에 만든 장치 파일이다. GPIO 5라고 썼다면 물리 5번 핀이 아니라 BCM 번호 5번이라는 뜻이다.

2단계: HC-SR04처럼 요청해야 답하는 센서는 폴링한다

아래 예시는 gpiozero.Motor로 H-bridge의 IN1/IN2를, DistanceSensor로 HC-SR04 계열의 trigger/echo를 읽는다. HC-SR04는 Pi가 trigger 펄스를 보내고 echo 왕복 시간을 재야 값을 얻으므로, 여기서는 20Hz 폴링이 맞다. Motor의 핀 번호는 BCM 기준이며 실제 배선과 driver board enable/STBY 핀은 보드에 맞춰 조정한다.

PYTHON
# car_control.py
from gpiozero import Motor, DistanceSensor
from time import sleep, monotonic

motor = Motor(forward=17, backward=27, enable=18)
range_sensor = DistanceSensor(echo=24, trigger=23, max_distance=2.0)

while True:
    distance_m = range_sensor.distance * 2.0
    command = "forward" if distance_m > 0.35 else "stop"
    if command == "forward":
        motor.forward(0.45)
    else:
        motor.stop()
    print(f"ts={monotonic():.3f} distance_m={distance_m:.3f} command={command}", flush=True)
    sleep(0.05)
BASH
python3 -u car_control.py | tee car-control.log
tail -f car-control.log

3단계: INT 또는 DATA_READY 핀이 있는 센서는 엣지 이벤트로 받는다

가속도계·ADC·일부 거리 센서는 새 측정값이 준비되었음을 알리는 INT 또는 DATA_READY 핀을 제공한다. 이 핀을 Pi GPIO에 연결하면 매 50ms마다 값을 물을 필요가 없다. 예를 들어 센서가 BCM GPIO 5에 active-high DATA_READY 펄스를 보낸다고 하자.

PYTHON
# data_ready.py: GPIO 엣지는 짧게 표시하고, 실제 I2C/SPI 읽기는 일반 루프에서 한다.
from gpiozero import Button
from threading import Event

data_ready = Button(5, pull_up=False, bounce_time=0.002)
sample_ready = Event()

def mark_sample_ready():
    sample_ready.set()  # 콜백에서는 오래 걸리는 I2C·네트워크 작업을 하지 않는다.

data_ready.when_pressed = mark_sample_ready

while True:
    sample_ready.wait()       # CPU를 쓰지 않고 Linux GPIO 이벤트를 기다린다.
    sample_ready.clear()
    # 여기에서 smbus2/spidev로 센서 레지스터를 읽고 제어값을 계산한다.
    print("new sample: read the sensor over I2C/SPI here")

when_pressed는 하드웨어 ISR 자체가 아니라 사용자 공간 콜백이다. 그럼에도 프로그램 관점에서는 “센서가 새 데이터 준비 신호를 내면 코드가 실행된다”는 push 방식이다. Linux GPIO 문자 장치 API도 상승·하강 엣지 이벤트를 사용자 공간에 전달하며 시간정보와 순번을 함께 제공한다. 더 낮은 층을 확인하고 싶다면 libgpiod 도구로 실제 엣지를 먼저 관찰할 수 있다.

BASH
# 먼저 실제 gpiochip 이름과 line 정보를 확인한다.
gpiodetect
gpioinfo

# gpiodetect 결과의 gpiochip 이름으로 바꾼다. line 번호는 BCM 번호라고 가정하지 말고 gpioinfo로 확인한다.
gpiomon -r -f gpiochip0 5

4단계: UART는 블로킹 읽기로 받는다

UART가 붙은 Arduino·센서 보드는 센서 측에서 한 줄씩 JSON을 보내고 Pi에서 읽는다. 이것이 기판과 Linux 애플리케이션 사이의 가장 단순한 telemetry 통로다. 여기서 readline()은 50ms마다 빈 버퍼를 확인하는 busy polling이 아니다. UART 하드웨어가 바이트 도착을 인터럽트로 알리고, Linux TTY 드라이버가 커널 버퍼에 넣은 뒤, 잠들어 있던 readline()을 깨운다.

CPP
// Arduino: 초음파/ADC 값 대신 예시 센서 값을 UART로 송신한다.
void setup() { Serial.begin(115200); }
void loop() {
  int raw = analogRead(A0);
  Serial.print("{\"raw\":"); Serial.print(raw);
  Serial.println(",\"source\":\"mcu\"}");
  delay(100);
}
PYTHON
# Pi: timeout=None은 한 줄이 올 때까지 블로킹한다.
# /dev/ttyACM0은 `ls /dev/ttyACM*` 결과로 확인한 실제 포트로 바꾼다.
import json, serial
with serial.Serial("/dev/ttyACM0", 115200, timeout=None) as uart:
    while True:
        line = uart.readline().decode(errors="replace").strip()
        if line:
            print(json.loads(line))

정리하면 폴링만 가능한 것이 아니다. DATA_READY가 있으면 GPIO 엣지 이벤트, UART처럼 스트림을 보내면 블로킹 readline(), I2C 센서가 요청에만 답하면 필요한 주기의 폴링을 고른다. 센서의 데이터시트에서 먼저 볼 항목은 “측정값 레지스터”만이 아니라 INT, DRDY, DATA_READY, FIFO 같은 단어다. 이 한 줄이 있으면 센서 → 운영체제 → Python 제어 루프의 구현 방식이 달라진다.

A Raspberry Pi car flow connecting sensor and UART input to motor control, camera capture, and a network server

<Raspberry Pi 자동차에서 센서·UART·모터·카메라·서버를 잇는 데이터 흐름 3.2>

5. 카메라 프레임을 서버로 보내기: Picamera2와 HTTP

카메라까지 붙이면 자동차는 센서값뿐 아니라 프레임을 서버에 보낼 수 있다. 최근 Raspberry Pi OS에서는 Picamera2libcamera/rpicam 스택 위에서 동작한다. Raspberry Pi 카메라 문서Picamera2 매뉴얼capture_array()로 NumPy 프레임을 얻는 흐름을 설명한다.

BASH
rpicam-hello -t 3000
sudo apt install -y python3-picamera2 python3-opencv python3-requests
PYTHON
# camera_uploader.py: 프레임을 JPEG로 바꿔 개발 서버에 multipart POST한다.
from picamera2 import Picamera2
import cv2, requests, time

SERVER = "http://192.168.0.10:8080/telemetry/frame"
camera = Picamera2()
camera.configure(camera.create_preview_configuration(main={"size": (640, 480), "format": "RGB888"}))
camera.start()

while True:
    frame = camera.capture_array("main")
    ok, jpeg = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 75])
    if ok:
        response = requests.post(
            SERVER,
            files={"frame": ("frame.jpg", jpeg.tobytes(), "image/jpeg")},
            data={"source": "toy-car", "captured_at": str(time.time())},
            timeout=2,
        )
        print("upload", response.status_code, len(jpeg))
    time.sleep(0.2)

서버 쪽은 처음에는 프레임을 받아 상태 코드만 돌려주는 정도면 충분하다. 이후 MQTT로 모터 명령을 분리하거나, 서버에서 객체 탐지 결과만 반환해 Pi가 판단에 사용하도록 확장할 수 있다. 이때 AI가 특히 잘하는 일은 GPIO 핀, UART baud rate, camera format, HTTP payload, systemd 서비스 로그처럼 서로 다른 문서에 흩어진 연결 규칙을 하나의 실행 가능한 초안으로 합치는 것이다.

임베디드가 어렵다고 느껴지는가? 필자도 마찬가지다. 하드웨어 센서들의 스펙과 그 콜백 구조를 모두 알아야 정확한 폴링구조와 센싱 값의 스펙을 분류하고 원하는 신호해석과 동작을 구현할 수 있다. 그러나 이제 그 허들의 실체를 눈으로 확인한거로 족하다. 이 허들을 보았으니 AI에게 질문할 수 있고 AI의 답을 재질문하며 딥 다이브를 할 수 있다.

3화의 핵심은 다양한 분야별 문턱을 눈으로 확인하는 것이다. 그리고 그 문턱의 실체를 비로소 AI에게 질문하는 것이다. AI는 이미 그 문제의 답을 어느정도 알고 있다. 애초에 문지방이 어디있는지 조차 몰랐던 우리가 이제 AI를 이용해 그 문턱의 실체를 확인하고 그 턱을 넘어가는 방법을 익히며 비로소 완성형까지도 노려볼수 있는 시대가 되었다는 것을 깨닳자.

마치며

과거 각 분야를 혼자서 감당 하기에는 상당한 분량과 방대한 지식을 필요로 했었다. 이 기술들로 산업을 꾸리는 업체들은 이 모든 기술과 역량으로 수익까지 실현해야 하니 산업현장 난이도는 더욱 올라간다. 그런데 AI가 등장했다. 많던 적던 수많은 운영체제의 기초 코드, 임베디드 코드, 가상화 기술, 수많은 공개된 오픈소스 기술, 모든 언어, 오래된 파이썬, 스크립트, 디자인을 모두 흡수한 AI는 인간의 응용력을 가뿐히 뛰어 넘는다. AI는 운영체제의 모든 API 세트를 알고 있고 Windows API를 이용해 손쉽게 윈도우의 창을 띄운다. 띄운 윈도우 창에서 윈도우즈 운영체제가 지원하는 모든 컴포넌트, 하드웨어 제어, 임베딩 드라이버 목록, 제어, 후킹 을 제한없이 알려준다. 방화벽, 권한 문제도 체계적으로 접근하거나 얻어오도록 알려준다. 키보드, 마우스 HID의 제어는 이제 누워서 떡먹기다. 윈도우즈가 제공하는 화면의 캡쳐, 이미지 프로세싱, 이미지 분석, 거대 LLM AI까지 붙이면 실시간 이미지를 분석까지 한다. 자동화 이벤트 발신 및 트리거 등 옛날의 그 얇팍했던 윈도우즈 커널 허들을 가뿐하게 넘어준다.

하드웨어의 컨트롤이 윈도우 뿐아니라 안드로이드여도 문제되지 않는다. 이미 공개되어 있는 AOSP(안드로이드 운영체제 오픈소스)는 안드로이드 운영체제의 파운데이션 코드가 공개되어 있다. AOSP 아키텍처 개요는 AOSP를 공개·수정 가능한 Android 소스라고 설명한다. 이는 학습한 AI는 모바일의 구동, 드라이버의 구현과 그 원리를 모를리가 없다. 당연히 모바일 블루투스, C-Type Cable 인터페이스, NFC의 기초 기술들은 AI를 통해 그 기초를 손쉽게 넘어버린다. 리눅스 운영체제 기반으로 동작하는 라즈베리파이, 아두이노 역시 마찬가지이다. 리눅스 운영체제와 기판을 연동하는 임베디드 드라이버 기술은 그 자체로 공공연한 비밀 같았는데 이제 AI 앞에서 비밀따위는 없다. 모든 기술, 테스트환경, 도구 등이 무엇이었는지 낱낱히 공개되었다. 개발자들에게 항상 외딴 섬 같았던 파이썬의 풍부한 기술들이 이제 AI로 인해 모든 개발자들에게 해금되었다. 기존 자바나 스크립트 레벨만 다루던 개발자들도 이제 C,C++,파이썬을 마음대로 활용할 수 있는 새로운 기회가 열린 것이다. 아직도 초기인 AI 바이브코딩 시장에서 개발자들은 아직 자신의 능력이 자신도 모르게 어디까지 확장되었는지 깨닳지도 못하고 있다. 이미 뻥튀기되어 거의 무한대로 뻗어진 자신의 기술력을 알기도 전에 AI에게 완전히 대체될지도 모를 일이다. AI의 파급력을 가장 먼저 얻어맞고 있는 개발자들은 이 외계기술 같은 AI를 가지고 감탄만 하면서 아직 능력의 10%도 못 사용하고 있다. 여러분은 이 외계의 기술의 극의를 어디까지 확인해 보았는가? 눈을 더욱 크게 떠야할 때이다.

이 글에서 살펴본 예시처럼 개발자 CLIAI 코딩 도구를 통해 낯선 분야에서도 어떤 클래스 → 어떤 콜백 → 어떤 로그 → 어떤 포트 → 어떤 테스트 순서로 질문하고 손을 움직일 수 있다. 그 흐름을 직접 실행하고 수정하는 순간, 언어와 플랫폼이 나눠 놓았던 벽은 이전보다 훨씬 낮아진다.

댓글 없음:

댓글 쓰기

404호 개발실 21 - 백화점

시리즈 · 404호 개발실 웹툰 · 연재 중 21회 · 404호 개발실 21 - 백화점 구글 백화점에서 옷을 찾다가 밤을 새웠습니다. <구글 백화점과 AI 코디네이터 21.1> 시리즈 전체 회차 1회 404...