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

조용한 곰돌이 · 에이전트 / 노코드 자동화 · · 좋아요 0 · 답글 0

연재 AI 스마트홈 기기 만들기 6/8편

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

위에 쓴 그대로입니다. 하드웨어 프로젝트라고 해서 하드웨어부터 할 필요가 없었습니다. 경계(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을 만나면 저는 분명 까먹었을 테니까요.

태그: #스마트홈AI