# 약 버튼, 진짜로 만들었습니다 — 안 눌리고 안 붙고 글자 깨지던 일주일

- 작성자: [조용한 곰돌이](https://community.clauders.ai/member/imported-member-OnVpNuQ5M7)
- 공간: [에이전트 / 노코드 자동화](https://community.clauders.ai/automation)
- 게시: 2026-08-05
- 연재: [AI 스마트홈 기기 만들기](https://community.clauders.ai/series/smart-home-ai-devices) 2/2편
- 태그: #스마트홈AI
- 주소: https://community.clauders.ai/automation/post/%EC%95%BD-%EB%B2%84%ED%8A%BC-%EC%A7%84%EC%A7%9C%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4-%EC%95%88-%EB%88%8C%EB%A6%AC%EA%B3%A0-%EC%95%88-%EB%B6%99%EA%B3%A0-%EA%B8%80%EC%9E%90-%EA%B9%A8%EC%A7%80%EB%8D%98-%EC%9D%BC%EC%A3%BC%EC%9D%BC-3fc1ba2a-2f26-872e-9a52-cf885871d4e3

---

### ① "부품이 와야 시작할 수 있다"

위에 쓴 그대로입니다. 하드웨어 프로젝트라고 해서 하드웨어부터 할 필요가 없었습니다. **경계(HTTP)를 먼저 정하면 양쪽을 따로 만들 수 있습니다.**

### ② "버튼이 안 눌리면 코드나 핀 설정 문제다"

배선하고 눌렀는데 시리얼에 아무것도 안 뜹니다. 코드를 몇 번이나 다시 봤습니다.

그러다 방법을 바꿨습니다. **버튼을 빼고 점퍼선으로 GPIO4와 GND를 직접 맞댔습니다.**

```

"눌림!"

```

떴습니다. 이 순간 문제가 확정됐습니다. 핀도 코드도 정상이고, **범인은 아케이드 버튼 단자**입니다. 클립이 단자에 제대로 안 물려 있었습니다.

배운 건 "의심 대상을 반으로 자르는 테스트를 먼저 하라"입니다. 코드를 열 번 다시 읽는 것보다 점퍼선 한 번이 빨랐습니다.

### ③ "같은 집 와이파이면 서로 통한다"

WiFi는 붙었는데 서버에 못 갑니다. 화면엔 `http -1`.

한참 뒤에 알았습니다. **맥미니는 5GHz에, ESP32는 2.4GHz에 붙어 있었습니다.** ESP32는 2.4GHz만 지원합니다. 그리고 공유기가 대역 간 통신을 막고 있어서, 같은 집 같은 공유기인데 **서로 다른 방에 있는 것처럼** 안 보였습니다.

맥미니를 2.4GHz로 옮기니 바로 됐습니다.

### ④ "IP 고정은 IP만 박으면 된다"

미니 IP가 바뀌면 버튼이 길을 잃으니 고정을 했습니다. 그랬더니 **인터넷이 죽었습니다.**

`networksetup -setmanual`로 IP를 수동 지정하면, DHCP가 같이 주던 **DNS 서버 주소가 날아갑니다.** IP는 멀쩡한데 도메인 이름을 못 찾아서 아무것도 안 됩니다.

```
networksetup -setdnsservers Wi-Fi 1.1.1.1 8.8.8.8
```

DNS를 따로 넣어주니 해결됐습니다. "고정 IP = IP + 서브넷 + 게이트웨이 + **DNS**"입니다.

### ⑤ "한글이 깨지면 폰트를 넣으면 된다"

OLED에 한글이 네모로 깨졌습니다. 기본 폰트에 한글 글리프가 없어서입니다.

한글 폰트를 펌웨어에 넣을까 하다가 멈췄습니다. **그러면 "펌웨어는 멍청하게" 원칙이 깨집니다.** 폰트를 넣는 순간 펌웨어가 무거워지고, 나중에 문구를 바꾸려면 다시 플래싱해야 합니다.

그래서 서버가 **한글과 영문을 둘 다 내려주게** 했습니다.

```
{
  "title": "오늘 완료 · 12:39",
  "subtitle": "14일 중 10일",
  "ascii_title": "DONE 12:39",
  "ascii_subtitle": "10 of 14 days"
}
화면엔 ascii_*를 쓰고, 한글은 로그용으로 씁니다. 원칙을 지키면서 문제를 넘겼습니다.
```

### ⑥ "연결이 안 되면 신호가 약한 거다"

이게 이번 주 마지막이자 제일 재밌었던 겁니다.

다 만들고 벽에 붙였는데, **가끔** `http -1`이 뜹니다. 며칠에 한두 번. 그러다 가만 놔두면 혼자 정상으로 돌아옵니다.

당연히 신호가 약한 줄 알았습니다. 그런데 확인해보니 서버는 멀쩡하고, 미니 신호도 -50dBm으로 아주 좋았습니다.

그래서 미니에서 버튼으로 핑을 240번 때려봤습니다.

| 대상 | 손실률 | 평균 지연 | 최대 지연 |

|---|---|---|---|

| **버튼(ESP32)** | **0.0%** | **192 ms** | **1267 ms** |

| 다른 기기 | 0.0% | 16 ms | 118 ms |

| 공유기 | 0.0% | 12 ms | 105 ms |

**손실이 하나도 없습니다.** 신호가 약하면 반드시 패킷을 잃습니다. 그런데 하나도 안 잃고 **지연만 다른 기기의 12~16배**로 튀었습니다.

이건 "전파가 안 닿는다"가 아니라 **"칩이 자고 있다가 늦게 깬다"**는 신호입니다.

ESP32는 `WiFi.begin()`만 부르면 **기본이 절전 모드**입니다. 비콘 주기에만 깨어나고, 그 사이 온 패킷은 공유기가 들고 기다립니다. 평소엔 문제없다가 타이밍이 어긋나면 TCP 연결이 제한 시간을 넘겨 실패합니다. 간헐적이고, 놔두면 혼자 낫는 이유가 이거였습니다.

```
WiFi.setSleep(false);   // 벽 전원인데 절전할 이유가 없다
```

같이 찾은 함정 하나 더. 저는 이렇게 써놨었습니다.

```
http.setTimeout(5000);
```

이건 **응답을 기다리는 시간**이지 TCP 연결 제한 시간이 아닙니다. 연결 단계에서 물리던 게 문제였으니 정작 필요한 건 이거였습니다.

```
http.setConnectTimeout(8000);
```

## 자잘하지만 중요했던 결정들

**하루 경계를 자정이 아니라 새벽 4시로.** 밤 11시 55분에 약을 먹고 5분 뒤 날짜가 넘어가면 "오늘 안 먹음"이 됩니다. 새벽 4시를 기준으로 하니 이 문제가 사라졌습니다.

**두 번 눌러도 한 번만 기록.** `{기기}:{부팅번호}:{순번}`을 키로 써서 같은 요청이 두 번 도착해도 한 번만 들어갑니다. 재시도를 마음 놓고 넣을 수 있게 됐습니다.

**같은 DB를 보는데 화면 두 곳이 다른 말을 했습니다.** "오늘 상태"는 취소를 반영하는데, 옆의 "14일 기록"은 취소한 날도 먹은 걸로 칠했습니다. 상태 계산은 이벤트를 재생하는데, 기록 계산은 `taken`만 세고 있었습니다. **같은 규칙을 두 군데에 각각 구현하면 언젠가 반드시 어긋납니다.**

**결번이 알려준 것.** 기록 순번이 …11, (12, 13 없음), 14… 로 비어 있었습니다. 누리가 실제로 누른 두 번이 그냥 사라진 겁니다. 순번을 넣어둔 덕에 **"실패했다"가 아니라 "몇 번 실패했다"**를 알 수 있었습니다.

이번 주에 배운 것

**1. 에러 코드를 먼저 해석하자.** `http -1`은 "서버가 거절했다"가 아니라 "연결 자체를 못 했다"입니다. 이걸 알고 나니 서버 로그를 뒤질 이유가 없어졌습니다.

**2. 반으로 자르는 테스트가 코드 정독보다 빠르다.** 점퍼선 한 번으로 버튼/핀/코드 중 범인이 확정됐습니다.

**3. 증상이 아니라 숫자를 봐야 한다.** "가끔 안 된다"로는 아무것도 못 합니다. **손실률 0% + 지연 192ms**라는 숫자가 나오자 원인이 한 방에 좁혀졌습니다. 신호 문제와 절전 문제는 증상이 똑같지만 숫자가 다릅니다.

**4. 원칙을 지키면 우회로가 보인다.** 한글이 깨졌을 때 폰트를 넣는 건 쉬운 길이었지만 원칙을 깼을 겁니다. 원칙을 지키려고 버티니 "서버가 둘 다 내려준다"는 더 나은 답이 나왔습니다.

**5. 삽질을 글로 남겨두면 자산이 된다.** 위 내용을 리포지토리 README에 그대로 적어뒀습니다. 반년 뒤에 또 `http -1`을 만나면 저는 분명 까먹었을 테니까요.
