---
title: "🚀 CRT Software — Trainee 2026.1"
url: "https://maker.wiznet.io/scott/projects/-crt-software--trainee-2026-1/"
markdown_url: "https://maker.wiznet.io/scott/projects/-crt-software--trainee-2026-1/md"
type: "UCC: User Created Content"
author: "joaogscosta (Capital Rocket Team)"
author_url: "https://github.com/joaogscosta/software-trainee-crt"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "joaogscosta (Capital Rocket Team)"
original_url: "https://github.com/joaogscosta/software-trainee-crt"
published: "2026-10-22"
language: "en"
hardware: ["WIZnet W5500"]
likes: 0
views: 6
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# 🚀 CRT Software — Trainee 2026.1

> CRT Software: Hybrid Rocket Ground Control That Falls Back from LoRa to Fiber — A Dual-Link Design with ESP32 and W5500

Original author: joaogscosta (Capital Rocket Team) (source: https://github.com/joaogscosta/software-trainee-crt)

## Components

- **WIZnet W5500** x 1 ([docs](https://docs.wiznet.io/Product/Chip/Ethernet/W5500))

WIZnet parts: W5500 ([Datasheet](https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet?utm_source=maker&utm_medium=project&utm_campaign=w5500), [product hub](https://maker.wiznet.io/products/w5500/))

## Article

## Summary

**CRT Software** is the hybrid rocket telemetry and control system of **Capital Rocket Team (CRT)**, a student rocketry team at the University of Brasília (UnB). **Three ESP32 nodes** (the rocket node uses two boards, a Master and a Slave) split the flight, launch pad, and operator roles. The primary link is **LoRa**, and the backup link is **W5500 UDP over about 1 km of fiber**. Both links carry **the same packet with the same CRC** and only swap the transport. Once filling has started, the launch pad puts itself into a safe state if both links drop. That combination is what makes the project worth a look.

## Overview

A hybrid rocket is loaded with **nitrous oxide (N₂O)** at the pad. Once filling starts, the operator has to work the valves and ignition from a control box far away. If the link carrying those commands drops, there is no longer a way to reach the equipment being filled.

**Capital Rocket Team** is a student team at UnB's Gama Faculty of Engineering (FGA). Local press reported that the team launched the hybrid rocket **"Circe"** at the 2022 Latin American Space Challenge and described it as the first hybrid rocket launch in Latin America. This repository is the software the team built in its **2026 trainee (new member) program**.

- **Developers**: Pedro Oliveira Melo, João Gabryel, Cecília

- **Timeline**: first commit 2026-06-24, last commit 2026-08-23, 30 commits

- **Environment**: ESP32-DevKit-V1, Arduino framework, PlatformIO, Wokwi simulator

→ GitHub: <https://github.com/joaogscosta/software-trainee-crt>
→ Report on the team's 2022 hybrid rocket launch (Jornal de Brasília): <https://jornaldebrasilia.com.br/brasilia/equipe-da-unb-lanca-primeiro-foguete-hibrido-da-america-latina/>

## Why Two Links

### How the Repository Has Been Used

- **Unconfirmed**: pin numbers, IP addresses, and MAC addresses still carry "provisional, confirm with the hardware team before soldering" TODOs, and the repository includes a Wokwi simulator setup. We found no record of it being used in a physical test or launch

- **Team track record confirmed**: the 2022 hybrid rocket launch and the team's planned entry in the hybrid/liquid category of the 2023 Spaceport America Cup were both reported in the press

- **Activity**: 1 star, 1 fork, no license

→ Report on the 2023 competition entry (JB): <https://www.jb.com.br/ciencia-e-tecnologia/2023/06/1044266-universidades-brasileiras-disputam-copa-mundial-de-foguetes-nos-eua.html>

### What the Competition Rules Require

The Design, Test & Evaluation Guide (**DTEG**) from **ESRA**, which runs the Spaceport America Cup (IREC), sets these requirements for hybrid propulsion and launch equipment.

| Section | Requirement |
| --- | --- |
| **5.10.1** | Hybrid and liquid systems must be able to **remotely vent or offload** all liquid and gaseous propellants |
| **11.3.4** | Hybrid launch controllers must include **oxidizer fill level instrumentation** |
| **11.5.3** | Hybrid launch controllers must have a failsafe that **automatically vents oxidizer if power or communication is lost** during fill |

[Inferred: the repository gives no reason for the dual link and never mentions these rules] With a single link, losing that link means losing remote control of the N₂O being filled. CRT **adds a wired path**, and once filling has started, **the launch pad moves itself into a venting state** if both paths drop. In other words, the code contains behavior that matches the "automatic venting on communication loss" called for in 11.5.3.

The edition cited here is **Revision 2024 V1.1.3** for the 2025 IREC (effective 2024-10-22). We could not confirm whether the rules of the Latin American Space Challenge (LASC), which the team entered in 2022, contain the same provisions.

→ ESRA IREC DTEG (2024 V1.1.3): <https://www.soundingrocket.org/uploads/9/0/6/4/9064598/2025_irec_dteg_v1.1.3_10-22-24.pdf>

## Architecture

- **ESP1 (rocket)**: sends flight data to the control box (ESP3) over LoRa and handles parachute deployment. The rocket code has a LoRa command receiver, but the control box never sends commands to the rocket

- **ESP2 (launch pad GCU)**: measures fill mass and pressure and drives the valves and igniter through relays. Once filling has started, it moves itself to a safe state if all links drop

- **ESP3 (control box)**: reads the operator's switches, sends commands, and shows the returned telemetry on LEDs and as CSV

- **Both links exist only between ESP2 and ESP3.** The rocket (ESP1) uses LoRa alone

## Technology Background

### LoRa and RadioHead Reliable Delivery

**LoRa** is a low-power, long-range radio scheme that uses chirp spread spectrum (CSS) modulation. Raising the **spreading factor (SF)** extends range but slows the link; lowering it speeds the link up at the cost of range. CRT uses **SF7, 125 kHz bandwidth, 4/5 coding rate, and 17 dBm**, and a comment in the configuration file puts the range of this setup at about 1 km.

LoRa by itself does not tell the sender whether a packet arrived. CRT therefore uses `RHReliableDatagram` from the **RadioHead** library, which adds node addressing, **ACKs**, and retries.

- **3 retries** and a **200 ms** ACK timeout (`RH_MAX_RETRIES`, `RH_TIMEOUT_MS`)

- When the command send function `sendtoWait()` reports failure, the control box **immediately switches to the wired path and sends the same command again**

It reaches far without a cable, but obstacles or interference can break it.

→ RadioHead documentation: <https://www.airspayce.com/mikem/arduino/RadioHead/>

### Media Converters and Single-Mode Fiber

Copper Ethernet (100BASE-TX) is limited by the standard to **100 m** per segment. To run a wired link over 1 km, CRT uses **media converters**. A media converter turns the Ethernet signal from its RJ45 port into light on a fiber, and the converter at the other end turns it back into an RJ45 signal.

- **Single-mode fiber** has a thin core and is used for spans of several kilometers or more

- Fiber **does not conduct electricity**, so it can electrically isolate the launch pad, with its ignition circuit and relays, from the control box [Inferred: the repository does not say why fiber was chosen]

- The W5500 at each end never touches the fiber. As far as the chip is concerned, **it just has an RJ45 cable plugged in**

According to a comment in the configuration file, this link used to be an **RS-485 (MAX485)** serial link and was later replaced with Ethernet over fiber.

### Watchdogs and the Failsafe State Machine

A **watchdog** is a monitor that treats "no signal within a set time" as a fault. CRT implements it with software timers.

| Constant | Value | Meaning |
| --- | --- | --- |
| `WATCHDOG_ESP2_MS` | **3,000 ms** | No packet for this long means switching links or aborting |
| `CABLE_TIMEOUT_MS` | **3,000 ms** | (Control box) No packet for this long after switching to the wired path means `COMM_ABORT`. The pad does not use this constant and aborts as soon as 3 s pass after the last packet |
| `CABLE_RETRY_LORA_MS` | **30,000 ms** | Interval for trying to restore LoRa while in wired mode |

**Failsafe** is the design principle of "fail toward the safe side." On the launch pad, CRT's safe-state function `executeSafeState()` does the following in one step.

- Fill valve **closed**, main valve **closed**, igniter **off**

- Vent **open**, tank purge and line purge **open** — the direction that relieves tank pressure

According to the comments in `config.h`, the tank purge valve is **normally open (NA)** and **opens when power is lost** (the vent is normally closed and closes when power is lost). During filling, however, this relay is energized to keep the purge closed, so if the MCU hangs while power stays on, the purge stays closed.

## Technical Highlights

- **Two shared headers tie the three nodes together**: all three nodes include `config.h` (pins, LoRa settings, IP addresses, mass and pressure limits, timing) and `packet_protocol.h` (packet structs, state and command codes, CRC8) through relative paths. The rule "no pin or packet is defined outside these files" is written down in `CONVENCOES.md`

- **Transport-independent packets**: a command is a **3-byte** `CommandPacket`, `[type 1B][command ID 1B][CRC8 1B]`. The **same struct** goes out whether it travels over LoRa or UDP

- **Actual valve state comes back**: the pad reads its relay pins directly and returns them as a 5-bit `valve_state`. The control box compares this with the operator's switches and shows each LED in one of three states: **off (switch off, pad reports closed) / blinking (switch position and pad report differ) / on (switch on, pad confirms open)**

- **Mission state machine**: `BOOT → SAFE → ARMED → IGNITION → READY_TO_LAUNCH → LAUNCHED`, and every state except BOOT can go to `ABORTED`. The control box aborts on any of three conditions: the manual ABORT button, manifold pressure at or above 58 bar, or loss of communication

- **Separate manager classes**: the control box code is split into `CommManager`, `MissionManager`, `InputManager`, `LedManager`, and `TelemetryManager`

- **Testing without hardware**: a simulation mode and a single-file Wokwi version (1,054 lines) let you mimic switches and link failures through serial key input

## Where WIZnet Fits

### TOE in Use: Yes

CRT uses the W5500 as a **hardware TCP/IP offload engine (TOE)**.

- Both nodes use `Ethernet.init()`, `Ethernet.begin()`, and `EthernetUDP` from the Arduino **`Ethernet` library** (`arduino-libraries/Ethernet`). This library **opens a hardware socket inside the W5500 in UDP mode**

- The ESP32 has its own TCP/IP stack (lwIP) and Ethernet MAC, but this link does not use them (`ETH.h`, `esp_eth`). As the W5500 datasheet describes, **the chip handles UDP, IP, and ARP**

### Configuration

| Item | Value | Location |
| --- | --- | --- |
| Chip | **Two W5500s** (one each on ESP2 and ESP3) | `UnidadeDeControle.ino`, `CommManager.cpp` |
| Transport | **UDP**, port **5000** | `config.h` |
| Addresses | ESP3 **192.168.10.10**, ESP2 **192.168.10.20**, /24, no gateway | `config.h` |
| SPI | **Same bus as LoRa** (SCK 18, MISO 19, MOSI 23), W5500 CS = **GPIO16** | `config.h` |
| Physical path | RJ45 → media converter → about 1 km of single-mode fiber → media converter → RJ45 | `config.h` comment |

Pins, IP addresses, and MAC addresses are all still marked as "provisional" TODOs.

→ W5500 datasheet: <https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet>

### How One Command Travels over the Wired Path

1. **Detect LoRa failure** — when the operator turns on the fill switch, the control box (ESP3) builds a 3-byte `CMD_OPEN_FILL` packet and sends it over LoRa. If RadioHead gets no ACK after its retries, `sendtoWait()` returns failure

2. **Switch to wired** — ESP3 sets the link state to `COMM_CABLE` and passes **the same packet** to `sendUDPPacket()`

3. **Write to the chip** — `beginPacket(192.168.10.20, 5000)`, `write(3 bytes)`, `endPacket()`. The data the MCU sends over SPI is **the destination address and port plus the 3-byte payload**; the rest is the send command and status checks

4. **The chip builds the frame** — the W5500 **resolves the peer's MAC with ARP**, adds the UDP header, IP header, and Ethernet framing, and sends it out the RJ45 port

5. **Fiber segment** — the media converter turns it into light for the 1 km run, and the converter at the other end turns it back into Ethernet for the pad's W5500

6. **The chip holds the data** — the pad's W5500 checks the destination IP and port and stores the payload in its internal receive buffer

7. **Fetch and verify** — on every loop, the pad's ESP2 checks for arrivals with `parsePacket()`, reads 3 bytes, and checks the **CRC8**

8. **Execute** — `handleCommand()` processes the command with **the same function** used for LoRa. `CMD_OPEN_FILL` moves the pad into the fill phase (`FASE_ENCHENDO`) when it is in the SAFE state, and the state machine turns on the fill valve relay on the next loop. From then on, the pad sends its telemetry back over the same UDP path

**What the MCU does not do**: ARP, building and checking IP and UDP headers, sending and receiving Ethernet frames, buffering received data
**What the MCU does**: build the 3-byte packet, check the CRC, decide the link state, drive the relays

### Analogy: Same Envelope, Two Delivery Routes

A command packet is **an envelope of a fixed size**. Normally it goes by mail (LoRa). If the first attempt and all three retries come back without a delivery confirmation, the same envelope is loaded onto **a dedicated delivery van (W5500 + fiber)**. The van's driver (the W5500) handles the address check, the route, and the packaging, so the sender (the ESP32) only has to hand over the envelope. At the other end, the envelope is **opened the same way** no matter which route it took, and its contents are carried out.

### What If the Chip Were Not There [Inferred]

The ESP32 has a **built-in Ethernet MAC**, so another option would be to attach an external PHY over RMII and use lwIP.

- That would need nine RMII signal lines, including the 50 MHz reference clock (per the ESP-IDF documentation). On CRT's boards, which already use GPIOs for relays, sensors, and switches, pin assignment could get tight

- The W5500 attaches to **the SPI bus LoRa already uses, adding just one CS pin**. In `config.h`, CRT reassigned GPIO16, 17, and 4, previously used by RS-485, to the W5500's CS, RST, and INT; the current code actually uses only CS

- On the software side, the send code is three lines: `beginPacket()`, `write()`, and `endPacket()`

## Similar Projects on WIZnet Makers

Several projects in student rocketry and remote telemetry already build their ground links with the W5500. They place the same chip in different spots, which makes CRT's choice easier to see.

- **warp-core** — launch control firmware from the Oregon State University hybrid rocket team. It links mission control and the pad with a single W5500 **TCP** socket on an 8-bit ATmega1284p. The biggest difference from CRT is that **it has only one link and its firmware does not detect communication loss**
  → Project link: <https://maker.wiznet.io/scott/projects/warp-core/>

- **Hybrid PCC** — the hybrid rocket pad control box from the CU InSpace team at Carleton University in Canada. **A wireless bridge covers the long span between mission control and the pad**, and the W5500 handles the wired segment on the control box side. CRT does the opposite: it **lays the W5500 and fiber across the long span itself** and uses it as the backup to the radio
  → Project link: <https://maker.wiznet.io/Sunny_/projects/hybrid-pcc/>

- **Aerostat-GCS-ESP32-TelemetryHub** — a ground control telemetry hub for a tethered aerostat. It puts the **ESP32 + W5500 TCP** output onto **fiber through a media converter** and runs it down the tether. It shares the "W5500 → media converter → fiber" setup with CRT, but **fiber is the primary link**, with no wireless backup
  → Project link: <https://maker.wiznet.io/lawrence/projects/prvin-embed-aerostat-gcs-esp32-telemetryhub/>

According to their maker articles, warp-core and Aerostat GCS **let the W5500 handle the protocol in hardware sockets** while the MCU deals only with data (this is not confirmed in public material for Hybrid PCC). What differs is **where the wired link sits and what role it plays**.

| Item | **CRT Software** (UnB) | warp-core (OSU) | Hybrid PCC (Carleton) | Aerostat GCS |
| --- | --- | --- | --- | --- |
| Purpose | Hybrid rocket fill and ignition | Hybrid rocket launch control | Hybrid rocket pad control | Tethered aerostat telemetry |
| MCU | **ESP32** ×3 nodes (4 boards) | ATmega1284p | RP2040 | ESP32 + Arduino Mega |
| W5500 mode | **TOE** | TOE | Unknown | TOE |
| Transport | **UDP** | TCP | Unknown | TCP |
| Long-range span | **LoRa + fiber, redundant** | Single wired Ethernet | Wireless bridge | Single fiber |
| Link-loss handling | **Automatic ABORT** (pad: 3 s after filling starts) | None in firmware | Unknown | Waits for reconnection, keeps logging to SD |

Of the four projects compared, **CRT is the only one confirmed by public material to use a wired link as the backup to radio**, and it defines in code what happens when both paths drop.

## Use Cases

- **Remote control with radio as the primary link and a wired backup** — the same structure is worth considering for engine test stands, launch sites, and equipment spread across a large site, where radio is convenient day to day but must not fail. When CRT moved its backup link from RS-485 to UDP, it kept the packet structure unchanged

- **Teams replacing RS-485 with Ethernet over fiber** — CRT took this path in code and configuration. It reassigned the pins RS-485 used to the W5500 and noted a media converter and about 1 km of fiber in the configuration comments (no record of a physical build)

- **Designs that share one SPI bus between a radio module and the W5500** — an example of pin assignment that runs LoRa and Ethernet together by giving each its own CS pin

- **Collaboration structure for student teams** — the shared headers and the `CONVENCOES.md` rules show one way to keep pin and packet definitions for several boards in one place

## Limitations and Future Improvements

In about two months of a trainee program, CRT Software gained **a three-node state machine, a redundant link, automatic ABORT, and a simulator**. Pins and IP addresses are still provisional, and we found no record of physical testing, so a team turning it into field equipment should look at the following first.

### 1. The Heartbeat Runs One Way — A Spot Where ABORT Can Happen on a Healthy Link

- **Today**: once filling has started (during filling and after it completes, including after ARM, until DISARM or ABORT), the launch pad (ESP2) **aborts if it receives no packet at all for 3 seconds** (`checkWatchdog()`). The control box (ESP3), however, sends valve commands **only when a switch changes**. The only repeated sends are in the ignition state (every loop for 2 seconds) and the ABORT state (every 500 ms). Even then, when the abort was caused by communication loss, the link is in `COMM_ABORT`, so `sendCommand()` sends nothing. PINGs go out only every 30 seconds over LoRa while in wired mode

- **[Inferred] Impact**: if the operator turns on the fill switch and touches nothing else for 3 seconds, the pad may treat a healthy link as lost and abort. We could not confirm this behavior on real hardware

- **Improvement**: have the control box **send a PING at a fixed interval (for example, every 500 ms)** regardless of state. The pad's UDP receiver reads into a 3-byte buffer and checks the last byte as the CRC, so a 6-byte `PingPacket` sent over UDP is dropped on a CRC mismatch (the LoRa path checks against the actual length and accepts PINGs). The read length and CRC position need to depend on the packet type

### 2. No Ignition Interlock on the Pad Side

- **Today**: the ARM key check lives only in the control box's state machine. The pad's `CMD_IGNITION` handler ignores the current state and **turns on the main valve and the igniter immediately**

- **Improvement**: add a one-line condition on the pad so ignition is accepted only when `g_estadoAtual == STATE_ARMED`. That gives the executing side a second chance to block a bad command

### 3. Waiting for DHCP at Boot

- **Today**: both nodes call `Ethernet.begin(mac)` first to try DHCP and fall back to a static IP if it fails. The Arduino Ethernet library's default DHCP timeout is **60 seconds**. A point-to-point fiber link has no DHCP server, so **every boot can wait up to about 60 seconds**

- **Improvement**: use a static IP from the start with `Ethernet.begin(mac, ip, dns, gateway, subnet)`

→ Arduino Ethernet library `Ethernet.h`: <https://github.com/arduino-libraries/Ethernet/blob/master/src/Ethernet.h>

### 4. Each Node Decides the Link State on Its Own

- **Today**: ESP2 and ESP3 each decide when to switch to `COMM_CABLE` (the control box by timer and send failure, the pad by the fill-phase timer and the path a packet arrived on). The pad sends telemetry over UDP only while it is in wired mode, so if the two nodes briefly disagree, replies can go down a different path

- **Improvement**: send telemetry over both paths and let the receiver use whichever arrives first, or align the link state between nodes through the packet's `comm_state` field

### 5. Provisional Values and Documentation

- **GPIO33 overlap**: the tank purge NF relay and the manifold vent relay are defined on the same pin, and the code carries a TODO to check it

- **Pressure conversion**: a provisional formula maps the ADC value linearly to 0–100 bar. The automatic ABORT at 58 bar depends on this value, so sensor calibration should come first

- **Documentation**: the CRC comment says "CRC-8/MAXIM," but the implementation uses the non-reflected polynomial 0x31. The README says the Ethernet library is "included in the ESP32 core," but it is actually pulled in separately through PlatformIO `lib_deps`

- The repository has **no LICENSE file**. Check with the team before reuse

## FAQ

**Q1. Why UDP instead of TCP for the backup link?**
The repository does not say. [Inferred] Command packets are only 3 bytes, and the application handles integrity with CRC8 and link monitoring with watchdogs, so UDP, with no connection to manage, was probably the simpler choice. UDP has no delivery confirmation, though, and valve commands are not retransmitted. In wired mode, the only practical confirmation for a valve command is **the operator checking the returned `valve_state` on the LEDs**. ARM, ignition, and ABORT commands have no such confirmation path.

**Q2. Why not just run a long LAN cable instead of fiber?**
Copper Ethernet is limited by the standard to **100 m** per segment. Covering 1 km in copper would take several switches along the way. A pair of media converters and a fiber cover that distance in one run and also electrically isolate the two ends. The repository does not say whether that is why CRT chose fiber.

**Q3. What happens when both links are up at the same time?**
Normally only LoRa is used. The pad listens on both LoRa and UDP and executes a command from either one, but the control box sends over UDP only when LoRa fails. In wired mode, the control box still sends a LoRa PING every 30 seconds, and it returns to LoRa when a PING succeeds or pad telemetry arrives over LoRa.

**Q4. Can I test it without the hardware?**
Yes. The control box code has a **simulation mode**, and a single-file Wokwi version is included. Serial key input lets you mimic the ARM key, valve switches, ABORT, and link failures and watch the state machine and LEDs respond. Actual W5500 and LoRa communication cannot be checked in the simulator.

---

---

## 요약

**CRT Software**는 브라질리아대학교(UnB) 학생 로켓 팀 **Capital Rocket Team(CRT)**의 하이브리드 로켓 텔레메트리·제어 시스템입니다. **ESP32 노드 3개**(로켓 노드는 Master·Slave 보드 2개)가 비행, 발사대, 운영자 역할을 나눠 맡고, 1차 링크는 **LoRa**, 예비 링크는 **W5500 UDP와 약 1km 광섬유**입니다. 두 링크가 **같은 패킷과 같은 CRC**를 실어 나르고 전송 수단만 바꿔 끼우며, 충전을 시작한 뒤 둘 다 끊기면 발사대가 스스로 안전 상태로 들어간다는 점이 이 프로젝트의 핵심입니다.

## 개요

하이브리드 로켓은 발사대에서 **아산화질소(N₂O)**를 충전합니다. 충전이 시작되면 운영자는 멀리 떨어진 조종 상자에서 밸브와 점화를 다뤄야 하고, 그 명령이 오가는 통신 링크가 끊기면 충전 중인 설비를 원격으로 다룰 수 없게 됩니다.

**Capital Rocket Team**은 UnB 가마 공과대학(FGA)의 학생 팀입니다. 현지 언론은 이 팀이 2022년 Latin American Space Challenge에서 하이브리드 로켓 **"Circe"**를 발사했다고 보도하면서, 라틴아메리카 최초의 하이브리드 로켓 발사로 소개했습니다. 이 저장소는 그 팀의 **2026년 트레이니(신입) 과정**에서 만든 소프트웨어입니다.

- **개발**: Pedro Oliveira Melo, João Gabryel, Cecília

- **기간**: 2026-06-24 첫 커밋 ~ 2026-08-23 마지막 커밋, 커밋 30개

- **환경**: ESP32-DevKit-V1, Arduino 프레임워크, PlatformIO, Wokwi 시뮬레이터

→ GitHub: <https://github.com/joaogscosta/software-trainee-crt>
→ 팀의 2022년 하이브리드 로켓 발사 보도(Jornal de Brasília): <https://jornaldebrasilia.com.br/brasilia/equipe-da-unb-lanca-primeiro-foguete-hibrido-da-america-latina/>

## 왜 링크를 둘로 나눴나

### 저장소의 실제 사용

- **확인 불가**: 핀 번호, IP, MAC 주소에 "임시값, 납땜 전에 하드웨어 팀과 확인" TODO가 남아 있고, 저장소에 Wokwi 시뮬레이터 설정이 들어 있습니다. 실물 시험이나 발사에 쓰였다는 기록은 찾지 못했습니다

- **팀 실적은 확인됨**: 2022년 하이브리드 로켓 발사와, 2023년 Spaceport America Cup 하이브리드·액체 부문 출전 예정이 보도되었습니다

- **활동 지표**: Star 1, Fork 1, 라이선스 없음

→ 2023년 대회 출전 보도(JB): <https://www.jb.com.br/ciencia-e-tecnologia/2023/06/1044266-universidades-brasileiras-disputam-copa-mundial-de-foguetes-nos-eua.html>

### 대회 규정이 요구하는 것

Spaceport America Cup(IREC)을 주관하는 **ESRA**의 설계·시험·평가 지침(**DTEG**)은 하이브리드 추진계와 발사 장비에 다음을 요구합니다.

| 조항 | 요구 내용 |
| --- | --- |
| **5.10.1** | 하이브리드·액체 추진은 모든 액체·기체 추진제를 **원격으로 배출(venting)하거나 빼낼** 수단을 갖출 것 |
| **11.3.4** | 하이브리드 발사 제어기는 **산화제 충전량 계측**을 포함할 것 |
| **11.5.3** | 충전 중 **전원 또는 통신이 끊기면 산화제를 자동 배출**하는 페일세이프를 갖출 것 |

[추정: 저장소에는 이중화 이유도, 이 규정에 대한 언급도 없습니다] 링크가 하나뿐이면 그 링크가 끊기는 순간 충전 중인 N₂O를 원격으로 다룰 수 없게 됩니다. CRT는 **유선 경로를 하나 더 두고**, 충전을 시작한 뒤 두 경로가 모두 끊기면 **발사대가 스스로 배출 상태로 들어가게** 했습니다. 11.5.3이 요구하는 "통신 두절 시 자동 배출"에 해당하는 동작이 코드에 있는 셈입니다.

인용한 판본은 2025 IREC용 **Revision 2024 V1.1.3**(2024-10-22 발효)입니다. 팀이 2022년에 출전한 Latin American Space Challenge(LASC) 규정에 같은 조항이 있는지는 확인하지 못했습니다.

→ ESRA IREC DTEG (2024 V1.1.3): <https://www.soundingrocket.org/uploads/9/0/6/4/9064598/2025_irec_dteg_v1.1.3_10-22-24.pdf>

## 시스템 구성

- **ESP1 (로켓)**: 비행 데이터를 LoRa로 조종 상자(ESP3)에 보냅니다. 낙하산 전개도 맡습니다. 로켓 코드에 LoRa 명령 수신부가 있지만, 조종 상자는 로켓에 명령을 보내지 않습니다

- **ESP2 (발사대 GCU)**: 충전량과 압력을 재고, 릴레이로 밸브와 점화기를 움직입니다. 충전을 시작한 뒤 링크가 모두 끊기면 스스로 안전 상태로 갑니다

- **ESP3 (조종 상자)**: 운영자의 스위치를 읽어 명령을 보내고, 받은 텔레메트리를 LED와 CSV로 보여 줍니다

- **두 링크는 ESP2와 ESP3 사이에만 있습니다.** 로켓(ESP1)은 LoRa 하나만 씁니다

## 기술 배경

### LoRa와 RadioHead 신뢰 전송

**LoRa**는 처프 확산 스펙트럼(CSS) 변조를 쓰는 저전력 장거리 무선 방식입니다. **확산 계수(SF)**를 높이면 더 멀리 가지만 느려지고, 낮추면 빨라지는 대신 거리가 줄어듭니다. CRT는 **SF7, 대역폭 125 kHz, 코딩률 4/5, 17 dBm**을 쓰고, 설정 파일 주석은 이 조합의 거리를 약 1km로 적었습니다.

LoRa 자체는 패킷이 도착했는지 알려주지 않습니다. 그래서 CRT는 **RadioHead** 라이브러리의 `RHReliableDatagram`을 씁니다. 이 계층이 노드 주소, **ACK**, 재시도를 붙입니다.

- 재시도 **3회**, ACK 대기 **200 ms** (`RH_MAX_RETRIES`, `RH_TIMEOUT_MS`)

- 명령 송신 함수 `sendtoWait()`가 실패를 돌려주면, 조종 상자는 **즉시 유선으로 넘어가 같은 명령을 다시 보냅니다**

선 없이 멀리 가지만, 장애물이나 간섭이 생기면 끊길 수 있습니다.

→ RadioHead 문서: <https://www.airspayce.com/mikem/arduino/RadioHead/>

### 미디어 컨버터와 싱글모드 광섬유

구리 Ethernet(100BASE-TX)은 표준상 한 구간이 **100 m**까지입니다. 1km를 유선으로 잇기 위해 CRT는 **미디어 컨버터**를 씁니다. 미디어 컨버터는 RJ45로 들어온 Ethernet 신호를 빛으로 바꿔 광섬유로 보내고, 반대편 컨버터가 다시 RJ45 신호로 되돌립니다.

- **싱글모드 광섬유**는 수 km 이상을 잇는 데 쓰는 가는 코어의 광섬유입니다

- 광섬유는 **전기를 통하지 않으므로**, 점화 회로와 릴레이가 있는 발사대와 조종 상자를 전기적으로 분리할 수 있습니다 [추정: 저장소에는 광섬유를 고른 이유가 적혀 있지 않습니다]

- 양 끝의 W5500은 광섬유를 직접 다루지 않습니다. 칩 입장에서는 **그냥 RJ45 케이블이 하나 꽂혀 있는 것**입니다

설정 파일 주석에 따르면 이 자리는 원래 **RS-485(MAX485)** 직렬 링크였고, Ethernet과 광섬유로 바뀌었습니다.

### 와치독과 페일세이프 상태머신

**와치독**은 "정해진 시간 안에 신호가 오지 않으면 문제가 생긴 것으로 본다"는 감시 장치입니다. CRT는 이를 소프트웨어 타이머로 구현합니다.

| 상수 | 값 | 의미 |
| --- | --- | --- |
| `WATCHDOG_ESP2_MS` | **3,000 ms** | 이 시간 동안 패킷이 없으면 링크를 바꾸거나 중단 |
| `CABLE_TIMEOUT_MS` | **3,000 ms** | (조종 상자) 유선으로 넘어간 뒤에도 이 시간 동안 없으면 `COMM_ABORT`. 발사대는 이 상수를 쓰지 않고, 마지막 패킷에서 3초가 지나면 바로 중단 |
| `CABLE_RETRY_LORA_MS` | **30,000 ms** | 유선 모드에서 LoRa 복구를 시도하는 간격 |

**페일세이프**는 "고장이 나면 안전한 쪽으로 간다"는 설계 원칙입니다. CRT 발사대의 안전 상태 함수 `executeSafeState()`는 다음을 한 번에 합니다.

- 충전 밸브 **닫힘**, 메인 밸브 **닫힘**, 점화기 **꺼짐**

- 벤트 **열림**, 탱크 퍼지와 라인 퍼지 **열림** — 탱크의 압력을 빼는 방향

`config.h` 주석에 따르면 탱크 퍼지 밸브는 **NA(평상시 열림)** 형이라 **전원이 끊기면 열리도록** 되어 있습니다(벤트는 NF형이라 전원이 끊기면 닫힙니다). 다만 충전 중에는 이 릴레이를 통전해 퍼지를 닫아 두므로, 전원이 살아 있는 채로 MCU가 멈추면 닫힌 상태가 유지됩니다.

## 기술 특징

- **공유 헤더 두 개로 세 노드를 묶음**: `config.h`(핀, LoRa 설정, IP, 질량·압력 한계, 타이밍)와 `packet_protocol.h`(패킷 구조체, 상태·명령 코드, CRC8)를 세 노드가 상대 경로로 함께 포함합니다. "이 파일 밖에서 핀이나 패킷을 정의하지 않는다"는 규칙을 `CONVENCOES.md`로 문서화했습니다

- **전송 수단과 무관한 패킷**: 명령은 `[유형 1B][명령 ID 1B][CRC8 1B]`의 **3바이트** `CommandPacket`입니다. LoRa로 보내든 UDP로 보내든 **같은 구조체를 그대로** 씁니다

- **실제 밸브 상태를 되돌려 받음**: 발사대는 릴레이 핀을 직접 읽어 5비트 `valve_state`로 돌려보냅니다. 조종 상자는 이것을 운영자가 켠 스위치와 비교해 LED를 **꺼짐(스위치 꺼짐·발사대 닫힘) / 깜빡임(스위치 위치와 발사대 보고가 다름) / 켜짐(스위치 켜짐·발사대 열림 확인)** 세 가지로 보여 줍니다

- **임무 상태머신**: `BOOT → SAFE → ARMED → IGNITION → READY_TO_LAUNCH → LAUNCHED`, 그리고 BOOT를 뺀 모든 상태에서 `ABORTED`로 갈 수 있습니다. 조종 상자는 수동 ABORT 버튼, 매니폴드 압력 58 bar 이상, 통신 두절 중 하나라도 걸리면 ABORT로 갑니다

- **관리 클래스 분리**: 조종 상자 코드를 `CommManager`, `MissionManager`, `InputManager`, `LedManager`, `TelemetryManager`로 나눴습니다

- **실물 없이 시험**: 시뮬레이션 모드와 Wokwi용 단일 파일 버전(1,054줄)이 있어, 시리얼 키 입력으로 스위치와 링크 장애를 흉내 낼 수 있습니다

## W5500이 맡은 일

### TOE 사용 판정: 예

CRT는 W5500을 **하드웨어 TCP/IP 오프로드(TOE)** 칩으로 씁니다.

- 두 노드 모두 Arduino **`Ethernet` 라이브러리**(`arduino-libraries/Ethernet`)의 `Ethernet.init()`, `Ethernet.begin()`, `EthernetUDP`를 씁니다. 이 라이브러리는 W5500 내부의 **하드웨어 소켓을 UDP 모드로 열어** 씁니다

- ESP32에는 자체 TCP/IP 스택(lwIP)과 Ethernet MAC이 있지만, 이 링크는 그쪽(`ETH.h`, `esp_eth`)을 쓰지 않습니다. W5500 데이터시트가 밝힌 대로 **UDP·IP·ARP 처리는 칩이 합니다**

### 설정값

| 항목 | 값 | 위치 |
| --- | --- | --- |
| 칩 | **W5500** 2개 (ESP2, ESP3에 하나씩) | `UnidadeDeControle.ino`, `CommManager.cpp` |
| 전송 | **UDP**, 포트 **5000** | `config.h` |
| 주소 | ESP3 **192.168.10.10**, ESP2 **192.168.10.20**, /24, 게이트웨이 없음 | `config.h` |
| SPI | LoRa와 **같은 버스**(SCK 18, MISO 19, MOSI 23), W5500 CS = **GPIO16** | `config.h` |
| 물리 구간 | RJ45 → 미디어 컨버터 → 싱글모드 광섬유 약 1km → 미디어 컨버터 → RJ45 | `config.h` 주석 |

핀, IP, MAC은 모두 "임시값" TODO 상태입니다.

→ W5500 데이터시트: <https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet>

### 명령 하나가 유선으로 가는 순서

1. **LoRa 실패 감지** — 운영자가 충전 스위치를 켜면 조종 상자(ESP3)가 3바이트 `CMD_OPEN_FILL` 패킷을 만들어 LoRa로 보냅니다. RadioHead가 재시도 3회 끝에 ACK를 받지 못하면 `sendtoWait()`가 실패를 돌려줍니다

2. **유선으로 전환** — ESP3가 링크 상태를 `COMM_CABLE`로 바꾸고, **같은 패킷**을 `sendUDPPacket()`으로 넘깁니다

3. **칩에 쓰기** — `beginPacket(192.168.10.20, 5000)`, `write(3바이트)`, `endPacket()`. MCU가 SPI로 넘기는 데이터는 **목적지 주소·포트와 3바이트 페이로드**이고, 나머지는 송신 명령과 상태 확인입니다

4. **칩이 프레임 생성** — W5500이 **ARP로 상대 MAC을 찾고**, UDP 헤더·IP 헤더·Ethernet 프레임을 붙여 RJ45로 내보냅니다

5. **광섬유 구간** — 미디어 컨버터가 빛으로 바꿔 1km를 보내고, 반대편 컨버터가 다시 Ethernet으로 돌려 발사대 W5500에 넣습니다

6. **칩이 수신 보관** — 발사대 W5500이 목적지 IP와 포트를 확인하고, 페이로드를 내부 수신 버퍼에 보관합니다

7. **꺼내기와 검증** — 발사대 ESP2가 루프마다 `parsePacket()`으로 도착 여부를 확인하고, 3바이트를 읽어 **CRC8**을 맞춰 봅니다

8. **실행** — `handleCommand()`가 LoRa로 받았을 때와 **같은 함수로** 명령을 처리합니다. `CMD_OPEN_FILL`은 발사대가 SAFE 상태일 때 충전 단계(`FASE_ENCHENDO`)로 바꾸고, 다음 루프의 상태머신이 충전 밸브 릴레이를 켭니다. 발사대는 이후 텔레메트리를 같은 UDP 경로로 돌려보냅니다

**MCU가 하지 않는 일**: ARP, IP·UDP 헤더 생성과 확인, Ethernet 프레임 송수신, 수신 데이터 버퍼링
**MCU가 하는 일**: 3바이트 패킷 만들기, CRC 확인, 링크 상태 판단, 릴레이 구동

### 비유: 같은 봉투, 두 개의 배송 경로

명령 패킷은 **규격이 정해진 봉투**입니다. 평소에는 우편(LoRa)으로 보냅니다. 우편이 처음 한 번과 재시도 세 번 모두 배달 확인을 못 받으면, 같은 봉투를 **전용 배송차(W5500 + 광섬유)**에 싣습니다. 배송차 운전사(W5500)가 주소 확인, 길 찾기, 포장을 다 하므로, 보내는 사람(ESP32)은 봉투를 건네기만 하면 됩니다. 받는 쪽은 어느 경로로 왔든 **같은 방식으로 봉투를 열고** 내용을 실행합니다.

### 만약 이 칩이 없었다면 [추정]

ESP32는 **Ethernet MAC을 내장**하고 있어, 외부 PHY 칩을 RMII로 붙이고 lwIP를 쓰는 방법도 있습니다.

- 그 경우 50 MHz 기준 클록을 포함한 RMII 신호선 9개(ESP-IDF 문서 기준)가 필요하고, 릴레이·센서·스위치에 이미 GPIO를 쓰고 있는 CRT 보드에서는 핀 배정이 빠듯해질 수 있습니다

- W5500은 **LoRa가 이미 쓰는 SPI 버스에 CS 핀 하나만 더해** 붙습니다. CRT는 `config.h`에서 RS-485가 쓰던 GPIO16·17·4를 W5500의 CS·RST·INT로 재배정했고, 현재 코드가 실제로 쓰는 것은 CS 하나입니다

- 소프트웨어 쪽에서는 송신 코드가 `beginPacket()`·`write()`·`endPacket()` 세 줄입니다

## WIZnet Makers 유사 사례

학생 로켓 팀과 원격 계측 분야에서 W5500으로 지상 링크를 만든 사례가 있습니다. 같은 칩을 서로 다른 위치에 둔 사례들이라 함께 읽으면 CRT의 선택이 잘 보입니다.

- **warp-core** — 오리건주립대 하이브리드 로켓 팀의 발사 제어 펌웨어입니다. 8비트 ATmega1284p와 W5500 **TCP** 소켓 하나로 관제와 발사대를 잇습니다. **링크가 하나뿐이고 펌웨어가 통신 두절을 감지하지 않는다**는 점이 CRT와 가장 크게 다릅니다
  → 프로젝트 링크: <https://maker.wiznet.io/scott/projects/warp-core/>

- **Hybrid PCC** — 캐나다 칼턴대 CU InSpace 팀의 하이브리드 로켓 발사대 제어 상자입니다. **관제와 발사대 사이의 장거리 구간은 무선 브리지**가 맡고, W5500은 제어 상자 쪽 유선 구간을 맡습니다. CRT는 반대로 **장거리 구간 자체에 W5500과 광섬유를 깔아** 무선의 예비 경로로 씁니다
  → 프로젝트 링크: <https://maker.wiznet.io/Sunny_/projects/hybrid-pcc/>

- **Aerostat-GCS-ESP32-TelemetryHub** — 계류 기구(aerostat)의 지상 관제 텔레메트리 허브입니다. **ESP32 + W5500 TCP** 출력을 **미디어 컨버터로 광섬유**에 실어 테더를 따라 내려보냅니다. CRT와 같은 "W5500 → 미디어 컨버터 → 광섬유" 구성이지만, 무선 백업 없이 **광섬유가 주 링크**입니다
  → 프로젝트 링크: <https://maker.wiznet.io/lawrence/projects/prvin-embed-aerostat-gcs-esp32-telemetryhub/>

각 maker 글에 따르면 warp-core와 Aerostat GCS는 **W5500이 하드웨어 소켓으로 프로토콜을 처리**하고 MCU는 데이터만 다룹니다(Hybrid PCC는 공개 자료로 확인되지 않습니다). 차이는 **유선을 어디에, 어떤 역할로 두느냐**입니다.

| 항목 | **CRT Software** (UnB) | warp-core (OSU) | Hybrid PCC (Carleton) | Aerostat GCS |
| --- | --- | --- | --- | --- |
| 용도 | 하이브리드 로켓 충전·점화 | 하이브리드 로켓 발사 제어 | 하이브리드 로켓 발사대 제어 | 계류 기구 텔레메트리 |
| MCU | **ESP32** 노드 ×3(보드 4개) | ATmega1284p | RP2040 | ESP32 + Arduino Mega |
| W5500 모드 | **TOE** | TOE | 미상 | TOE |
| 전송 | **UDP** | TCP | 미상 | TCP |
| 장거리 구간 | **LoRa + 광섬유 이중화** | 유선 Ethernet 하나 | 무선 브리지 | 광섬유 하나 |
| 통신 두절 처리 | **자동 ABORT**(발사대: 충전 시작 후 3초) | 펌웨어에 없음 | 미상 | 재접속 대기, SD 기록 지속 |

비교한 네 사례 중 **유선을 무선의 예비 경로로 둔 것이 공개 자료로 확인되는 것은 CRT뿐**이고, 두 경로가 모두 끊겼을 때의 동작까지 코드로 정해 두었습니다.

## 어디에 쓰나

- **무선이 주 링크이고 유선을 예비로 두는 원격 제어** — 엔진 시험대, 발사장, 넓은 부지의 현장 설비처럼 평소에는 무선이 편하지만 끊기면 안 되는 곳에 같은 구조를 검토해 볼 수 있습니다. CRT는 예비 링크를 RS-485에서 UDP로 바꿀 때 패킷 구조를 그대로 유지했습니다

- **RS-485를 Ethernet과 광섬유로 바꾸려는 팀** — CRT가 코드와 설정에서 밟은 경로입니다. RS-485가 쓰던 핀을 W5500에 재배정했고, 설정 주석에 미디어 컨버터와 광섬유 약 1km 구성을 적어 두었습니다(실물 구축 기록은 없음)

- **무선 모듈과 W5500을 한 SPI 버스에 함께 두는 설계** — CS 핀만 나눠 LoRa와 Ethernet을 함께 쓰는 핀 배치의 예입니다

- **학생 팀의 협업 구조** — 공유 헤더와 `CONVENCOES.md` 규칙으로 여러 보드의 핀·패킷 정의를 한곳에 묶는 방식을 참고할 수 있습니다

## 한계와 개선 방향

CRT Software는 두 달 남짓한 트레이니 과정에서 **3노드 상태머신, 이중화 링크, 자동 ABORT, 시뮬레이터**까지 갖춘 저장소입니다. 핀·IP가 아직 임시값이고 실물 시험 기록도 찾지 못했으므로, 실전 장비로 다듬을 때 아래를 먼저 보는 것이 좋습니다.

### 1. 하트비트가 한쪽에만 있다 — 정상 링크에서도 ABORT가 날 수 있는 지점

- **현재**: 발사대(ESP2)는 충전을 시작한 뒤(충전 중과 충전 완료 후, ARM 이후 포함, DISARM이나 ABORT 전까지) **3초 동안 패킷을 하나도 받지 못하면 ABORT**합니다(`checkWatchdog()`). 그런데 조종 상자(ESP3)는 밸브 명령을 **스위치가 바뀔 때만** 보내고, 반복해서 보내는 것은 점화 상태(2초 동안 루프마다)와 ABORT 상태(500 ms마다)뿐입니다. 그나마 통신 두절로 ABORT된 경우에는 링크가 `COMM_ABORT`라 `sendCommand()`가 아무것도 보내지 않습니다. PING도 유선 모드에서 30초마다 LoRa로 보낼 때만 나갑니다

- **[추정] 영향**: 충전 스위치를 켠 뒤 3초 안에 다른 조작이 없으면, 링크가 멀쩡해도 발사대가 통신 두절로 판단해 ABORT할 수 있습니다. 실물에서 이렇게 동작하는지는 확인하지 못했습니다

- **개선 방향**: 조종 상자가 상태와 관계없이 **주기적으로(예: 500 ms) PING을 보내도록** 합니다. 발사대의 UDP 수신부는 3바이트 버퍼로만 읽고 그 마지막 바이트를 CRC로 검사하므로, 6바이트 `PingPacket`을 UDP로 보내면 CRC 불일치로 버려집니다(LoRa 경로는 길이에 맞춰 검사하므로 PING을 받습니다). 패킷 유형별로 읽는 길이와 CRC 위치를 나눠야 합니다

### 2. 발사대 쪽에 점화 인터록이 없다

- **현재**: ARM 키 확인은 조종 상자의 상태머신에만 있습니다. 발사대의 `CMD_IGNITION` 처리는 현재 상태를 보지 않고 **메인 밸브와 점화기를 바로 켭니다**

- **개선 방향**: 발사대에서도 `g_estadoAtual == STATE_ARMED`일 때만 점화를 받도록 한 줄 조건을 넣습니다. 명령이 잘못 들어와도 실행 쪽에서 한 번 더 막는 구조가 됩니다

### 3. 부팅 때 DHCP를 기다린다

- **현재**: 두 노드 모두 `Ethernet.begin(mac)`으로 먼저 DHCP를 시도하고, 실패하면 고정 IP로 넘어갑니다. Arduino Ethernet 라이브러리의 DHCP 기본 타임아웃은 **60초**입니다. 점대점 광 링크에는 DHCP 서버가 없으므로 **부팅할 때마다 최대 약 60초**를 기다리게 됩니다

- **개선 방향**: 처음부터 `Ethernet.begin(mac, ip, dns, gateway, subnet)`으로 고정 IP를 씁니다

→ Arduino Ethernet 라이브러리 `Ethernet.h`: <https://github.com/arduino-libraries/Ethernet/blob/master/src/Ethernet.h>

### 4. 링크 판단을 두 노드가 따로 한다

- **현재**: ESP2와 ESP3가 `COMM_CABLE` 전환을 따로 정합니다(조종 상자는 타이머와 송신 실패로, 발사대는 충전 중 타이머와 패킷이 들어온 경로로). 발사대는 자기가 유선 모드일 때만 UDP로 텔레메트리를 보내므로, 두 노드의 판단이 잠깐 어긋나면 응답이 다른 경로로 갈 수 있습니다

- **개선 방향**: 텔레메트리를 두 경로로 동시에 보내고 받는 쪽이 먼저 온 것을 쓰거나, 링크 상태를 패킷의 `comm_state` 필드로 서로 맞춥니다

### 5. 임시값과 문서

- **GPIO33 겹침**: 탱크 퍼지 NF 릴레이와 매니폴드 벤트 릴레이가 같은 핀으로 정의되어 있고, 코드에도 확인하라는 TODO가 있습니다

- **압력 변환식**: ADC 값을 0~100 bar로 단순 비례 변환하는 임시식입니다. 58 bar 자동 ABORT가 이 값에 기대므로 센서 보정이 먼저입니다

- **문서 표기**: CRC 주석은 "CRC-8/MAXIM"이라고 하지만 구현은 비반사형 다항식 0x31입니다. README는 Ethernet 라이브러리가 "ESP32 core에 포함"이라고 하지만, 실제로는 PlatformIO `lib_deps`로 따로 받습니다

- 저장소에 **LICENSE 파일이 없습니다**. 재사용 전 팀에 확인하는 것이 좋습니다

## FAQ

**Q1. 예비 링크에 왜 TCP가 아니라 UDP를 썼나요?**
저장소에 이유는 적혀 있지 않습니다. [추정] 명령 패킷이 3바이트로 작고, 무결성은 CRC8, 링크 감시는 와치독으로 앱이 직접 챙기는 구조라 연결 관리가 필요 없는 UDP가 단순했을 것으로 보입니다. 다만 UDP는 도착 확인이 없고 밸브 명령의 재전송도 없으므로, 유선 모드에서 밸브 명령은 **발사대가 돌려보내는 `valve_state`를 운영자가 LED로 보고 확인**하는 것이 사실상 유일한 확인 수단입니다. ARM·점화·ABORT 명령에는 이런 확인 경로가 없습니다.

**Q2. 광섬유 대신 긴 LAN 케이블을 쓰면 안 되나요?**
구리 Ethernet은 표준상 한 구간이 **100 m**까지입니다. 1km를 구리로 가려면 중간에 스위치를 여러 개 두어야 합니다. 미디어 컨버터 한 쌍과 광섬유는 그 거리를 한 번에 잇고, 양 끝을 전기적으로 분리하는 이점도 있습니다. CRT가 이 이유로 광섬유를 골랐는지는 저장소에 적혀 있지 않습니다.

**Q3. 두 링크가 동시에 살아 있으면 어떻게 되나요?**
평소에는 LoRa만 씁니다. 발사대는 LoRa와 UDP 양쪽을 모두 듣고 있어 어느 쪽으로 명령이 오든 실행하지만, 조종 상자는 LoRa가 실패했을 때만 UDP로 보냅니다. 유선 모드에서도 30초마다 LoRa PING을 보내고, PING이 성공하거나 발사대 텔레메트리가 LoRa로 들어오면 다시 LoRa로 돌아갑니다.

**Q4. 실물 보드 없이 시험할 수 있나요?**
네. 조종 상자 코드에 **시뮬레이션 모드**가 있고, Wokwi용 단일 파일 버전이 함께 들어 있습니다. 시리얼 키 입력으로 ARM 키, 밸브 스위치, ABORT, 링크 장애를 흉내 내며 상태머신과 LED 동작을 볼 수 있습니다. W5500과 LoRa의 실제 통신은 시뮬레이터에서 확인할 수 없습니다.

---

Source: https://maker.wiznet.io/scott/projects/-crt-software--trainee-2026-1/
