CONFIG_LORAWAN_REGION_* 심벌은 이 글의 3.7.0 설정으로 옮기지 않는다. 아래 그림은 API 관계를 다시 그린 개념도이며 RF 성능 측정값이 아니다.1. 같은 라디오를 써도 계약이 다르다
LoRa는 CSS 변조를 쓰는 무선 PHY다. <zephyr/drivers/lora.h>의 lora_config()·lora_send()·lora_recv()는 라디오 장치의 직접 패킷 송수신 계약이다. 반면 <zephyr/lorawan/lorawan.h>의 lorawan_start()·lorawan_join()·lorawan_send()는 LoRaWAN 네트워크에 참여한 종단 장치의 계약이다. gateway가 raw LoRa 패킷을 받았다고 해서 LoRaWAN 가입이나 애플리케이션 전달이 성립한 것은 아니다.
↔ 좁은 화면에서는 애플리케이션에서 라디오까지 오른쪽으로 이동하세요.
lora_send / recv: 원시 패킷lorawan_join / send: 네트워크 프레임두 API의 성공 의미는 같지 않다lora0 alias와 드라이버 준비 확인LoRaWAN: gateway → network servergateway가 종단 장치의 가입·키 정책을 처리해 주지는 않는다
| 질문 | 직접 LoRa | LoRaWAN |
|---|---|---|
| 호출 계층 | 라디오 드라이버 lora_* | 네트워크 스택 lorawan_* |
| 사전 조건 | 양쪽 라디오의 주파수·BW·SF·coding rate 등 일치 | 지역 채널 계획, 장치/서버 등록, 가입 정보와 gateway 경로 |
| 보내는 단위 | 원시 무선 payload | 포트·confirmed 여부가 정해진 LoRaWAN uplink |
| 성공의 한계 | 로컬 무선 전송 완료; 상대 수신을 증명하지 않음 | API/스택 결과; 서버의 애플리케이션 처리까지 증명하지 않음 |
2. 직접 LoRa는 먼저 모뎀 설정을 맞춘다
struct lora_modem_config는 Hz 단위 frequency, bandwidth, datarate(SF), coding_rate, preamble, 출력 전력, tx 방향, IQ 반전과 public/private sync 설정을 담는다. 다른 보드의 raw LoRa 패킷을 받으려면 양쪽 설정과 하드웨어 대역이 호환돼야 한다. 3.7.0의 수신 예시는 DT_ALIAS(lora0)로 장치를 가져오므로, 모든 보드에 그 alias가 있다는 뜻은 아니다.
| 호출 | 실행 계약 | 검증할 것 |
|---|---|---|
lora_config(dev, &cfg) | 라디오의 송수신 설정을 적용 | 주파수·BW·SF·coding rate·sync 선택과 보드 지원 |
lora_send(dev, data, len) | 로컬 전송 완료까지 블로킹 | 0을 상대 수신 확인으로 해석하지 않기 |
lora_send_async(..., signal) | 전송 시작 뒤 바로 반환하고 완료되면 signal로 통지 | 완료 전에 같은 라디오를 다시 쓰지 않기 |
lora_recv(..., timeout, &rssi, &snr) | 블로킹 수신, 성공하면 받은 길이 반환 | 최대 255바이트 버퍼와 timeout·RSSI·SNR 처리 |
lora_recv_async(dev, cb) | 앞서 적용한 설정으로 연속 수신, cb=NULL로 취소 | 3.7.0은 최신 API의 별도 user_data 인자를 쓰지 않음 |
직접 링크의 sync 선택. 3.7.0 문서는 peer-to-peer 사용에서 private sync를 보통 권하고, public sync는 고급 용도로 설명한다. public_network=true만 설정해서 raw 전송을 LoRaWAN 프레임으로 바꿀 수는 없다.
근거: 3.7.0 LoRa PHY API와 modem config, 3.7.0 수신 예시의 보드·alias 조건.
3. LoRaWAN은 지역과 장치 등록을 먼저 맞춘다
LoRaWAN 경로에는 라디오뿐 아니라 network server에 등록한 장치 식별자와 키가 필요하다. 3.7.0 Kconfig의 지역 심벌은 CONFIG_LORAMAC_REGION_KR920, CONFIG_LORAMAC_REGION_EU868처럼 LORAMAC_REGION 접두사를 쓴다. enum LORAWAN_REGION_KR920 같은 런타임 지역 선택자는 별개다. 한 지역만 빌드에 넣었다면 lorawan_set_region() 호출은 필수가 아니며, 여러 지역을 넣었다면 lorawan_start() 전에 원하는 지역을 선택한다. 실제 지역·채널 계획은 사용 국가, 모듈과 네트워크 서버 설정에 맞춰 확인해야 한다.
| 준비 항목 | 장치 측 | 맞춰볼 상대 | 빠졌을 때 현상 |
|---|---|---|---|
| 라디오·안테나 | 지원 보드, lora0 alias, device ready | 모듈 주파수 대역 | 초기화 실패 또는 RF 경로 부재 |
| 지역 | CONFIG_LORAMAC_REGION_*, 필요하면 lorawan_set_region() | gateway·server 채널 계획 | 가입 요청이 네트워크에 닿지 않음 |
| OTAA 식별·키 | DevEUI, JoinEUI, AppKey, NwkKey를 활성 LoRaWAN 버전·서버 정책에 맞춤 | network server의 장치 등록 | join reject/timeout 또는 키 불일치 |
| DevNonce | 같은 EUI로 재가입할 때 비휘발성 증가 값 관리 | 서버의 재사용 방지 상태 | 재부팅 뒤 join이 거절될 수 있음 |
예시와 제품의 차이. 3.7.0 Class A 샘플은 이해를 돕기 위해 EUI·키 배열과 dev_nonce=0을 코드에 넣는다. 공식 3.7.0 API 문서는 LoRaWAN 1.0.4부터 같은 EUI의 OTAA DevNonce가 단조 증가해야 하며 애플리케이션이 비휘발성 저장소에 보관해야 한다고 명시한다. 샘플 값을 제품 등록 정보로 복사하지 않는다.
근거: 3.7.0 Kconfig 지역 목록, 3.7.0 OTAA 구조체와 지역 API, v3.7.0 Class A 샘플 소스.
4. OTAA는 region → start → join 순서로 읽는다
Class A 샘플은 라디오 device_is_ready()를 먼저 검사하고, 빌드한 지역에 따라 필요하면 lorawan_set_region()을 호출한다. 그다음 lorawan_start()로 스택을 시작하고 lorawan_register_downlink_callback()과 별도의 lorawan_register_dr_changed_callback()을 등록한다. 이어 struct lorawan_join_config의 mode=LORAWAN_ACT_OTAA와 EUI·키·nonce를 채워 lorawan_join()한다. join 성공 전에 주기 uplink를 보내는 순서로 바꾸지 않는다. ABP 구조체도 API에 있지만 이 도식은 OTAA 경로만 그린다.
↔ 좁은 화면에서는 준비 단계부터 가입 결과까지 이동하세요.
DT_ALIAS(lora0)alias 존재와 device_is_ready 확인lorawan_set_region() 여러 지역을 빌드했을 때 start 이전에 선택lorawan_start()지역 채널 계획의 실행 상태를 초기화lorawan_join(&cfg) 0 성공 후에 애플리케이션 uplink| API | 언제 | 반환·해석 |
|---|---|---|
lorawan_set_region(region) | 여러 빌드 지역 중 선택할 경우 start 전 | 지원·설정된 지역인지 결과 확인 |
lorawan_start() | join 전 | 0이면 스택 시작; 가입 성공을 뜻하지 않음 |
lorawan_join(&cfg) | OTAA 정보와 서버 등록을 맞춘 뒤 | 0이면 가입 성공; 음수 오류면 지역·키·RF를 분리 진단 |
lorawan_register_downlink_callback(&cb) | downlink를 받기 전 | port·수신 payload를 받는 구조체의 수명을 유지 |
lorawan_register_dr_changed_callback(cb) | data rate 변화를 관찰하려면 join 전 별도 등록 | join 성공 직후 1회, 이후 ADR에 따라 바뀔 때마다 통지 |
근거: 3.7.0 Class A 예시 안내, 태그 고정 예시의 호출 순서, 3.7.0 LoRaWAN API.
5. uplink 뒤 Class A 수신 창이 열린다
lorawan_send(port, data, len, type)는 가입된 네트워크에 uplink를 보낸다. payload가 있으면 port를 설정해야 하며, C 인자 len의 상한 255바이트가 실제 전송 가능 payload라는 뜻은 아니다. 지역·data rate·MAC 명령에 따라 다음 패킷의 허용 길이가 작아질 수 있으므로 lorawan_get_payload_sizes()를 확인한다. LORAWAN_MSG_CONFIRMED는 네트워크 ACK를 요구하지만 애플리케이션 처리 완료 증명으로 해석하지 않는다.
이 수신창 도식은 Class A에 한정된다. Class A에서는 각 uplink 뒤 RX1과, 필요하면 RX2 수신 기회가 온다. 따라서 Class A 서버의 downlink를 즉시 임의 시간에 밀어 넣는 통로로 상상하면 안 된다. RX1에 대상 패킷이 들어오면 RX2를 열 필요가 없고, 실제 window 시간·주파수·data rate는 선택한 지역 설정과 네트워크가 좌우한다.
3.7.0 API에는 LORAWAN_CLASS_C와 lorawan_set_class()도 있다. Class C로 전환해 지원 구성에서 호출이 성공하면, 장치는 송신과 RX1·RX2 처리 시간을 제외하고 RXC 수신을 지속한다. 따라서 모든 클래스의 downlink가 다음 uplink에만 묶인다고 일반화할 수 없다. Class C는 수신기의 가동 시간과 전력 조건도 따로 검토해야 한다.
↔ 좁은 화면에서는 송신에서 다음 uplink까지 오른쪽으로 이동하세요. 시간 간격은 축척이 아니며 Class C의 RXC는 이 도식에 포함하지 않았다.
lorawan_send게이트웨이를 거쳐 서버로 전달 시도| 관측 지점 | 3.7.0 계약 | 잘못된 해석 |
|---|---|---|
lorawan_send 반환 | 0 또는 음수 오류. 샘플은 payload 크기 조건의 -EAGAIN을 별도 처리 | 0이면 서버 애플리케이션이 반드시 소비했다 |
| downlink callback | (port, bool data_pending, rssi, snr, len, data); system workqueue에서 실행 | 최신 버전의 uint8_t flags 인자를 3.7.0에 사용 |
| ACK 또는 payload | port 0은 ACK 통지, 일반 downlink는 port 1–255; ACK는 길이 0·data NULL | 모든 callback에 사용자 payload가 담긴다 |
| data rate 변경 callback | lorawan_register_dr_changed_callback()으로 downlink callback과 별도 등록. 가입 성공 직후 1회, 이후 ADR에 따른 DR 변화마다 호출 | downlink callback 등록만으로 DR 통지를 받는다 |
| 수신 클래스 | Class A는 uplink 뒤 RX1/RX2. 지원 구성의 Class C는 lorawan_set_class(LORAWAN_CLASS_C)가 성공하면 그 외 시간에 RXC를 수신 | 모든 downlink는 다음 uplink를 기다린다 |
callback은 짧게. 3.7.0 문서는 downlink callback이 system workqueue에서 실행된다고 명시한다. callback에서 긴 처리나 대기를 하지 말고 필요한 데이터를 복사해 별도 작업으로 넘긴다. 또한 3.7.0의 두 번째 인자는 bool data_pending이다. 최신 헤더의 flags 비트 해석을 그대로 가져오면 함수 시그니처가 맞지 않는다.
근거: 3.7.0 send·callback·payload 크기, 3.7.0 DR callback·Class C enum·set_class API, LoRa Alliance 1.0.4 Class A RX1/RX2·Class C RXC.
6. 한 번에 한 층씩 확인한다
보드가 준비되지 않았다면 서버 설정을 바꿔도 소용없다. 반대로 raw LoRa의 수신 RSSI가 찍힌다고 네트워크 키가 맞는 것은 아니다. 실패 지점을 아래 순서로 좁히고, 각 단계의 로그·설정·서버 이벤트를 같은 시험 시각으로 맞춘다.
v3.7.0 보드 타깃,
lora0 alias, 드라이버와 안테나 대역을 확인한다.두 보드에 맞춘 주파수·BW·SF·coding rate로 패킷 송수신을 확인한다.
CONFIG_LORAMAC_REGION_* 및 네트워크 서버의 채널·EUI·키를 대조한다.join 반환, 다음 payload 한계, uplink 결과와 Class A 수신 창의 서버 이벤트를 비교한다.
| 증상 | 먼저 볼 경계 | 다음 증거 |
|---|---|---|
device_is_ready 실패 | 보드 devicetree·드라이버 | lora0 alias와 pin·SPI·전원 |
| raw 수신 timeout | RF 설정·안테나 | 양쪽 주파수, BW, SF, coding rate, sync |
| join timeout/거절 | 지역 채널 계획·OTAA 등록 | gateway uplink 관측, DevEUI·JoinEUI·키·DevNonce |
| join 후 send 오류 | payload·data rate | lorawan_get_payload_sizes()와 port·오류 코드 |
| uplink만 보이고 downlink 없음 | Class A 창·서버 큐 | RX1/RX2 설정과 다음 uplink 뒤 서버 이벤트 |
7. 고정 버전 근거와 적용 범위
API·구조체·설정 심벌은 아래 3.7.0 문서와 태그 고정 샘플에서 확인했다. LoRa Alliance 문서는 Class A의 RX1/RX2와 Class C의 RXC 수신 창 근거로만 사용했다. 특정 라디오 모듈·국가의 송신 허가, 링크 거리, 소비전류, gateway 호환성은 이 문서만으로 보장하지 않는다. 실제 제품은 사용 지역의 주파수 규정과 보드 드라이버·네트워크 서버 설정을 별도 검증해야 한다.