
래더가 인터넷의 날씨를 보고 팬을 켜고 끈다. 만든 것은 래더 네 줄과 C# 함수 하나다. C#이 날씨 API에서 서울 기온을 받아 D0에 써 넣고 래더는 그 값을 비교해서 판단한다.
준비물
| 구분 | 내용 |
|---|---|
| 프로젝트 | PLC 트랙(래더 + C#) 프로젝트 하나 |
| 런타임 연결 | 하단 연결 아이콘으로 런타임에 연결한 상태. 이 글의 화면은 PC에서 띄운 런타임에 배포한 것이다 |
| 인터넷 | 런타임이 도는 장치에서 외부 주소로 나갈 수 있어야 한다 |
| 출력 주소 | 팬 릴레이를 물릴 출력. 예제는 메인 보드 ZPi-IO8R의 OUT1인 P5 |
전체 구조
- C#: 60초마다 날씨 API를 불러 기온을
D0.R에 쓴다. - 래더: 기온이 28℃ 이상이면
TooHot, 26℃ 이하면Cooled를 켜고 이 두 비트로 팬을 켜고 끈다.
바깥 세계의 값을 가져오는 일은 C#이, 그 값으로 무엇을 할지 정하는 일은 래더가 맡는다. 둘 사이의 연결은 D0이라는 같은 메모리 한 칸이다.
1. 심볼

| 이름 | 주소 | 설명 |
|---|---|---|
OutdoorTemp | D0.R | 바깥 기온(℃). C#이 쓴다 |
TooHot | M10 | 기온 ≥ 28 |
Cooled | M11 | 기온 ≤ 26 |
FanOn | P5 | 팬 출력 |
기온에 소수가 있으니 주소 끝에 .R을 붙였다. .R은 float이고 워드 두 칸(D0, D1)을 쓴다. 래더와 C#에서 OutdoorTemp라는 같은 이름으로 19.9 같은 값을 그대로 읽고 쓴다.
2. 래더

| 줄 | 접점 | 출력 |
|---|---|---|
| Too hot | OutdoorTemp>=28f | TooHot 코일 |
| Cooled down | OutdoorTemp<=26f | Cooled 코일 |
| Fan on | TooHot | SETOUT(FanOn) |
| Fan off | Cooled | RSTOUT(FanOn) |
첫 두 줄은 접점 자리에 비교식을 그대로 쓴다. 조건이 맞으면 접점이 통하고 코일이 켜진다. 빌드하면 이 접점은 (Scan.D[0].R>=28f) 같은 C# 식이 된다.
아래 두 줄은 래치다. SETOUT은 한번 켠 출력을 유지하고 RSTOUT이 끈다. 켜는 기준(28℃)과 끄는 기준(26℃)을 다르게 둔 덕에 26~28℃ 사이에서는 어느 쪽도 켜지지 않고 팬이 직전 상태를 유지한다. 경계 근처에서 팬이 켜졌다 꺼졌다 반복하지 않는다.
시작 직후에는 OutdoorTemp가 0이라 Cooled가 먼저 켜지고, 팬은 꺼진 채로 시작한다.
3. C#

using System.Net.Http;
using System.Text.Json;
namespace Senbrix.Generated;
public partial class App : PlcApp
{
// Seoul. Open-Meteo needs no API key.
const string Url = "https://api.open-meteo.com/v1/forecast"
+ "?latitude=37.57&longitude=126.98¤t=temperature_2m";
static readonly HttpClient Http = new() { Timeout = TimeSpan.FromSeconds(5) };
long _last;
// Runs on its own task, so waiting on the network never stalls the ladder.
protected override void Loop()
{
if (Environment.TickCount64 - _last < 60_000) return; // refresh every 60 s
_last = Environment.TickCount64;
try
{
using var doc = JsonDocument.Parse(Http.GetStringAsync(Url).Result);
OutdoorTemp = doc.RootElement.GetProperty("current")
.GetProperty("temperature_2m").GetSingle();
}
catch { } // keep the last value; an unhandled exception would end this task
}
}
짚어 둘 곳이 네 군데 있다.
- 별도 작업으로 돈다.
Setup()과Loop()는 래더 사이클과 따로 도는 작업이다. 네트워크 응답을 기다려도 래더의 10ms 사이클은 밀리지 않는다. Loop()는 계속 반복해서 불린다. 그대로 두면 API를 쉬지 않고 호출하게 되니Environment.TickCount64로 60초가 지났는지만 확인하고 나머지는 바로 빠져나간다.- 실패하면 마지막 값을 쓴다. 연결이 끊기거나 5초 안에 응답이 없으면 예외가 난다.
try/catch로 삼키면OutdoorTemp는 직전 값 그대로다. - 예외가 밖으로 새면 안 된다.
Loop()에서 빠져나간 예외는 이 작업 전체를 조용히 끝낸다. 기온 갱신만 멈추고 래더는 그대로 도니까 알아채기도 어렵다.
4. 빌드·배포·모니터
Build → Deploy → Monitor 순서로 누른다. 상태 표시줄에 배포 완료와 실행 중이 뜨면 래더 위에 모니터가 올라온다.

'' OutdoorTemp 워치 셀을 래더 옆에 두면 값을 바로 볼 수 있다. 2026-10-02에 서울 기온은 19.8℃에서 시작해 몇 분 뒤 19.9℃로 바뀌었다. 우리가 60초마다 묻더라도 API가 내놓는 값은 15분 간격으로 갱신되니 기온이 매번 바뀌지는 않는다.
화면에서 볼 것은 세 가지다.
OutdoorTemp<=26f접점이 통하고Cooled코일이 켜져 있다.Fan off줄의RSTOUT(FanOn)이 통해 팬은 꺼진 상태다.TooHot줄은 꺼져 있다. 28℃가 아니니 당연하다.
팬이 켜지는 쪽을 보려면 기온이 28℃를 넘어야 한다. 지금 기온으로 보고 싶다면 켜는 값과 끄는 값을 둘 다 현재 기온 아래로 내린다(예: 18과 16). 그러면 TooHot이 켜지고 Cooled는 꺼져서 SETOUT(FanOn)이 통한다. 끄는 값만 남겨 두면 Fan off 줄이 아래에 있어서 팬이 켜지지 않으니 두 값을 같이 바꿔야 한다.
응용
- 위치 바꾸기:
Url의latitude와longitude만 바꾸면 된다. - 다른 값 쓰기: 습도나 풍속 같은 값도 같은 방식으로
D영역에 쓰고 래더는 비교만 바꾼다.Loop()가 값을 가져오고 래더가 판단한다는 구조는 그대로다.
댓글
닉네임만 입력하면 바로 댓글을 남길 수 있어요. Google/GitHub 로그인도 가능합니다.