Senbrix 모니터링 화면 - 래더 4줄과 OutdoorTemp 워치 셀에 표시된 기온 19.9

래더가 인터넷의 날씨를 보고 팬을 켜고 끈다. 만든 것은 래더 네 줄과 C# 함수 하나다. C#이 날씨 API에서 서울 기온을 받아 D0에 써 넣고 래더는 그 값을 비교해서 판단한다.

준비물

구분내용
프로젝트PLC 트랙(래더 + C#) 프로젝트 하나
런타임 연결하단 연결 아이콘으로 런타임에 연결한 상태. 이 글의 화면은 PC에서 띄운 런타임에 배포한 것이다
인터넷런타임이 도는 장치에서 외부 주소로 나갈 수 있어야 한다
출력 주소팬 릴레이를 물릴 출력. 예제는 메인 보드 ZPi-IO8R의 OUT1인 P5

전체 구조

  • C#: 60초마다 날씨 API를 불러 기온을 D0.R에 쓴다.
  • 래더: 기온이 28℃ 이상이면 TooHot, 26℃ 이하면 Cooled를 켜고 이 두 비트로 팬을 켜고 끈다.

바깥 세계의 값을 가져오는 일은 C#이, 그 값으로 무엇을 할지 정하는 일은 래더가 맡는다. 둘 사이의 연결은 D0이라는 같은 메모리 한 칸이다.

1. 심볼

심볼 페이지 - FanOn P5, TooHot M10, Cooled M11, OutdoorTemp D0.R

이름주소설명
OutdoorTempD0.R바깥 기온(℃). C#이 쓴다
TooHotM10기온 ≥ 28
CooledM11기온 ≤ 26
FanOnP5팬 출력

기온에 소수가 있으니 주소 끝에 .R을 붙였다. .R은 float이고 워드 두 칸(D0, D1)을 쓴다. 래더와 C#에서 OutdoorTemp라는 같은 이름으로 19.9 같은 값을 그대로 읽고 쓴다.

2. 래더

래더 페이지 - 비교 접점으로 TooHot과 Cooled를 만들고 SETOUT과 RSTOUT으로 FanOn을 제어하는 4줄

줄접점출력
Too hotOutdoorTemp>=28fTooHot 코일
Cooled downOutdoorTemp<=26fCooled 코일
Fan onTooHotSETOUT(FanOn)
Fan offCooledRSTOUT(FanOn)

첫 두 줄은 접점 자리에 비교식을 그대로 쓴다. 조건이 맞으면 접점이 통하고 코일이 켜진다. 빌드하면 이 접점은 (Scan.D[0].R>=28f) 같은 C# 식이 된다.

아래 두 줄은 래치다. SETOUT은 한번 켠 출력을 유지하고 RSTOUT이 끈다. 켜는 기준(28℃)과 끄는 기준(26℃)을 다르게 둔 덕에 26~28℃ 사이에서는 어느 쪽도 켜지지 않고 팬이 직전 상태를 유지한다. 경계 근처에서 팬이 켜졌다 꺼졌다 반복하지 않는다.

시작 직후에는 OutdoorTemp가 0이라 Cooled가 먼저 켜지고, 팬은 꺼진 채로 시작한다.

3. C#

코드 페이지 - App.cs의 Loop에서 Open-Meteo를 호출해 OutdoorTemp에 쓰는 코드

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&current=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 워치 셀에 19.9가 표시되고 Cooled 코일과 RSTOUT 줄이 통전된 상태

'' 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()가 값을 가져오고 래더가 판단한다는 구조는 그대로다.