🔑 핵심 요약
✦ AI·GEN필자의 집 천장등은 이사 올 때부터 있던 것으로, 출처도 모델명도 알 수 없는 자가발전 무선 스위치로 켜고 끈다. 이를 Home Assistant로 제어하기 위해 필자는 먼저 Broadlink RM4 Pro로 꼬박 밤을 새웠다. 868MHz에서의 "학습 완료"는 노이즈였고, 기기가 보고한 433.46MHz는 스캐너가 놀고 있을 때 멈춰 있는 값이었으며, 직접 짠 도구에는 주파수 필드 하나가 통째로 빠져 있었다. 결국 Broadlink의 RF 학습은 특정 프로토콜만 인식하는 복조기라서 이런 스위치는 받을 수 없다는 결론에 이른다. NT$220짜리 ESP32-C3와 CC1101로 바꾼 뒤 진짜 열쇠는 symbol_rate였다. 잘못 설정했을 때 녹화된 가짜 코드의 변동계수는 7.3%로 진짜 코드의 9.4%보다도 낮았고, 필자는 "일관성"이 "정확성"을 뜻하지는 않는다는 교훈을 얻는다. 글 말미에는 3주 뒤의 후유증을 기록한다. restore_mode 기본값 때문에 재부팅할 때마다 조명이 토글된 일, 벽 너머의 실제 스위치 신호가 잡히지 않은 일, 그리고 하루 17번 WiFi가 끊긴 진짜 원인이 서버 뒤의 USB 포트였다는 것.
내 방 조명은 이사 왔을 때부터 있던 거고, 벽에 붙어 있는 건 전선도 없는 무선 스위치 하나다. 누가 달았는지, 어디서 샀는지, 무슨 브랜드인지 전혀 모른다.
집의 에어컨과 공기청정기를 하나씩 Home Assistant에 붙이고 나니 자연스럽게 이런 생각이 들었다. 조명도 되지 않을까? 마침 에어컨 때문에 산 Broadlink RM4 Pro가 있었고, 스펙에는 433MHz RF 학습을 지원한다고 적혀 있었다. 한 번 녹화하면 끝날 줄 알았다.
결과적으로 이틀 동안 녹화를 붙잡고 있었고, 벽의 스위치는 수백 번 눌러서 그게 먼저 고장 날까 봐 걱정될 정도였다. RM4 Pro는 처음부터 끝까지 단 한 번도 녹화에 성공하지 못했고, 결국 NT$220짜리 ESP32-C3에 CC1101을 붙여서 해결했다. 중간에 아주 음흉한 함정도 하나 밟았다. 통계적으로 완벽해 보이는 코드를 녹화했는데, 구워 넣고 나니 조명이 나를 완전히 무시했다.
이 글은 그 길을 처음부터 끝까지 따라간다. 이런 스위치가 대체 뭔지, 왜 RM4 Pro는 못 잡는지, CC1101은 어떻게 배선하고 설정하는지, 그리고 신호를 녹화할 때 스스로를 속이기 가장 쉬운 지점이 어디인지. 집에 이런 스위치가 있다면 후반부 설정은 그대로 베껴 가고, 코드만 직접 녹화한 걸로 바꾸면 된다.
먼저 이 스위치부터 이해하기#
떼어 보니 스위치 뒷면엔 아무것도 없었다. 전선도 없고 배터리도 없다. 이건 로, 누르는 그 순간 발전을 해서 무선 신호 한 번 보낼 만큼의 전기를 만든다. 누를 때 '딸깍, 딸깍' 두 번 소리가 나는데, 눌릴 때 한 번, 튀어 오를 때 한 번이다.
실제로 전원을 끊는 건 천장등 옆에 나중에 달린 작은 상자로, 라고 부른다. 거기엔 간체자로 적힌 '입력/출력' 단자, 열쇠 모양 버튼 하나, 파란 LED 하나뿐이고, 역시 브랜드나 모델명은 전혀 없다.
스마트 전구로 바꾸는 길은 처음부터 막혀 있었다. 조명은 DanceLight(舞光)의 24W 천장등이고, 광원은 기판에 납땜된 SMD LED 24개라서 바꿀 전구가 없다. 그러니 HA로 제어하려면 방법은 하나뿐이다. 내가 그 스위치인 척하고, 똑같은 신호를 수신기에 보내는 것.
문제는 그게 어떤 주파수, 어떤 포맷을 쓰는지조차 모른다는 거였다.
RM4 Pro: 에어컨은 순조로웠는데 조명은 전혀 안 됐다#
에어컨 쪽은 정말 순조로웠다. 적외선 코드 하나를 녹화해서 SmartIR 코드 DB와 비교했더니 Hisense 코드 하나가 걸렸고, 171 bit 중 93%가 일치해서 에어컨은 금방 HA에 들어갔다. 그래서 조명에 대한 기대도 컸다.
그리고 꼬박 밤을 새운 끝에 얻은 결론은 RM4 Pro가 이 스위치를 못 받는다는 거였다. 그 사이 가장 황당하게 틀렸던 몇 번은 기록해 둘 만하다. 매번 그 순간엔 진전처럼 보였으니까.
868 MHz에서 "학습 완료"#
RM4 Pro의 RF 학습은 첫 단계에서 리모컨을 길게 눌러 주파수를 스캔하게 해야 하는데, 자가발전 스위치는 한 번 누르면 몇 밀리초만 신호를 보내니 길게 누르는 게 애초에 불가능하다. python-broadlink 소스를 뒤져 보니 find_rf_packet()으로 주파수를 직접 지정해서 스캔을 건너뛸 수 있길래, 433.92, 315, 868 MHz를 번갈아 듣는 작은 도구를 짰다.
처음 돌리자마자 868.35 MHz에서 114 bytes를 학습했다고 떴다. 한 10초쯤 기뻐하다가, 패킷 타입이 0x00이라는 걸 알아챘다(유효한 건 적외선 0x26, 433의 0xb2, 315의 0xd7뿐이다). 펄스 폭도 15종류에 131µs부터 49260µs까지 걸쳐 있었다. RM4 Pro는 868MHz를 아예 지원하지 않는다. 지원하지 않는 주파수를 지정하면 에러를 내는 대신 노이즈를 그대로 뱉어 준다.
기기가 직접 보고한 주파수도 믿을 수 없다#
RM4 Pro의 자체 스캔으로 바꿨더니, 아주 자신 있게 433.460 MHz라고 보고했다. 그걸 믿고 도구를 전부 433.46으로 다시 녹화하고 다시 송신하게 고쳐서 또 몇 라운드를 돌렸다.
나중에 대조군을 하나 만들어 보려고 집의 T100 스피커 리모컨으로 스캔해 봤더니, 그것도 아주 자신 있게 305.000 MHz라고 보고했다. 그 리모컨은 적외선이다.
스캔의 원시 응답을 덤프해 보고서야 이해했다.
check_frequency 원시 응답:00 68 a7 04 00
↑ └─────────┘
│ 0x0004a768 = 305000 kHz
└─ is_found = 0(실제로는 못 찾음)첫 바이트가 0이라는 건 애초에 못 찾았다는 뜻이고, 305 MHz는 그냥 스캐너가 놀고 있을 때 멈춰 있는 위치였다. 앞의 433.46도 아마 같은 얘기였을 거다. 나는 가짜 숫자 하나를 들고 몇 라운드나 추론을 이어 갔던 셈이다.
수신기를 10초 길게 누르면 페어링이 전부 지워진다#
녹화가 안 되니 방향을 바꿨다. 수신기를 학습 모드로 넣고, 거꾸로 RM4 Pro가 보내는 새 코드를 학습시키는 거다. 하지만 모델명이 없으니 설명서도 없고, 추측할 수밖에 없었다.
열쇠 모양 버튼을 한 번 눌렀더니 조명이 꺼졌다. 그냥 수동 스위치였다. 이번엔 길게 누르면서 LED가 깜빡이기 시작하길 기다렸는데, 한 번, 두 번, 세 번, 네 번 깜빡이더니 벽의 스위치가 먹통이 됐다. 나중에 같은 종류의 수신기 설명서를 찾고서야 무슨 일이 있었는지 알았다.
WARNING
이런 무브랜드 수신기의 흔한 조작법은 3초 길게 눌러 1번 깜빡이면 학습 모드, 10초 길게 눌러 4번 깜빡이면 페어링 전체 삭제다. 나는 인터넷에서 말하는 "8번 연속으로 누르면 초기화된다"는 건 아주 조심스럽게 피했는데, 정작 길게 누르기로 전부 날려 버렸다.
전부 지워져도 당황할 필요 없다. 3초 길게 눌러 한 번 깜빡이면 손을 떼고, 벽의 스위치를 한 번 누르면 페어링이 돌아온다.
스위치 뜯어보기#
모르겠으면 뜯는다.
기판에는 정류 다이오드 4개, 승압 인덕터 1개, 에너지 저장용 탄탈 커패시터 1개가 있고, 아래의 구리 코일이 발전기, 왼쪽의 구불구불한 동박이 PCB 안테나다. 가장 쓸모 있었던 건 크리스털이었다.
Y1 = 26.2982 MHz
26.2982 × 16.5 = 433.9203 MHz주파수는 433.92. 하드웨어로 확인됐으니 433.46은 버려도 된다. 아쉽게도 옆의 메인 칩은 각인이 없었는데, 크리스털 값으로 미루어 보면 아마 HopeRF(華普微) CMT21xx 계열일 거다. 이런 칩의 는 모듈 업체가 EEPROM에 구워 넣는 값이라 0.5에서 40 kbps까지 뭐든 가능하고, datasheet는 이 칩이 얼마로 설정됐는지 알려 주지 않는다.
내 도구 자체도 고장 나 있었다#
새벽이 깊어질 무렵, 주파수, 펄스 폭, 패킷 타입, 극성까지 스캔할 수 있는 건 다 스캔했지만 전부 실패였다. 돌아보니 여러 라운드는 애초에 내 도구가 발목을 잡고 있었다.
pulses_to_data의 기본 시간 단위는 32.84µs인데, 공식 문서에는 2⁻¹⁵초, 즉 30.52µs라고 적혀 있다. 7.7% 차이다.- 변환할 때 버림을 써서 25µs가 0으로 잘렸고,
0x00은 Broadlink 포맷에서 이스케이프 문자라 데이터 전체가 거기서부터 어긋났다. - RF 패킷에는 사실 4바이트가 더 필요하다. python-broadlink의 #778에서 다른 사람이 올린 동작하는 패킷을 찾았는데, 앞부분에 RF 433MHz를 뜻하는
b1c0가 붙어 있고, 길이 뒤에00 9f 06 00이 더 있었다. 리틀 엔디언으로 환산하면 정확히 433920, 즉 433.92MHz다. 나는 밤새 이 부분을 빼먹고 보내고 있었다. - 마지막에 자체 테스트를 추가하다가 잡아냈는데, 다른 명령을 고쳐 쓰다가 정답에 가장 가까웠던 송신 명령을 통째로 지워 버렸었다. 그 명령은 한 번도 올바른 패킷으로 테스트된 적이 없었다.
전부 고치고 17개 테스트를 다 통과한 뒤 다시 해 봤지만, 조명은 여전히 꿈쩍도 안 했다.
왜 못 받는가#
결국 상황을 설명해 준 건 이슈 두 개였다. python-broadlink의 #739에서 누군가 아주 직설적으로 말했다. Broadlink가 이해하는 "RF"는 433과 315 MHz 위의 특정 프로토콜 일부뿐이고, 다른 프로토콜은 Flipper Zero 같은 걸 써야 할 수도 있다고. 즉 적외선 학습은 진짜로 펄스를 녹화하지만, RF 학습은 펌웨어 안의 프로토콜 복조기라서 모르는 포맷은 받지 못한다.
rtl_433의 #1790 "Help decoding Kinetic light switch"는 첫머리부터 내 증상 그대로다. 질문자는 Sonoff RF bridge와 Broadlink RM을 써 봤는데 둘 다 못 받았고, RTL-SDR로 바꾸고서야 신호가 보였다고 한다.
순정 앱도 써 봤다. 처음엔 가끔 한 번씩 녹화가 되긴 했는데, 그 코드를 눌러도 조명은 반응이 없었고, 나중엔 아무리 해도 아예 녹화가 안 됐다.
내 도구도, 순정 앱도, 다른 사람들의 경험도 전부 0이었다. 여기까지 와서야 RM4 Pro로는 안 된다는 걸 순순히 인정했다. 솔직히 가장 시간을 낭비한 건 기술 자체가 아니라, 숫자가 진짜인지 먼저 멈춰서 확인하지 않고 가짜 숫자를 들고 계속 밀고 나갔던 그 몇 라운드였다.
433MHz는 그냥 주파수 대역일 뿐#
처음에 가장 이해가 안 됐던 건 이거였다. 433MHz면 엄청 흔하지 않나? 셔터 문, 자동차 키, 무선 초인종이 다 그건데, 433을 지원한다는 기기가 왜 녹화를 못 하지?
나중에야 "433MHz"는 가장 바깥 층만 설명한다는 걸 이해했다.
| 이 스위치 | RM4 Pro | |
|---|---|---|
| 반송파 주파수 | 433.92 MHz | ✅ |
| 변조 | ✅ | |
| 인코딩 포맷과 타이밍 | 가장 짧은 펄스 약 37µs | ❌ |
WiFi와 블루투스가 "둘 다 2.4GHz"인 것과 같다. 주파수가 맞는다는 건 양쪽이 같은 채널에 있다는 뜻일 뿐이고, 상대가 어떤 포맷으로 얼마나 빠르게 말하는지는 별개의 문제다. RM4 Pro의 수신 쪽은 펌웨어에 내장된 프로토콜만 알아듣고, 이 스위치는 거기 없다. 송신 쪽은 이론상 간신히 닿을 수도 있지만, 포맷 검증까지 마친 패킷으로 끝까지 시도해도 성공하지 못했고, 실제로 송신이 되긴 했는지 확인할 장비도 없었다.
진실을 알려면 원시 파형을 직접 볼 수 있는 뭔가가 필요했다.
하드웨어 고르기#
고려했던 선택지(가격은 2026년 10월 기준):
| 방안 | 가격 | 안 고른 이유 |
|---|---|---|
| Shelly 1PM Mini를 수신기 앞단에 ×2 | 대만에선 드묾, 공식 사이트 개당 약 €15 | 천장에 올라가 상용 전원 배선을 두 번 해야 하는데, 나는 전기 배선을 해 본 적이 없다 |
| SwitchBot 스위치 봇 ×2 | 2개입 약 NT$2,370 | 벽에 붙여야 하고 배터리도 갈아야 한다 |
| LILYGO T-Embed CC1101 | NT$3,400에서 4,200 | 비싸고, 화면·NFC·적외선 다 나한텐 필요 없다 |
| Flipper Zero | 대만 정식 유통 없음, 공식 사이트 US$169 | 잡는 것도 쏘는 것도 되지만 HA에 상주시킬 수는 없다 |
| RTL-SDR Blog V4 | NT$3,000에서 3,700 | 수신만 되고 송신은 안 된다 |
| Shopee의 USB CC1101 동글 | NT$291 | 칩이 사실은 기능이 깎인 CC110L이다 |
TIP
RTL-SDR을 살 생각이라면, Shopee에서 "V4"라고 붙여 놓고 천 대만달러 남짓에 팔면서 R860이라고 적힌 건 짝퉁일 가능성이 높다. 정품 V4의 튜너는 R828D다.
결국 가장 싼 걸 골랐다. 케이스도 없는 맨몸의 모듈에 ESP32-C3 SuperMini를 연결하고, 펌웨어는 으로 짰다. 유일한 단점은 못생겼다는 것. 맨 기판 두 장에 점퍼선 여덟 가닥이다.
CC1101 433MHz 모듈(SMA 안테나 포함) $110
ESP32-C3 SuperMini $90
암-암 점퍼선 $20
──────────────────────────────────────────
$220, 무료배송C3를 고른 건 WiFi가 있기 때문이고, 이 용도엔 핀 6개면 충분해서 S3는 과하다. 못생긴 문제는 회사 동료가 해결해 줬다. 3D 프린터가 있는 동료인데, 부품이 도착하자 납땜도 해 주고 케이스도 뽑아 줬다.
케이스는 다섯 번째 버전까지 갔다. 인터넷에는 ESP32-S3 SuperMini와 CC1101 조합의 케이스도 있고 일반 ESP32 개발 보드용도 있는데, 하필 C3 SuperMini와 CC1101 조합만 없어서 직접 재고 직접 고쳐 가며 들어갈 때까지 한 버전씩 뽑았다.
배선과 설정#
ESPHome 공식 문서의 2핀 배선을 따랐다. SPI는 보드 실크스크린에 원래 표시된 핀을 쓰고, 데이터 핀 두 개는 부팅 역할이 없는 GPIO로 골랐다.
| CC1101 | ESP32-C3 SuperMini | 용도 |
|---|---|---|
| VCC | 3V3 | ⚠️ 5V에 연결 금지 |
| GND | GND | |
| SCK | GPIO4 | SPI |
| MISO | GPIO5 | SPI |
| MOSI | GPIO6 | SPI |
| CSN | GPIO7 | SPI |
| GDO0 | GPIO10 | 송신 |
| GDO2 | GPIO3 | 수신 |
C3 SuperMini는 GPIO2/8/9(부팅용 strapping 핀)와 GPIO20/21(UART)을 피해야 한다. 제대로 연결됐다면 부팅 로그에 CC1101 found! Chip ID: 0x0004가 찍힌다.
설정 파일의 핵심은 아래 몇 부분이다(간략화한 것이고, 전체 버전은 아래에 있다).
logger:
hardware_uart: USB_SERIAL_JTAG # C3는 기본이 UART0이라, 이게 없으면 USB로 로그를 못 읽는다
wifi:
power_save_mode: NONE # 절전을 끄지 않으면 LAN ping이 147ms까지 치솟는다
spi:
clk_pin: GPIO4
miso_pin: GPIO5
mosi_pin: GPIO6
cc1101:
id: transceiver
cs_pin: GPIO7
frequency: 433.92MHz
modulation_type: ASK/OOK
filter_bandwidth: 200kHz
symbol_rate: 40000 # 이 글 전체에서 가장 중요한 한 줄. 뒤에서 설명한다
remote_receiver:
pin: GPIO3
dump: raw # 녹화할 때 켜고, 다 녹화하면 꺼도 된다
filter: 20us
idle: 25ms
tolerance: 45%
remote_transmitter:
pin: GPIO10
carrier_duty_percent: 100%
on_transmit:
- cc1101.begin_tx: transceiver
on_complete:
- cc1101.begin_rx: transceiver
switch:
- platform: template
name: 房間燈
optimistic: true
restore_mode: DISABLED # 이것도 아주 중요하다. 뒤에서 설명한다
turn_on_action: &toggle
- remote_transmitter.transmit_raw:
code: [<캡처한 코드>]
repeat:
times: 8
turn_off_action: *toggle이런 스위치는 "토글" 동작 하나뿐이라서, 켤 때와 끌 때 같은 코드를 보낸다.
실제로 쓰는 버전에는 송신 대기열, 역방향 감지, 진단 센서 몇 개가 더 붙어 있다. 전체 설정 파일은 여기 있고, 두 코드는 자리표시자로 바꿔 뒀다. 자기 코드를 녹화한 뒤 transmit_raw와 on_raw의 두 비교값에 채워 넣으면 바로 쓸 수 있다.
전체 rf-bridge.yaml
# ─────────────────────────────────────────────────────────────────────────
# rf-bridge —— ESP32-C3 SuperMini + CC1101 433MHz 송수신 게이트웨이
#
# 목적: 거실/방의 "자가발전 무선 스위치"를 HA에 넣을 수 없는 문제를 해결한다.
# • RM4 Pro는 이런 스위치를 못 받는다(RF는 특정 프로토콜만 인식하고, 시간 해상도도 부족하다)
# • CC1101은 임의의 OOK 원시 파형을 엿들을 수 있다 → 스위치 코드를 녹화 → 같은 보드로 수신기에 송신
#
# 사용법은 두 단계:
# 1단계(녹화): dump: raw로 두고 벽 스위치를 눌러, 로그에 찍히는 원시 타이밍을 본다.
# 이때 이 장치를 스위치 옆으로 가져가서(보조배터리 전원) 확실히 수신되게 한다.
# 2단계(조명 제어): 녹화한 코드를 아래 script의 transmit_raw에 채우고, 두 방 모두 닿는 위치에 상주시킨다.
#
# 핀은 ESP32-C3 SuperMini의 실크스크린 기준:
# SPI는 보드에 원래 표시된 SCK(IO4) / MISO(IO5) / MOSI(IO6) / SS(IO7)
# GDO 두 개는 보드에서 "부팅이나 시스템 역할이 전혀 없는" 자유 핀 IO3과 IO10
# (나머지 자유 핀 IO0 / IO1은 예비로 남겨 둠)
# IO2 / IO8 / IO9 세 개의 strapping 핀과 IO20 / IO21 두 개의 UART 핀은 피한다.
# 참고: IO4~IO7은 데이터시트상 JTAG(MTMS/MTDI/MTCK/MTDO)로 표시돼 있지만, C3는 USB-JTAG가 내장돼 있어서
# 외부 JTAG를 쓰지 않는다면 SPI로 쓰는 게 정상적인 사용법이고, 보드 제조사가 표시한 용도도 그것이다.
# ─────────────────────────────────────────────────────────────────────────
substitutions:
name: rf-bridge
friendly_name: "RF Bridge (CC1101)"
esphome:
name: ${name}
friendly_name: ${friendly_name}
min_version: 2024.6.0
on_boot:
# 안전장치: CC1101이 부팅하자마자 수신 모드로 들어가도록 명시한다.
# 공식 예제는 "송신 완료 후"에만 begin_rx를 하고 부팅 시 초기 상태는 언급하지 않아서,
# 녹화 단계에서 아무것도 안 잡히면 신호가 없는 건지 수신을 안 하는 건지 구분하기 어렵다.
priority: -100
then:
- cc1101.begin_rx: transceiver
# 기억해 둔 조명 상태를 HA에 "표시"만 한다. publish만 하고 동작은 실행하지 않으므로 RF 신호는 전혀 보내지 않는다
- lambda: |-
id(room_light).publish_state(id(room_light_saved));
id(mom_light).publish_state(id(mom_light_saved));
esp32:
board: esp32-c3-devkitm-1 # C3 SuperMini는 이 board 설정이면 된다
framework:
type: esp-idf
logger:
# 평소엔 INFO. 새 리모컨을 다시 녹화할 때는 DEBUG로 되돌리고, 아래 remote_receiver의
# dump: raw를 켜야 원시 µs 타이밍이 보인다.
level: INFO
# ⚠️ ESP32-C3의 logger는 기본적으로 UART0(물리 핀 GPIO20/21)으로 출력하는데,
# 그 두 핀엔 아무것도 연결돼 있지 않다 → USB로 로그를 읽으면 텅 비어 있다(처음 구웠을 때 밟았다).
# 칩 내장 USB Serial/JTAG로 명시해야 부팅 메시지가 보인다.
hardware_uart: USB_SERIAL_JTAG
api:
# HA용 "표시만 바꾸고 송신은 안 하는" 명령: HA가 조도 센서로 상태 불일치를 발견했을 때 호출한다.
# publish_state는 on_state를 트리거하므로 플래시에 저장된 상태도 함께 갱신된다.
actions:
- action: set_room_light_state
variables:
state: bool
then:
- lambda: id(room_light).publish_state(state);
- action: set_mom_light_state
variables:
state: bool
then:
- lambda: id(mom_light).publish_state(state);
encryption:
key: !secret rf_bridge_api_key
ota:
- platform: esphome
password: !secret rf_bridge_ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# ⚠️ 절전은 반드시 꺼야 한다. ESP32-C3의 WiFi light sleep은 두 가지 문제를 일으킨다(둘 다 실제로 겪음):
# 1. LAN ping 지연이 67~147ms로 치솟고 심하게 흔들린다 —— 즉각 반응해야 하는 RF 브리지로선 용납 불가
# 2. USB Serial/JTAG도 같이 죽어서 시리얼 포트가 텅 비고, 한때 칩이 멈춘 줄 알았다
power_save_mode: NONE
# ⚠️ 서버 / PC의 USB 3.0 포트에 오래 꽂아 두지 말 것: 거기 2.4GHz 노이즈 때문에 하루 17번 끊겼고,
# 휴대폰 충전기로 옮긴 뒤 2일간 0번.
# output_power: 8.5dB도 시도해 봤지만 → 전원을 껐다 켜도 아예 연결이 안 돼서 기본값보다 나빴다. 삭제함.
# 끊길 때마다 한 번씩 기록해 두면, 다시 연결된 뒤 HA에서 이번 부팅 동안 몇 번 끊겼는지 볼 수 있다
on_disconnect:
- lambda: id(wifi_drops) += 1;
# 수신이 불안정할 때 쓰는 예비 핫스팟(최초 설정용)
ap:
ssid: "rf-bridge-setup"
password: !secret rf_bridge_ap_password
globals:
- id: wifi_drops
type: int
restore_value: no
initial_value: '0'
# 두 조명의 마지막 상태. 플래시에 저장해 두고 재부팅 후 HA 표시를 복원하는 데 쓴다
- id: room_light_saved
type: bool
restore_value: yes
initial_value: 'false'
- id: mom_light_saved
type: bool
restore_value: yes
initial_value: 'false'
# 마지막 송신 시각 기록. on_raw의 쿨다운 판단용
- id: last_tx_ms
type: uint32_t
initial_value: '0'
captive_portal:
# 내장 웹 UI —— HA에 연결하기 전에 버튼을 바로 테스트하는 용도.
# 나중에 빼려면 이 부분을 지우면 된다(flash가 조금 절약된다).
web_server:
port: 80
version: 3
# 처음 USB 시리얼로 플래싱한 뒤에는 WiFi OTA로 올릴 수 있다
improv_serial:
# ─── SPI 버스: 소프트웨어 SPI, 지뢰 핀을 피한 일반 GPIO 사용 ───
spi:
clk_pin: GPIO4
miso_pin: GPIO5
mosi_pin: GPIO6
# ─── CC1101 본체 ───
# 주파수는 433.92로 고정(내 스위치 크리스털 26.2982×16.5로 환산한 값).
cc1101:
id: transceiver
cs_pin: GPIO7
frequency: 433.92MHz
modulation_type: ASK/OOK
# 350kHz는 너무 넓어서 AGC가 노이즈 플로어까지 증폭한다. 200kHz로 좁혀 노이즈를 억제한다.
# (실측: 325kHz일 때 스위치를 한 번 누를 때마다 길이가 제각각인 조각이 8개씩 들어왔다)
filter_bandwidth: 200kHz
# ⚠️ symbol_rate는 CC1101의 데이터 필터 대역폭에 영향을 준다. "송신할 때만 의미 있는" 값이 아니다.
# 원래는 5000(5 kbaud = 200µs/심볼)이었는데, 실측한 신호의 기본 시간 단위는 25µs였다
# (모든 펄스 폭이 25의 정수배: 25/50/75/100/.../475/875).
# 8배나 빠른 셈이라 필터가 빠른 변화를 뭉개 버린다 → 복조할 때마다 결과가 달라진다.
# 40000 baud = 25µs/심볼, 신호의 최소 특징과 정확히 맞는다.
symbol_rate: 40000
output_power: 10 # dBm, 최대 약 +11
# 참고: cc1101에는 rssi / lqi라는 설정 항목이 없다(처음에 잘못 썼다가 compile에서 바로 막혔다).
# RSSI와 LQI는 packet_mode의 on_packet 트리거 안에서 변수로만 쓸 수 있고,
# async 모드(지금 쓰는 것)에서는 얻을 수 없으며 센서 엔티티로도 못 뽑는다.
# ─── 수신(2핀 배선, GDO2) ───
remote_receiver:
id: rx
pin:
number: GPIO3 # CC1101 GDO2에 연결
# 녹화할 때는 `dump: raw`를 켜야 원시 µs 타이밍이 보인다.
# ⚠️ dump: all은 쓰지 말 것 —— 그건 "프로토콜 디코더"만 켜고 원시 파형은 포함하지 않으며,
# 노이즈를 Pronto / Beo4 같은 적외선 프로토콜에 억지로 끼워 맞춰서 로그만 도배하고 쓸모가 없다.
# 평소엔 꺼 둔다: 역방향 감지는 아래 on_raw에 의존하므로 dump 없이도 동작하고,
# 켜 두면 이웃집 리모컨과 노이즈까지 전부 로그에 찍힌다.
# dump: raw
# ⚠️ filter는 단순히 "짧은 펄스를 버리는" 게 아니라, 짧은 펄스 양쪽의 신호를 "합쳐" 버린다:
# 실제 185하이 / 60로우 / 185하이 → filter 90µs 이후 단일 430µs 하이
# 그래서 90µs일 때 녹화된 477 / 327은 이렇게 만들어진 가짜 값일 가능성이 높다.
# 가까이서 녹화하면 신호가 노이즈보다 훨씬 강하므로 filter를 낮춰야 실제 구조가 잡힌다.
filter: 20us
# idle을 늘려서 한 번 누를 때의 반복 프레임이 전부 한 건 안에 담기고 잘리지 않게 한다
idle: 25ms
tolerance: 45%
# 참고: 수신 측에는 on_transmit / on_complete가 필요 없다(받아 주지도 않는다).
# 모드 전환은 전부 remote_transmitter 쪽이 담당하고, 송신이 끝나면 알아서 rx로 돌아간다.
#
# ── 역방향 감지: 누군가 실제 스위치를 누르면 HA의 상태를 뒤집는다 ──
# ESPHome 내장 raw 비교는 쓰지 않는다. 그건 프레임 시작부터 정렬돼야 하는데,
# 우리 캡처는 시작 위상이 일정하지 않다(한 번 누르면 반복이 2~4번 들어오고 시작점도 제각각).
# 대신 직접 26 bit로 디코딩해서 값을 비교하므로, 어디서 시작하든 알아본다.
on_raw:
then:
- lambda: |-
// 송신 후 1초 동안은 무시한다. 내가 보낸 신호 때문에 상태가 두 번 뒤집히는 걸 막기 위해
static uint32_t last_seen = 0;
if (millis() - id(last_tx_ms) < 1000) return;
uint32_t code = 0; int nbits = 0;
for (size_t i = 0; i + 1 < x.size(); i += 2) {
int32_t m = x[i], sp = -x[i+1];
if (m < 20 || sp < 20) { code = 0; nbits = 0; continue; }
if (sp > 400) { // 동기 간격 → 한 프레임 끝
code = 0; nbits = 0; continue;
}
code = (code << 1) | (m > sp ? 1 : 0);
if (++nbits > 26) { code &= 0x3FFFFFF; nbits = 26; }
if (nbits == 26) {
if (millis() - last_seen < 800) break; // 같은 누름의 반복 프레임은 한 번만 센다
if (code == 0x0000000UL) { // 방 조명의 26 bit 값으로 바꿀 것
last_seen = millis();
ESP_LOGI("rf", "偵測到實體開關:房間燈");
id(room_light).publish_state(!id(room_light).state);
break;
} else if (code == 0x0000001UL) { // 엄마 방 조명의 26 bit 값으로 바꿀 것
last_seen = millis();
ESP_LOGI("rf", "偵測到實體開關:媽媽房間燈");
id(mom_light).publish_state(!id(mom_light).state);
break;
}
}
}
# ─── 송신(2핀 배선, GDO0) ───
remote_transmitter:
id: tx
pin:
number: GPIO10 # CC1101 GDO0에 연결
carrier_duty_percent: 100%
on_transmit:
then:
- lambda: 'id(last_tx_ms) = millis();'
- cc1101.begin_tx: transceiver
on_complete:
then:
- cc1101.begin_rx: transceiver
# ─── 조명 스위치 엔티티 ───
# button 대신 switch를 쓴다. 그래야 HA에 "켜짐/꺼짐" 상태가 생겨서 표시하고 자동화에 쓸 수 있다.
# 이런 RF 스위치는 "토글" 동작 하나뿐이라(켜기/끄기 명령이 따로 없음),
# turn_on과 turn_off가 같은 코드를 보내고, optimistic으로 상태를 유지한다.
# 누군가 실제 스위치를 누르면 위 remote_receiver의 on_raw가 상태를 뒤집는다.
#
# 두 스위치의 코드(2026-09-09 녹화, 각각 모듈에 붙이고 열 번씩 눌러 합의값을 취함):
# 방 조명 26 bit = <직접 녹화한 코드>
# 엄마 방 조명 26 bit = <직접 녹화한 코드>
# 앞 10 bit는 같고(같은 모델의 제조사 코드), 그 뒤로 갈라진다 → 서로 간섭하지 않는다
switch:
- platform: template
name: "房間燈"
id: room_light
icon: "mdi:ceiling-light"
optimistic: true
# ⚠️ 기본 restore_mode는 ALWAYS_OFF라서, 부팅 시 "실제로 turn_off를 한 번 실행"한다.
# 그런데 turn_off가 보내는 건 토글 코드다 → 재부팅할 때마다 조명이 한 번씩 토글된다. 그래서 반드시 DISABLED로 하고,
# 상태는 위 globals에 기억해 두고, on_boot에서는 표시만 하고 송신은 하지 않는다.
restore_mode: DISABLED
on_state:
- lambda: id(room_light_saved) = x;
turn_on_action: &toggle_room
- script.execute: tx_room_light
turn_off_action: *toggle_room
- platform: template
name: "媽媽房間燈"
id: mom_light
icon: "mdi:ceiling-light"
optimistic: true
# ⚠️ 기본 restore_mode는 ALWAYS_OFF라서, 부팅 시 "실제로 turn_off를 한 번 실행"한다.
# 그런데 turn_off가 보내는 건 토글 코드다 → 재부팅할 때마다 조명이 한 번씩 토글된다. 그래서 반드시 DISABLED로 하고,
# 상태는 위 globals에 기억해 두고, on_boot에서는 표시만 하고 송신은 하지 않는다.
restore_mode: DISABLED
on_state:
- lambda: id(mom_light_saved) = x;
turn_on_action: &toggle_mom
- script.execute: tx_mom_light
turn_off_action: *toggle_mom
# ─── 송신 대기열 ───
# HA에서 연타할 때 두 신호열이 붙어 버리면 조명은 한 번 누른 걸로 받아들이는데, HA는 이미 두 번 뒤집었다 → 불일치.
# 그래서 조명마다 queued script를 하나씩 둔다: 한 번에 한 신호열만 보내고, 다 보낸 뒤 최소 700ms를 두고 다음을 보낸다.
script:
- id: tx_room_light
mode: queued
max_runs: 6
then:
- remote_transmitter.transmit_raw:
code: [<캡처한 코드로 교체>]
repeat:
times: 8
wait_time: 0s
- delay: 700ms
- id: tx_mom_light
mode: queued
max_runs: 6
then:
- remote_transmitter.transmit_raw:
code: [<캡처한 코드로 교체>]
repeat:
times: 8
wait_time: 0s
- delay: 700ms
# ─── 진단 ───
sensor:
- platform: wifi_signal
name: "${friendly_name} WiFi"
update_interval: 60s
entity_category: diagnostic
- platform: uptime
name: "${friendly_name} 開機時間"
update_interval: 60s
entity_category: diagnostic
- platform: template
name: "${friendly_name} WiFi 斷線次數"
lambda: return id(wifi_drops);
accuracy_decimals: 0
state_class: measurement
update_interval: 60s
entity_category: diagnostic
text_sensor:
- platform: wifi_info
ip_address:
name: "${friendly_name} IP"
bssid:
name: "${friendly_name} BSSID"
- platform: debug
reset_reason:
name: "${friendly_name} 重開原因"
debug:
update_interval: 5min비밀번호와 키는 ESPHome의 secrets.yaml로 관리하고, 필요한 항목은 wifi_ssid, wifi_password, rf_bridge_api_key, rf_bridge_ota_password, rf_bridge_ap_password다.
그리고 C3 보드는 첫 플래싱 때 작은 함정이 하나 있다. esptool 마지막에 나오는 "Hard resetting via RTS"가 이 보드엔 안 먹혀서, 다 구운 뒤 직접 RESET을 한 번 눌러 줘야 한다. 안 그러면 계속 다운로드 모드에 머물러서 멈춘 것처럼 보인다.
신호 녹화: 스스로를 속이기 가장 쉬운 지점#
하드웨어를 연결하고 나서 진짜 시간이 걸린 건 녹화였다. 세 라운드를 했는데, 두 번째 라운드에서 다 끝난 줄 알 뻔했다.
1라운드: 눈으로 보고 "똑같아 보이네"#
처음엔 dump: all을 썼는데, 로그엔 온통 Received Pronto가 1초에 열 건씩 찍혔다. ESPHome이 노이즈를 적외선 프로토콜에 억지로 끼워 맞춰 디코딩하고 있던 거다. dump: all은 프로토콜 디코더만 켜고 원시 파형은 포함하지 않아서, dump: raw로 바꿔야 진짜 타이밍이 보인다.
누르면 1초에 8건이 들어오고, 안 누르면 30초에 1건 정도였다. 적어도 스위치가 송신하고 있고 CC1101이 받고 있다는 건 확인됐다. 두 건을 골라 나란히 놓고 보니 같은 위치의 값이 거의 똑같아서, 이거다 싶었다.
그리고 통계를 돌렸다.
길이 41 그룹(25건) 위치별 변동계수: 중앙값 45% 최대 374%
캡처 길이: 15-67, 표준편차 9.4같은 리모컨 코드라면 매번 캡처한 같은 위치의 값은 거의 같아야 하고, 가 10% 이하여야 의미가 있다. 45%라는 건 내가 고른 두 건이 우연히 비슷했을 뿐이라는 뜻이다.
2라운드: 예쁜 가짜 코드#
25, 50, 75µs 같은 아주 짧은 펄스를 노이즈로 보고, filter를 10µs에서 90µs로 올려 걸러 냈다. 이번엔 숫자가 훨씬 예뻤다.
완전한 프레임 160개 중 134개가 같은 길이
위치별 변동: 중앙값 7.3% 최대 12.2%
프레임: 186, -110, 477, -105, 184, -847, 182, -111, 327, -552, 186, -13057.3%, 기준선 아래다. 신나서 HA 버튼으로 만들고 여섯 번 연달아 눌렀는데, 조명은 한 번도 반응하지 않았다.
처음엔 거리를 의심했는데, 말이 안 됐다. 그 스위치는 떼어서 멀리 가져가 눌러도 잘 작동했다. 그 쥐꼬리만 한 출력으로도 충분하다면 CC1101이 부족할 리가 없다. 그러니 문제는 코드 자체에 있을 수밖에 없었다.
돌아보니 틀린 건 파라미터 두 개였다. 첫째는 filter. 너무 짧은 펄스를 버리기만 하는 게 아니라, 짧은 펄스 양쪽의 신호를 이어 붙여 버린다.
실제: 185µs 하이 → 60µs 로우 → 185µs 하이
filter 90µs 이후: 430µs 하이프레임 안의 477과 327은 아마 이렇게 붙어서 생긴 값일 거다.
둘째는 더 근본적이었다. 스위치를 모듈에 바짝 붙이고 다시 녹화하다가, 모든 펄스 폭이 약 25µs의 정수배라는 걸 발견했다. 그런데 설정 파일에는 symbol_rate: 5000, 즉 심볼당 200µs라고 적혀 있었다. CC1101의 데이터 필터 대역폭은 심볼 레이트를 따라가기 때문에, 너무 느리게 설정하면 빠른 변화가 그냥 걸러져 버린다. 나는 처음에 설정 파일에 "녹화 단계에서는 raw 캡처에 영향 없음"이라는 주석까지 달아 놨는데, 완전히 거꾸로였다.
cc1101:
frequency: 433.92MHz
symbol_rate: 5000
symbol_rate: 40000
remote_receiver:
filter: 90us
filter: 20us
idle: 25msfilter를 20µs까지 과감하게 낮출 수 있었던 건, 스위치를 모듈에 바짝 붙여서 누르는 방식으로 바꿔서 신호가 노이즈보다 훨씬 강해졌기 때문이다. 그때 나는 한 손으로 안테나를 붙잡고 다른 손으로 스위치를 누르면서 녹화했다.
3라운드: 54개의 값#
수정한 뒤엔 매번 캡처되는 길이가 19에서 63이던 게 단번에 93에서 169로 바뀌었고, 두 무리로 나뉘었는데 이는 같은 프레임이 두 번, 세 번 반복된 것과 정확히 대응했다. 계산해 보니 주기는 54개 값, 위치별 변동 중앙값은 9.4%였다.
구조도 드디어 말이 됐다. 동기 헤더 하나, 이어서 26개의 bit, 각 bit는 짧은 펄스와 긴 펄스 한 쌍으로 짧은 쪽이 약 37µs, 긴 쪽이 그 세 배. 전형적인 계열이다. 구워 넣고 HA 버튼을 눌렀더니 조명이 꺼졌다. 그날 저녁 7시 40분쯤이었다.
WARNING
2라운드와 3라운드를 잘 봐라. 가짜 코드의 변동계수가 진짜 코드보다 더 낮다.
필터가 잘못 설정돼 있으면 매번 같은 방식으로 신호를 망가뜨리기 때문에 아주 일관되게 틀리고, 통계로는 전혀 드러나지 않는다. 변동계수 10% 미만은 필요조건일 뿐이다. 가짜 코드의 정체를 드러낸 건 다른 두 가지였다. 구조가 너무 짧았다는 것(하이/로우 펄스 쌍이 6개뿐인데, 일반적인 리모컨 코드는 최소 스무 몇 bit는 된다), 그리고 "스위치를 멀리 가져가도 작동한다"는 생활 상식.
엄마 방, 그리고 거꾸로 듣기#
성공한 김에 엄마 방 조명도 했다. 같은 방법으로 프레임 9개를 녹화했고, 변동 중앙값은 6.7%. 두 코드는 앞 10 bit가 완전히 같고(아마 같은 제조사일 거다), 뒤쪽 11 bit가 달라서 서로 간섭하지 않는다. 중간에 한 번 변동 0.0%가 나와서 1초도 안 되게 기뻐했는데, 샘플이 하나뿐이었다. 비교할 게 없으니 당연히 0이다.
CC1101이 스위치 신호를 받을 수 있으니 반대로도 할 수 있다. 누군가 실제 스위치를 누르면 HA의 상태도 따라 뒤집히게 하는 거다. ESPHome 내장 비교는 프레임 시작부터 정렬돼야 하는데 매번 캡처 시작점이 일정하지 않아서, on_raw 안에서 직접 파형을 26개 bit로 디코딩해 값을 비교하게 바꿨다. 송신 후 1초 쿨다운(안 그러면 내가 보낸 신호를 듣는다)과, 800ms 안의 같은 누름은 한 번만 세는 처리도 넣었다. 첫날 테스트에선 두 방을 깔끔하게 구분했다.
3주 쓰고 나서야 발견한 문제들#
재부팅할 때마다 조명이 저절로 토글된다#
9월 26일, HA의 조명 상태가 실제와 안 맞기 시작했다. 파 보니 ESPHome template switch의 restore_mode 기본값이 ALWAYS_OFF인데, 이건 부팅 시 turn_off를 실제로 한 번 실행한다는 뜻이다. 하필 이 조명엔 토글 코드밖에 없어서 turn_off를 보내면 그게 곧 토글이다. 그래서 보드가 재부팅될 때마다(뽑았다 꽂거나, 펌웨어를 업데이트하거나, WiFi가 너무 오래 끊겨서 스스로 재시작하거나) 조명이 한 번씩 토글됐고, HA는 꺼짐으로 표시했다.
restore_mode: DISABLED로 바꾸고, 상태는 플래시 메모리에 저장해서 부팅 시엔 HA에 상태만 보고하고 송신은 하지 않게 했다. 하는 김에 송신도 대기열 방식으로 바꿔 매번 700ms 간격을 두게 했다. 두 번 연타한 게 붙어서 수신기가 한 번으로 받아들이는 걸 막기 위해서다. 위의 설정 파일은 이미 수정된 버전이다.
실제 스위치 소리가 거의 안 들린다#
역방향 감지는 첫날엔 잘 됐는데, 9월 9일부터 26일까지 실제 스위치를 감지한 건 단 한 번이었고, 천천히 10번 눌러 봐도 0번이었다. 그때 보드는 거실 서버에 꽂혀 있었고, 두 방의 스위치와는 4에서 5미터 떨어진 데다 벽까지 하나 끼어 있었다. 코드를 녹화할 땐 모듈에 바짝 붙여서 했으니, 자가발전 스위치의 그 작은 출력으론 벽을 넘기에 부족했던 거다.
이건 억지로 풀지 않고 SwitchBot Hub 3의 조도 센서로 보완했다. 밤에만 동작하고, 방 밝기가 4단계 이상인데 HA가 꺼짐으로 표시하면 켜짐으로, 2단계 이하인데 켜짐으로 표시하면 꺼짐으로 바꾼다. HA의 표시만 갱신하고 송신은 하지 않는다. 센서가 약 7.5분마다 보고하니까 상태는 늦어도 8분 안에 따라잡는다.
하루 17번 끊기는 WiFi#
같은 시기에 연결이 끊기기 시작했다. 9월 25일부터 하루에 십여 번씩 끊겼고, 26일 저녁엔 아예 IoT용 SSID가 스캔되지 않아서 USB를 뽑았다 꽂아야만 돌아왔다. 그 뒤 사흘 동안 생각할 수 있는 건 다 해 봤다.
| 가설 | 결과 |
|---|---|
| 공유기의 IoT 네트워크 설정이 바뀌었다 | ✗ 휴대폰에선 보이고, 2.4GHz, 채널 6 모두 정상 |
| 공유기가 MAC을 차단했다 | ✗ 필터, 차단 목록, AiProtection 모두 깨끗 |
| 공유기의 밴드 스티어링이 2.4GHz 기기를 다시 받아 주지 않는다 | ✗ IoT 네트워크를 2.4GHz 전용으로 바꾸자 다른 8대(똑같이 2.4GHz만 지원하는 C210 카메라 포함)는 다 다시 붙었는데, 이것만 안 됐다 |
송신 출력 낮추기 output_power: 8.5dB | ✗ 더 나빠짐. 전원을 껐다 켜도 연결 안 됨 |
| C3 SuperMini의 설계 결함(크리스털이 안테나에 너무 가까움) | △ 보드가 약한 건 맞지만 주원인은 아님 |
C3 SuperMini 일부 로트의 설계 결함은 꽤 유명하다. done.land에 정리돼 있다. 2024년에 나온 한 개정판이 크리스털을 세라믹 안테나에서 고작 0.3mm 떨어진 곳으로 옮겼고(정상이라면 최소 1mm는 돼야 한다), 증상은 WiFi 스캔은 되는데 연결이 안 되는 것이다. 심지어 손으로 안테나를 만지고 있어야 연결된다는 사람도 있다. 나는 한때 이게 원인이라고 확신해서, 외장 안테나 보드를 살 뻔했다. 웃긴 건 그 글이 권하는 해결책이 바로 송신 출력을 8.5dB로 낮추는 거였는데, 나는 낮췄더니 오히려 더 연결이 안 됐다는 거다. 돌이켜 보면 그게 이미 문제가 보드만이 아니라는 신호였다.
진짜로 방향을 틀게 만든 건 또 다른 디테일이었다. 한 번은 뽑았다 꽂으면서 옆 USB 포트에 꽂게 됐는데, 무슨 짓을 해도 연결이 안 되다가 원래 포트로 다시 꽂자마자 바로 붙었다.
몇 센티미터 차이로 이렇게 달라진다면 환경 문제에 가깝다. USB 3.0 포트와 케이블은 그 자체로 2.4GHz 간섭을 내고, 서버 뒤쪽은 금속 케이스에 메인보드 노이즈까지 있다. 원래부터 WiFi가 약한 보드한테는 아마 집에서 최악의 자리였을 거다. 웃기게도 계획할 때 서버 옆에 둘 거면 케이스에서 좀 떨어뜨려야 한다는 걸 알고 있었는데, 결국 거기서 3주를 살았다.
원래는 방의 PC에 꽂으려고 했는데, PC에도 USB 3.0이 있고 꺼지거나 절전 상태일 땐 USB 전원이 끊길 수도 있다. 한밤중에 조명을 끄고 싶을 때가 오히려 전원이 없기 가장 쉬운 때라는 거다. 결국 평범한 휴대폰 충전기에 꽂았다.
보드는 바꿀 필요가 없었다.
지금 모습#
이게 동료가 뽑아 준 다섯 번째 버전 케이스로, 내 방 벽의 충전기에 꽂고 안테나를 세워 뒀다. 이 위치에서 내 방과 엄마 방 조명 둘 다 제어된다. RM4 Pro는 다시 에어컨만 맡게 됐다. 적외선에서는 처음부터 끝까지 제 몫을 톡톡히 해 왔다.
아직 검증 못 한 건 상태 동기화다. 지금 이 위치에서 두 실제 스위치 신호가 들리는지. 들린다면 HA의 조명 상태가 실제 스위치를 실시간으로 따라가고, 안 들린다면 계속 조도 센서로 보정한다. 내 짐작으론 아마 안 들릴 거다. 자가발전 스위치는 출력이 정말 작고, 거리도 아직 좀 멀다.
그래도 상태가 동기화되든 말든, 침대에 누워서 불을 끄는 건 진짜로 해냈다.
- python-broadlink: RF 학습은 특정 프로토콜만 인식한다는 논의issue #739
- python-broadlink: b1c0 접두사로 RF를 송신하는 동작 패킷issue #778
- rtl_433: Help decoding Kinetic light switchissue #1790
- ESP-Home CC1101 Kinetic Switch Transceiverbillmaterial
- ESP32-C3 SuperMini: 크리스털이 안테나에 너무 가까운 설계 결함done.land
- ESPHome: CC1101 컴포넌트esphome.io
아직 댓글이 없어요
✨ 첫 댓글을 남겨보세요