C#을 이용한 부호 있는 값 지원이 가능한 PLC 통신 및 노트 관리 시스템 + 공정 실제구동 영상 , C#을 이용한 plc 구동 다양한 구현 예제 ( Enhanced PLC Communication and Note Management System with Signed Value Support in C# )

2024. 6. 18. 18:23카테고리 없음

https://www.microcontrollertips.com/programmable-logic-controllers-evolution-history/

 

Programmable Logic Controllers, Part 2: Evolution and history

 

www.microcontrollertips.com

들어가기 앞서 읽어주십시오!

plc의 근본적인 부분을 다룬 포스팅입니다!

음수단위 출력 구현완료

간단한 출력 예제 코드

 

 

Form1.Designer.cs

csharp코드 복사
namespace WindowsFormsApp1
{
    partial class Form1
    {
        private System.ComponentModel.IContainer components = null;
        private System.Windows.Forms.Label lblDeviceNumber;
        private System.Windows.Forms.TextBox tbDeviceNumber;
        private System.Windows.Forms.Label lblValue;
        private System.Windows.Forms.TextBox tbValue;
        private System.Windows.Forms.Button btnRead;
        private System.Windows.Forms.Button btnWrite;
        private System.Windows.Forms.TextBox tbStatus;
        private System.Windows.Forms.Label lblStatus;
        private System.Windows.Forms.TextBox tbLog;
        private System.Windows.Forms.Label lblLog;
        private System.Windows.Forms.Button btnReset;
        private System.Windows.Forms.TextBox tbNotes;
        private System.Windows.Forms.Label lblNotes;
        private System.Windows.Forms.ListBox lbLogDates;
        private System.Windows.Forms.Button btnSaveNotes;
        private System.Windows.Forms.Button btnDeleteNotes;
        private System.Windows.Forms.ComboBox cbDateInput;
        private System.Windows.Forms.Button btnAddDate;
        private System.Windows.Forms.Label lblMessage;
        private System.Windows.Forms.TextBox tbMessage;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.lblDeviceNumber = new System.Windows.Forms.Label();
            this.tbDeviceNumber = new System.Windows.Forms.TextBox();
            this.lblValue = new System.Windows.Forms.Label();
            this.tbValue = new System.Windows.Forms.TextBox();
            this.btnRead = new System.Windows.Forms.Button();
            this.btnWrite = new System.Windows.Forms.Button();
            this.tbStatus = new System.Windows.Forms.TextBox();
            this.lblStatus = new System.Windows.Forms.Label();
            this.tbLog = new System.Windows.Forms.TextBox();
            this.lblLog = new System.Windows.Forms.Label();
            this.btnReset = new System.Windows.Forms.Button();
            this.tbNotes = new System.Windows.Forms.TextBox();
            this.lblNotes = new System.Windows.Forms.Label();
            this.lbLogDates = new System.Windows.Forms.ListBox();
            this.btnSaveNotes = new System.Windows.Forms.Button();
            this.btnDeleteNotes = new System.Windows.Forms.Button();
            this.cbDateInput = new System.Windows.Forms.ComboBox();
            this.btnAddDate = new System.Windows.Forms.Button();
            this.lblMessage = new System.Windows.Forms.Label();
            this.tbMessage = new System.Windows.Forms.TextBox();
            this.SuspendLayout();

            //
            // lblDeviceNumber
            //
            this.lblDeviceNumber.AutoSize = true;
            this.lblDeviceNumber.ForeColor = System.Drawing.Color.White;
            this.lblDeviceNumber.Location = new System.Drawing.Point(30, 30);
            this.lblDeviceNumber.Name = "lblDeviceNumber";
            this.lblDeviceNumber.Size = new System.Drawing.Size(85, 13);
            this.lblDeviceNumber.TabIndex = 0;
            this.lblDeviceNumber.Text = "Device Number:";

            //
            // tbDeviceNumber
            //
            this.tbDeviceNumber.BackColor = System.Drawing.Color.Gray;
            this.tbDeviceNumber.ForeColor = System.Drawing.Color.White;
            this.tbDeviceNumber.Location = new System.Drawing.Point(130, 27);
            this.tbDeviceNumber.Name = "tbDeviceNumber";
            this.tbDeviceNumber.Size = new System.Drawing.Size(200, 20);
            this.tbDeviceNumber.TabIndex = 1;

            //
            // lblValue
            //
            this.lblValue.AutoSize = true;
            this.lblValue.ForeColor = System.Drawing.Color.White;
            this.lblValue.Location = new System.Drawing.Point(30, 60);
            this.lblValue.Name = "lblValue";
            this.lblValue.Size = new System.Drawing.Size(37, 13);
            this.lblValue.TabIndex = 2;
            this.lblValue.Text = "Value:";

            //
            // tbValue
            //
            this.tbValue.BackColor = System.Drawing.Color.Gray;
            this.tbValue.ForeColor = System.Drawing.Color.White;
            this.tbValue.Location = new System.Drawing.Point(130, 57);
            this.tbValue.Name = "tbValue";
            this.tbValue.Size = new System.Drawing.Size(200, 20);
            this.tbValue.TabIndex = 3;

            //
            // btnRead
            //
            this.btnRead.BackColor = System.Drawing.Color.Black;
            this.btnRead.ForeColor = System.Drawing.Color.White;
            this.btnRead.Location = new System.Drawing.Point(30, 90);
            this.btnRead.Name = "btnRead";
            this.btnRead.Size = new System.Drawing.Size(75, 23);
            this.btnRead.TabIndex = 4;
            this.btnRead.Text = "Read";
            this.btnRead.UseVisualStyleBackColor = false;
            this.btnRead.Click += new System.EventHandler(this.btnRead_Click);
            this.btnRead.MouseEnter += new System.EventHandler(this.btnRead_MouseEnter);
            this.btnRead.MouseLeave += new System.EventHandler(this.btnRead_MouseLeave);

            //
            // btnWrite
            //
            this.btnWrite.BackColor = System.Drawing.Color.Black;
            this.btnWrite.ForeColor = System.Drawing.Color.White;
            this.btnWrite.Location = new System.Drawing.Point(255, 90);
            this.btnWrite.Name = "btnWrite";
            this.btnWrite.Size = new System.Drawing.Size(75, 23);
            this.btnWrite.TabIndex = 5;
            this.btnWrite.Text = "Write";
            this.btnWrite.UseVisualStyleBackColor = false;
            this.btnWrite.Click += new System.EventHandler(this.btnWrite_Click);
            this.btnWrite.MouseEnter += new System.EventHandler(this.btnWrite_MouseEnter);
            this.btnWrite.MouseLeave += new System.EventHandler(this.btnWrite_MouseLeave);

            //
            // lblStatus
            //
            this.lblStatus.AutoSize = true;
            this.lblStatus.ForeColor = System.Drawing.Color.White;
            this.lblStatus.Location = new System.Drawing.Point(30, 130);
            this.lblStatus.Name = "lblStatus";
            this.lblStatus.Size = new System.Drawing.Size(40, 13);
            this.lblStatus.TabIndex = 6;
            this.lblStatus.Text = "Status:";

            //
            // tbStatus
            //
            this.tbStatus.BackColor = System.Drawing.Color.Gray;
            this.tbStatus.ForeColor = System.Drawing.Color.White;
            this.tbStatus.Location = new System.Drawing.Point(130, 127);
            this.tbStatus.Name = "tbStatus";
            this.tbStatus.ReadOnly = true;
            this.tbStatus.Size = new System.Drawing.Size(200, 20);
            this.tbStatus.TabIndex = 7;

            //
            // lblLog
            //
            this.lblLog.AutoSize = true;
            this.lblLog.ForeColor = System.Drawing.Color.White;
            this.lblLog.Location = new System.Drawing.Point(30, 160);
            this.lblLog.Name = "lblLog";
            this.lblLog.Size = new System.Drawing.Size(28, 13);
            this.lblLog.TabIndex = 8;
            this.lblLog.Text = "Log:";

            //
            // tbLog
            //
            this.tbLog.BackColor = System.Drawing.Color.Gray;
            this.tbLog.ForeColor = System.Drawing.Color.White;
            this.tbLog.Location = new System.Drawing.Point(130, 157);
            this.tbLog.Multiline = true;
            this.tbLog.Name = "tbLog";
            this.tbLog.ReadOnly = true;
            this.tbLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.tbLog.Size = new System.Drawing.Size(200, 100);
            this.tbLog.TabIndex = 9;

            //
            // btnReset
            //
            this.btnReset.BackColor = System.Drawing.Color.Black;
            this.btnReset.ForeColor = System.Drawing.Color.White;
            this.btnReset.Location = new System.Drawing.Point(130, 270);
            this.btnReset.Name = "btnReset";
            this.btnReset.Size = new System.Drawing.Size(75, 23);
            this.btnReset.TabIndex = 10;
            this.btnReset.Text = "Reset";
            this.btnReset.UseVisualStyleBackColor = false;
            this.btnReset.Click += new System.EventHandler(this.btnReset_Click);
            this.btnReset.MouseEnter += new System.EventHandler(this.btnReset_MouseEnter);
            this.btnReset.MouseLeave += new System.EventHandler(this.btnReset_MouseLeave);

            //
            // lblNotes
            //
            this.lblNotes.AutoSize = true;
            this.lblNotes.ForeColor = System.Drawing.Color.White;
            this.lblNotes.Location = new System.Drawing.Point(350, 30);
            this.lblNotes.Name = "lblNotes";
            this.lblNotes.Size = new System.Drawing.Size(38, 13);
            this.lblNotes.TabIndex = 11;
            this.lblNotes.Text = "Notes:";

            //
            // tbNotes
            //
            this.tbNotes.BackColor = System.Drawing.Color.Gray;
            this.tbNotes.ForeColor = System.Drawing.Color.White;
            this.tbNotes.Location = new System.Drawing.Point(350, 57);
            this.tbNotes.Multiline = true;
            this.tbNotes.Name = "tbNotes";
            this.tbNotes.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.tbNotes.Size = new System.Drawing.Size(200, 200);
            this.tbNotes.TabIndex = 12;

            //
            // lbLogDates
            //
            this.lbLogDates.BackColor = System.Drawing.Color.Gray;
            this.lbLogDates.ForeColor = System.Drawing.Color.White;
            this.lbLogDates.FormattingEnabled = true;
            this.lbLogDates.Location = new System.Drawing.Point(575, 57);
            this.lbLogDates.Name = "lbLogDates";
            this.lbLogDates.Size = new System.Drawing.Size(200, 199);
            this.lbLogDates.TabIndex = 13;
            this.lbLogDates.SelectedIndexChanged += new System.EventHandler(this.lbLogDates_SelectedIndexChanged);

            //
            // btnSaveNotes
            //
            this.btnSaveNotes.BackColor = System.Drawing.Color.Black;
            this.btnSaveNotes.ForeColor = System.Drawing.Color.White;
            this.btnSaveNotes.Location = new System.Drawing.Point(350, 270);
            this.btnSaveNotes.Name = "btnSaveNotes";
            this.btnSaveNotes.Size = new System.Drawing.Size(75, 23);
            this.btnSaveNotes.TabIndex = 14;
            this.btnSaveNotes.Text = "Save Notes";
            this.btnSaveNotes.UseVisualStyleBackColor = false;
            this.btnSaveNotes.Click += new System.EventHandler(this.btnSaveNotes_Click);
            this.btnSaveNotes.MouseEnter += new System.EventHandler(this.btnSaveNotes_MouseEnter);
            this.btnSaveNotes.MouseLeave += new System.EventHandler(this.btnSaveNotes_MouseLeave);

            //
            // btnDeleteNotes
            //
            this.btnDeleteNotes.BackColor = System.Drawing.Color.Black;
            this.btnDeleteNotes.ForeColor = System.Drawing.Color.White;
            this.btnDeleteNotes.Location = new System.Drawing.Point(575, 270);
            this.btnDeleteNotes.Name = "btnDeleteNotes";
            this.btnDeleteNotes.Size = new System.Drawing.Size(100, 23);
            this.btnDeleteNotes.TabIndex = 15;
            this.btnDeleteNotes.Text = "Delete Notes";
            this.btnDeleteNotes.UseVisualStyleBackColor = false;
            this.btnDeleteNotes.Click += new System.EventHandler(this.btnDeleteNotes_Click);
            this.btnDeleteNotes.MouseEnter += new System.EventHandler(this.btnDeleteNotes_MouseEnter);
            this.btnDeleteNotes.MouseLeave += new System.EventHandler(this.btnDeleteNotes_MouseLeave);

            //
            // cbDateInput
            //
            this.cbDateInput.BackColor = System.Drawing.Color.Gray;
            this.cbDateInput.ForeColor = System.Drawing.Color.White;
            this.cbDateInput.FormattingEnabled = true;
            this.cbDateInput.Location = new System.Drawing.Point(575, 27);
            this.cbDateInput.Name = "cbDateInput";
            this.cbDateInput.Size = new System.Drawing.Size(200, 21);
            this.cbDateInput.TabIndex = 16;

            //
            // btnAddDate
            //
            this.btnAddDate.BackColor = System.Drawing.Color.Black;
            this.btnAddDate.ForeColor = System.Drawing.Color.White;
            this.btnAddDate.Location = new System.Drawing.Point(575, 5);
            this.btnAddDate.Name = "btnAddDate";
            this.btnAddDate.Size = new System.Drawing.Size(75, 23);
            this.btnAddDate.TabIndex = 17;
            this.btnAddDate.Text = "Add Date";
            this.btnAddDate.UseVisualStyleBackColor = false;
            this.btnAddDate.Click += new System.EventHandler(this.btnAddDate_Click);
            this.btnAddDate.MouseEnter += new System.EventHandler(this.btnAddDate_MouseEnter);
            this.btnAddDate.MouseLeave += new System.EventHandler(this.btnAddDate_MouseLeave);

            //
            // lblMessage
            //
            this.lblMessage.AutoSize = true;
            this.lblMessage.ForeColor = System.Drawing.Color.White;
            this.lblMessage.Location = new System.Drawing.Point(350, 270);
            this.lblMessage.Name = "lblMessage";
            this.lblMessage.Size = new System.Drawing.Size(50, 13);
            this.lblMessage.TabIndex = 18;
            this.lblMessage.Text = "Message:";

            //
            // tbMessage
            //
            this.tbMessage.BackColor = System.Drawing.Color.Gray;
            this.tbMessage.ForeColor = System.Drawing.Color.White;
            this.tbMessage.Location = new System.Drawing.Point(410, 267);
            this.tbMessage.Name = "tbMessage";
            this.tbMessage.Size = new System.Drawing.Size(200, 20);
            this.tbMessage.TabIndex = 19;
            this.tbMessage.ReadOnly = true;

            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.Color.DimGray; // 폼 배경색 설정
            this.ClientSize = new System.Drawing.Size(800, 311); // 폼 크기 설정
            this.Controls.Add(this.tbMessage); // Message 텍스트 박스 추가
            this.Controls.Add(this.lblMessage); // Message 레이블 추가
            this.Controls.Add(this.btnAddDate); // Add Date 버튼 추가
            this.Controls.Add(this.cbDateInput); // Date Input 콤보박스 추가
            this.Controls.Add(this.btnDeleteNotes); // Notes 삭제 버튼 추가
            this.Controls.Add(this.btnSaveNotes); // Notes 저장 버튼 추가
            this.Controls.Add(this.lbLogDates); // 로그 날짜 리스트박스 추가
            this.Controls.Add(this.tbNotes); // Notes 텍스트 박스 추가
            this.Controls.Add(this.lblNotes); // Notes 레이블 추가
            this.Controls.Add(this.btnReset); // Reset 버튼 추가
            this.Controls.Add(this.tbLog); // Log 텍스트 박스 추가
            this.Controls.Add(this.lblLog); // Log 레이블 추가
            this.Controls.Add(this.tbStatus); // Status 텍스트 박스 추가
            this.Controls.Add(this.lblStatus); // Status 레이블 추가
            this.Controls.Add(this.btnWrite); // Write 버튼 추가
            this.Controls.Add(this.btnRead); // Read 버튼 추가
            this.Controls.Add(this.tbValue); // Value 텍스트 박스 추가
            this.Controls.Add(this.lblValue); // Value 레이블 추가
            this.Controls.Add(this.tbDeviceNumber); // Device Number 텍스트 박스 추가
            this.Controls.Add(this.lblDeviceNumber); // Device Number 레이블 추가
            this.Name = "Form1"; // 폼 이름 설정
            this.Text = "PLC Communication"; // 폼 제목 설정
            this.Load += new System.EventHandler(this.Form1_Load); // 폼 로드 이벤트 핸들러
            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing); // 폼 종료 이벤트 핸들러
            this.ResumeLayout(false);
            this.PerformLayout();
        }
    }
}

Form1.cs

csharp코드 복사
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using ActUtlTypeLib; // ActUtlTypeLib 라이브러리 사용

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private ActUtlType plc; // PLC 객체 선언
        private Dictionary<string, string> notes; // 노트 데이터를 관리할 딕셔너리
        private const string NotesDirectory = "Notes"; // 노트 저장 디렉토리

        public Form1()
        {
            InitializeComponent();
            plc = new ActUtlType(); // PLC 객체 초기화
            notes = new Dictionary<string, string>(); // 노트 딕셔너리 초기화

            // Notes 디렉토리 생성
            if (!Directory.Exists(NotesDirectory))
            {
                Directory.CreateDirectory(NotesDirectory);
            }

            // 날짜 콤보박스 초기화
            InitializeDateComboBox();

            // 로그 날짜 리스트박스 초기화
            LoadLogDates();
        }

        // 폼 로드 이벤트 핸들러
        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                plc.ActLogicalStationNumber = 1; // 논리 스테이션 번호 설정
                plc.Open(); // PLC 연결 열기
                tbStatus.Text = "Connected"; // 상태 텍스트 박스에 연결 상태 표시
                AppendLog("Connected to PLC successfully.");
            }
            catch (Exception ex)
            {
                tbStatus.Text = "Connection Error"; // 상태 텍스트 박스에 오류 표시
                AppendLog("Error connecting to PLC: " + ex.Message);
                MessageBox.Show("PLC 연결 오류: " + ex.Message);
            }
        }

        // Read 버튼 클릭 이벤트 핸들러
        private void btnRead_Click(object sender, EventArgs e)
        {
            try
            {
                string device = tbDeviceNumber.Text; // 텍스트 박스에서 디바이스 번호 가져오기
                int value;
                int result = plc.GetDevice(device, out value); // PLC에서 값 읽기
                if (result == 0)
                {
                    short signedValue = (short)value; // 부호 있는 16비트 정수로 변환
                    tbValue.Text = signedValue.ToString(); // 읽은 값 텍스트 박스에 표시
                    AppendLog($"Read from {device}: {signedValue}");
                }
                else
                {
                    AppendLog($"Error reading from {device}: {result}");
                    MessageBox.Show("PLC에서 읽기 오류: " + result.ToString());
                }
            }
            catch (Exception ex)
            {
                AppendLog("Exception: " + ex.Message);
                MessageBox.Show("예외: " + ex.Message);
            }
        }

        // Write 버튼 클릭 이벤트 핸들러
        private void btnWrite_Click(object sender, EventArgs e)
        {
            try
            {
                string device = tbDeviceNumber.Text; // 텍스트 박스에서 디바이스 번호 가져오기
                short signedValue = short.Parse(tbValue.Text); // 텍스트 박스에서 값 가져오기
                int value = signedValue; // 부호 있는 16비트 정수를 int로 변환
                int result = plc.SetDevice(device, value); // PLC에 값 쓰기
                if (result != 0)
                {
                    AppendLog($"Error writing to {device}: {result}");
                    MessageBox.Show("PLC에 쓰기 오류: " + result.ToString());
                }
                else
                {
                    AppendLog($"Written to {device}: {signedValue}");
                }
            }
            catch (Exception ex)
            {
                AppendLog("Exception: " + ex.Message);
                MessageBox.Show("예외: " + ex.Message);
            }
        }

        // 폼 종료 이벤트 핸들러
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            plc.Close(); // PLC 연결 닫기
        }

        // 로그 추가 메서드
        private void AppendLog(string message)
        {
            string logEntry = $"{DateTime.Now}: {message}";
            tbLog.AppendText($"{logEntry}\\r\\n");

            // 로그 날짜 리스트박스 업데이트
            string currentDate = DateTime.Now.ToShortDateString();
            if (!lbLogDates.Items.Contains(currentDate))
            {
                lbLogDates.Items.Add(currentDate);
            }
        }

        // Read 버튼 호버 효과
        private void btnRead_MouseEnter(object sender, EventArgs e)
        {
            btnRead.BackColor = System.Drawing.Color.DarkGray; // 마우스가 버튼 위에 있을 때 색상 변경
        }

        private void btnRead_MouseLeave(object sender, EventArgs e)
        {
            btnRead.BackColor = System.Drawing.Color.Black; // 마우스가 버튼을 벗어났을 때 색상 복원
        }

        // Write 버튼 호버 효과
        private void btnWrite_MouseEnter(object sender, EventArgs e)
        {
            btnWrite.BackColor = System.Drawing.Color.DarkGray; // 마우스가 버튼 위에 있을 때 색상 변경
        }

        private void btnWrite_MouseLeave(object sender, EventArgs e)
        {
            btnWrite.BackColor = System.Drawing.Color.Black; // 마우스가 버튼을 벗어났을 때 색상 복원
        }

        // Reset 버튼 클릭 이벤트 핸들러
        private void btnReset_Click(object sender, EventArgs e)
        {
            try
            {
                plc.Close();
                plc.Open();
                tbStatus.Text = "Reconnected";
                AppendLog("PLC connection reset successfully.");
            }
            catch (Exception ex)
            {
                tbStatus.Text = "Reset Error";
                AppendLog("Error resetting PLC connection: " + ex.Message);
                MessageBox.Show("PLC 연결 재설정 오류: " + ex.Message);
            }
        }

        // Reset 버튼 호버 효과
        private void btnReset_MouseEnter(object sender, EventArgs e)
        {
            btnReset.BackColor = System.Drawing.Color.DarkGray; // 마우스가 버튼 위에 있을 때 색상 변경
        }

        private void btnReset_MouseLeave(object sender, EventArgs e)
        {
            btnReset.BackColor = System.Drawing.Color.Black; // 마우스가 버튼을 벗어났을 때 색상 복원
        }

        // Notes 저장 버튼 클릭 이벤트 핸들러
        private void btnSaveNotes_Click(object sender, EventArgs e)
        {
            string selectedDate = lbLogDates.SelectedItem?.ToString();
            if (!string.IsNullOrEmpty(selectedDate))
            {
                notes[selectedDate] = tbNotes.Text;
                SaveNotesToFile(selectedDate);
                AppendLog($"Notes saved for {selectedDate}");
            }
            else
            {
                MessageBox.Show("Please select a date from the log to save notes.");
            }
        }

        // Notes 저장 버튼 호버 효과
        private void btnSaveNotes_MouseEnter(object sender, EventArgs e)
        {
            btnSaveNotes.BackColor = System.Drawing.Color.DarkGray; // 마우스가 버튼 위에 있을 때 색상 변경
        }

        private void btnSaveNotes_MouseLeave(object sender, EventArgs e)
        {
            btnSaveNotes.BackColor = System.Drawing.Color.Black; // 마우스가 버튼을 벗어났을 때 색상 복원
        }

        // Notes 삭제 버튼 클릭 이벤트 핸들러
        private void btnDeleteNotes_Click(object sender, EventArgs e)
        {
            string selectedDate = lbLogDates.SelectedItem?.ToString();
            if (!string.IsNullOrEmpty(selectedDate))
            {
                if (notes.ContainsKey(selectedDate))
                {
                    notes.Remove(selectedDate);
                    DeleteNotesFromFile(selectedDate);
                    tbNotes.Clear();
                    tbMessage.Clear();
                    AppendLog($"Notes deleted for {selectedDate}");
                }
                else
                {
                    MessageBox.Show("No notes found for the selected date.");
                }
            }
            else
            {
                MessageBox.Show("Please select a date from the log to delete notes.");
            }
        }

        // Notes 삭제 버튼 호버 효과
        private void btnDeleteNotes_MouseEnter(object sender, EventArgs e)
        {
            btnDeleteNotes.BackColor = System.Drawing.Color.DarkGray; // 마우스가 버튼 위에 있을 때 색상 변경
        }

        private void btnDeleteNotes_MouseLeave(object sender, EventArgs e)
        {
            btnDeleteNotes.BackColor = System.Drawing.Color.Black; // 마우스가 버튼을 벗어났을 때 색상 복원
        }

        // 로그 날짜 리스트박스 선택 이벤트 핸들러
        private void lbLogDates_SelectedIndexChanged(object sender, EventArgs e)
        {
            string selectedDate = lbLogDates.SelectedItem?.ToString();
            if (!string.IsNullOrEmpty(selectedDate))
            {
                if (notes.ContainsKey(selectedDate))
                {
                    tbNotes.Text = notes[selectedDate];
                    tbMessage.Text = $"{selectedDate}: {notes[selectedDate]}";
                    AppendLog($"Notes loaded for {selectedDate}");
                }
                else
                {
                    tbNotes.Clear();
                    tbMessage.Clear();
                    AppendLog($"No notes found for {selectedDate}");
                }
            }
        }

        // 로그 날짜 리스트박스 초기화 메서드
        private void LoadLogDates()
        {
            lbLogDates.Items.Clear();
            foreach (string filePath in Directory.GetFiles(NotesDirectory, "*.txt"))
            {
                string date = Path.GetFileNameWithoutExtension(filePath);
                lbLogDates.Items.Add(date);
                notes[date] = File.ReadAllText(filePath);
            }
        }

        // 노트를 파일에 저장하는 메서드
        private void SaveNotesToFile(string date)
        {
            string filePath = Path.Combine(NotesDirectory, $"{date}.txt");
            File.WriteAllText(filePath, notes[date]);
        }

        // 노트를 파일에서 삭제하는 메서드
        private void DeleteNotesFromFile(string date)
        {
            string filePath = Path.Combine(NotesDirectory, $"{date}.txt");
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
        }

        // 날짜 콤보박스 초기화 메서드
        private void InitializeDateComboBox()
        {
            cbDateInput.Items.Clear();
            for (int i = 0; i <= 7; i++)
            {
                DateTime date = DateTime.Today.AddDays(-i);
                cbDateInput.Items.Add(date.ToShortDateString());
            }
            cbDateInput.SelectedIndex = 0;
        }

        // 날짜 추가 버튼 클릭 이벤트 핸들러
        private void btnAddDate_Click(object sender, EventArgs e)
        {
            string selectedDate = cbDateInput.SelectedItem.ToString();
            if (!lbLogDates.Items.Contains(selectedDate))
            {
                lbLogDates.Items.Add(selectedDate);
                AppendLog($"Date added: {selectedDate}");
            }
            else
            {
                MessageBox.Show("The date already exists in the list.");
            }
        }

        // 날짜 추가 버튼 호버 효과
        private void btnAddDate_MouseEnter(object sender, EventArgs e)
        {
            btnAddDate.BackColor = System.Drawing.Color.DarkGray; // 마우스가 버튼 위에 있을 때 색상 변경
        }

        private void btnAddDate_MouseLeave(object sender, EventArgs e)
        {
            btnAddDate.BackColor = System.Drawing.Color.Black; // 마우스가 버튼을 벗어났을 때 색상 복원
        }
    }
}

주요 구현 부분 리뷰 및 포스팅 형식 정리

1. 프로젝트 개요

PLC (Programmable Logic Controller)와의 통신을 위한 C# WinForms 애플리케이션을 구현했습니다.

이 애플리케이션은 PLC와 연결하여 데이터를 읽고 쓰는 기능을 제공합니다.

또한, 날짜별로 노트를 저장하고 관리할 수 있는 기능도 포함되어 있습니다.

2. 주요 기능

2.1 PLC 데이터 읽기 및 쓰기

  • PLC 데이터 읽기: PLC에서 데이터를 읽어와서 텍스트 박스에 표시합니다. 데이터를 읽을 때, 음수 값을 올바르게 처리하기 위해 부호 있는 16비트 정수로 변환합니다.
  • PLC 데이터 쓰기: 텍스트 박스에 입력된 값을 PLC에 씁니다. 음수 값도 올바르게 처리하도록 부호 있는 16비트 정수로 변환합니다.

2.2 노트 관리

  • 노트 저장: 날짜별로 노트를 작성하고 저장할 수 있습니다. 저장된 노트는 텍스트 파일로 관리됩니다.
  • 노트 삭제: 특정 날짜의 노트를 삭제할 수 있습니다.
  • 날짜 입력: 날짜 입력을 위한 콤보박스를 사용하여 사용자가 직접 날짜를 입력하지 않아도 선택할 수 있도록 했습니다. 콤보박스에는 과거 7일 전부터 현재까지의 날짜만 포함됩니다.

3. 구현 상세

3.1 PLC 데이터 읽기

csharp코드 복사
private void btnRead_Click(object sender, EventArgs e)
{
    try
    {
        string device = tbDeviceNumber.Text; // 텍스트 박스에서 디바이스 번호 가져오기
        int value;
        int result = plc.GetDevice(device, out value); // PLC에서 값 읽기
        if (result == 0)
        {
            short signedValue = (short)value; // 부호 있는 16비트 정수로 변환
            tbValue.Text = signedValue.ToString(); // 읽은 값 텍스트 박스에 표시
            AppendLog($"Read from {device}: {signedValue}");
        }
        else
        {
            AppendLog($"Error reading from {device}: {result}");
            MessageBox.Show("PLC에서 읽기 오류: " + result.ToString());
        }
    }
    catch (Exception ex)
    {
        AppendLog("Exception: " + ex.Message);
        MessageBox.Show("예외: " + ex.Message);
    }
}

3.2 PLC 데이터 쓰기

csharp코드 복사
private void btnWrite_Click(object sender, EventArgs e)
{
    try
    {
        string device = tbDeviceNumber.Text; // 텍스트 박스에서 디바이스 번호 가져오기
        short signedValue = short.Parse(tbValue.Text); // 텍스트 박스에서 값 가져오기
        int value = signedValue; // 부호 있는 16비트 정수를 int로 변환
        int result = plc.SetDevice(device, value); // PLC에 값 쓰기
        if (result != 0)
        {
            AppendLog($"Error writing to {device}: {result}");
            MessageBox.Show("PLC에 쓰기 오류: " + result.ToString());
        }
        else
        {
            AppendLog($"Written to {device}: {signedValue}");
        }
    }
    catch (Exception ex)
    {
        AppendLog("Exception: " + ex.Message);
        MessageBox.Show("예외: " + ex.Message);
    }
}

3.3 노트 관리

csharp코드 복사
// 노트 저장 버튼 클릭 이벤트 핸들러
private void btnSaveNotes_Click(object sender, EventArgs e)
{
    string selectedDate = lbLogDates.SelectedItem?.ToString();
    if (!string.IsNullOrEmpty(selectedDate))
    {
        notes[selectedDate] = tbNotes.Text;
        SaveNotesToFile(selectedDate);
        AppendLog($"Notes saved for {selectedDate}");
    }
    else
    {
        MessageBox.Show("Please select a date from the log to save notes.");
    }
}

// 노트 삭제 버튼 클릭 이벤트 핸들러
private void btnDeleteNotes_Click(object sender, EventArgs e)
{
    string selectedDate = lbLogDates.SelectedItem?.ToString();
    if (!string.IsNullOrEmpty(selectedDate))
    {
        if (notes.ContainsKey(selectedDate))
        {
            notes.Remove(selectedDate);
            DeleteNotesFromFile(selectedDate);
            tbNotes.Clear();
            tbMessage.Clear();
            AppendLog($"Notes deleted for {selectedDate}");
        }
        else
        {
            MessageBox.Show("No notes found for the selected date.");
        }
    }
    else
    {
        MessageBox.Show("Please select a date from the log to delete notes.");
    }
}

4. 주요 개선 사항

  • 음수 값 처리: PLC에서 값을 읽고 쓸 때 부호 있는 16비트 정수로 변환하여 음수 값도 올바르게 처리하도록 개선했습니다.
  • 날짜 입력 편의성: 날짜 입력을 콤보박스로 변경하여 사용자가 직접 입력하지 않아도 되도록 했습니다. 콤보박스에는 과거 7일 전부터 현재까지의 날짜만 포함됩니다.

이 프로젝트는 PLC와의 통신을 손쉽게 할 수 있는 C# WinForms 애플리케이션을 구현한 사례입니다. 음수 값 처리가 필요한 경우 부호 있는 16비트 정수로 변환하여 처리함으로써 정확한 데이터를 다룰 수 있습니다.

추가적으로, 날짜별 노트 관리 기능을 통해 사용자의 기록을 효과적으로 관리할 수 있습니다.


간단한 구현 예제입니다
using System;
using System.Windows.Forms;
using ActUtlTypeLib;

namespace AutomationTest
{
    public partial class MainForm : Form
    {
        private ActUtlType plc;

        public MainForm()
        {
            InitializeComponent();
            InitializePLC();
        }

        private void InitializePLC()
        {
            plc = new ActUtlType();
            plc.ActLogicalStationNumber = 2; // 스테이션 넘버 설정

            int result = plc.Open();
            if (result != 0)
            {
                MessageBox.Show("PLC 연결 실패: " + result.ToString());
            }
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            plc.Close(); // 폼이 닫힐 때 PLC 연결 종료
        }

        private bool ReadBit(string address)
        {
            int value = 0;
            int result = plc.GetDevice(address, out value);
            if (result == 0)
            {
                return value != 0;
            }
            else
            {
                MessageBox.Show("비트 읽기 실패: " + result.ToString());
                return false;
            }
        }

        private void WriteBit(string address, bool value)
        {
            int result = plc.SetDevice(address, value ? 1 : 0);
            if (result != 0)
            {
                MessageBox.Show("비트 쓰기 실패: " + result.ToString());
            }
        }

        private void UpdateStatusLabel(Label label, bool status)
        {
            label.Text = status ? "ON" : "OFF";
        }

        private void btnToggleSupply_Click(object sender, EventArgs e)
        {
            // 공급 실린더 상태 읽기
            bool isSupplyOn = ReadBit("Y20");
            // 공급 실린더 상태 토글
            WriteBit("Y20", !isSupplyOn);
            // 상태 레이블 업데이트
            UpdateStatusLabel(lblSupplyStatus, !isSupplyOn);
        }

        private void btnReadSensor_Click(object sender, EventArgs e)
        {
            // 센서 값 읽기
            bool sensorValue = ReadBit("X08");
            // 상태 레이블 업데이트
            UpdateStatusLabel(lblSensorStatus, sensorValue);
        }

        private void btnControlConveyor_Click(object sender, EventArgs e)
        {
            // 컨베이어 상태 읽기
            bool isConveyorOn = ReadBit("Y28");
            // 컨베이어 상태 토글
            WriteBit("Y28", !isConveyorOn);
            // 상태 레이블 업데이트
            UpdateStatusLabel(lblConveyorStatus, !isConveyorOn);
        }

        private void btnStartProcessing_Click(object sender, EventArgs e)
        {
            // 공급 시작
            WriteBit("Y20", true);
            System.Threading.Thread.Sleep(1000);
            // 공급 완료
            WriteBit("Y21", true);

            // 가공 시작
            WriteBit("Y26", true);
            System.Threading.Thread.Sleep(2000);
            // 가공 완료
            WriteBit("Y27", true);

            // 상태 레이블 업데이트
            UpdateStatusLabel(lblProcessingStatus, true);
        }

        private void btnCheckError_Click(object sender, EventArgs e)
        {
            // 오류 상태 읽기
            bool isError = ReadBit("X0A");
            if (isError)
            {
                // 오류 발생 시 적색 램프 켜기
                WriteBit("Y30", true);
            }
            else
            {
                // 오류 없음
                WriteBit("Y30", false);
            }
            // 상태 레이블 업데이트
            UpdateStatusLabel(lblErrorStatus, isError);
        }

        private void btnInitialize_Click(object sender, EventArgs e)
        {
            // 모든 구성 요소 초기화
            WriteBit("Y20", false); // 공급 끄기
            WriteBit("Y21", false); // 공급 완료 끄기
            WriteBit("Y26", false); // 가공 끄기
            WriteBit("Y27", false); // 가공 완료 끄기
            // 상태 레이블 업데이트
            UpdateStatusLabel(lblInitializationStatus, true);
        }
    }
}

 

코드 요약

  1. PLC 초기화 및 연결
    • PLC 객체를 초기화하고, 스테이션 넘버를 설정한 후 PLC와의 연결을 시도합니다.
    • 연결 실패 시, 에러 메시지를 표시합니다.
  2. csharp코드 복사 private void InitializePLC() { plc = new ActUtlType(); plc.ActLogicalStationNumber = 2; // 스테이션 넘버 설정 int result = plc.Open(); if (result != 0) { MessageBox.Show("PLC 연결 실패: " + result.ToString()); } }
  3. 폼 종료 시 PLC 연결 종료
    • 폼이 종료될 때 PLC 연결을 종료하여 리소스를 해제합니다.
  4. csharp코드 복사 private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { plc.Close(); // 폼이 닫힐 때 PLC 연결 종료 }
  5. 비트 읽기
    • 지정된 주소의 비트를 읽고, 읽기 성공 시 값을 반환합니다.
    • 읽기 실패 시 에러 메시지를 표시합니다.
  6. csharp코드 복사 private bool ReadBit(string address) { int value = 0; int result = plc.GetDevice(address, out value); if (result == 0) { return value != 0; } else { MessageBox.Show("비트 읽기 실패: " + result.ToString()); return false; } }
  7. 비트 쓰기
    • 지정된 주소의 비트 값을 설정합니다.
    • 쓰기 실패 시 에러 메시지를 표시합니다.
  8. csharp코드 복사 private void WriteBit(string address, bool value) { int result = plc.SetDevice(address, value ? 1 : 0); if (result != 0) { MessageBox.Show("비트 쓰기 실패: " + result.ToString()); } }
  9. 상태 레이블 업데이트
    • 특정 레이블의 텍스트를 상태에 따라 업데이트합니다.
  10. csharp코드 복사 private void UpdateStatusLabel(Label label, bool status) { label.Text = status ? "ON" : "OFF"; }
  11. 버튼 이벤트 핸들러
    • 공급 실린더 상태 토글
      • 공급 실린더의 현재 상태를 읽고, 상태를 토글한 후 레이블을 업데이트합니다.
    • csharp코드 복사 private void btnToggleSupply_Click(object sender, EventArgs e) { bool isSupplyOn = ReadBit("Y20"); WriteBit("Y20", !isSupplyOn); UpdateStatusLabel(lblSupplyStatus, !isSupplyOn); }
    • 센서 값 읽기
      • 센서의 값을 읽고, 레이블을 업데이트합니다.
    • csharp코드 복사 private void btnReadSensor_Click(object sender, EventArgs e) { bool sensorValue = ReadBit("X08"); UpdateStatusLabel(lblSensorStatus, sensorValue); }
    • 컨베이어 상태 토글
      • 컨베이어의 현재 상태를 읽고, 상태를 토글한 후 레이블을 업데이트합니다.
    • csharp코드 복사 private void btnControlConveyor_Click(object sender, EventArgs e) { bool isConveyorOn = ReadBit("Y28"); WriteBit("Y28", !isConveyorOn); UpdateStatusLabel(lblConveyorStatus, !isConveyorOn); }
    • 공정 시작
      • 공급과 가공 공정을 순차적으로 시작하고, 레이블을 업데이트합니다.
    • csharp코드 복사 private void btnStartProcessing_Click(object sender, EventArgs e) { WriteBit("Y20", true); System.Threading.Thread.Sleep(1000); WriteBit("Y21", true); WriteBit("Y26", true); System.Threading.Thread.Sleep(2000); WriteBit("Y27", true); UpdateStatusLabel(lblProcessingStatus, true); }
    • 오류 상태 확인
      • 오류 상태를 확인하고, 오류 발생 시 적색 램프를 켭니다. 레이블을 업데이트합니다.
    • csharp코드 복사 private void btnCheckError_Click(object sender, EventArgs e) { bool isError = ReadBit("X0A"); WriteBit("Y30", isError); UpdateStatusLabel(lblErrorStatus, isError); }
    • 초기화
      • 모든 구성 요소를 초기화하고, 레이블을 업데이트합니다.
    • csharp코드 복사 private void btnInitialize_Click(object sender, EventArgs e) { WriteBit("Y20", false); WriteBit("Y21", false); WriteBit("Y26", false); WriteBit("Y27", false); UpdateStatusLabel(lblInitializationStatus, true); }

결론

이 코드는 PLC와의 통신을 통해 다양한 장비를 제어하고 상태를 모니터링하는 기능을 제공합니다. 각 기능은 명확하게 분리되어 있으며, 쉽게 확장할 수 있도록 구성되어 있습니다.

 


C#으로 공정 코드를 작성할 때와 PLC 래더로 공정 코드를 작성할 때의 차이점
프로그래밍 언어 고급 프로그래밍 언어(C#) 저급 프로그래밍 언어(래더 다이어그램)
개발 환경 Visual Studio 같은 통합 개발 환경(IDE) PLC 제조사 제공 소프트웨어(예: GX Works)
코드 구조 객체 지향 프로그래밍 절차적 프로그래밍
가독성 텍스트 기반, 코드 가독성 높음 그래픽 기반, 복잡한 논리 가독성 낮음
디버깅 고급 디버깅 도구 사용 가능 PLC에 내장된 디버깅 도구 사용
유연성 다양한 라이브러리 및 API 사용 가능 PLC 기능에 한정됨
실시간 처리 실시간 처리에 부적합할 수 있음 실시간 처리에 최적화
확장성 확장 및 통합 용이 특정 PLC 모델에 종속적
성능 고성능 하드웨어 요구 PLC의 안정적인 실시간 성능
유지보수 복잡한 유지보수 가능 유지보수 간단
디버깅 및 테스트 가상 환경에서 다양한 테스트 가능 실제 하드웨어에서 테스트 필요
학습 곡선 일반 프로그래머에게 익숙한 환경 자동화 기술자에게 익숙한 환경
비용 개발 환경 구축 비용이 저렴 PLC 및 관련 소프트웨어 비용 높음
제어 논리 구현 고급 제어 논리 구현 가능 기본 제어 논리에 최적화
통신 다양한 통신 프로토콜 지원 특정 통신 프로토콜에 종속적

 


Q1: C#으로 공정 코드를 작성할 때 통신 프로토콜의 선택이 중요한 이유는 무엇인가요?

C#으로 공정 코드를 작성할 때 통신 프로토콜의 선택이 중요한 이유는 다음과 같습니다:

  1. 호환성: 다양한 PLC와 장치들이 서로 다른 통신 프로토콜을 사용합니다. 올바른 프로토콜을 선택하지 않으면 장치 간의 통신이 불가능할 수 있습니다.
  2. 성능: 일부 통신 프로토콜은 높은 데이터 전송 속도와 낮은 지연 시간을 제공합니다. 실시간 데이터 처리가 필요한 공정에서는 성능이 중요한 요소입니다.
  3. 안정성: 안정적인 통신 프로토콜을 사용하면 데이터 손실이나 통신 오류를 최소화할 수 있습니다. 이는 공정의 신뢰성과 안전성에 직접적인 영향을 미칩니다.
  4. 보안: 통신 프로토콜에 따라 데이터 암호화 및 인증 기능이 달라질 수 있습니다. 민감한 데이터가 오가는 공정에서는 보안이 매우 중요합니다.
  5. 유지보수: 표준화된 통신 프로토콜을 사용하면 시스템 확장이나 유지보수가 용이합니다. 비표준 프로토콜을 사용하면 유지보수 시 복잡도가 증가합니다.

Q2: PLC 래더 프로그램의 유지보수 장점은 무엇이며, 이것이 중요한 이유는 무엇인가요?

PLC 래더 프로그램의 유지보수 장점과 그 중요성은 다음과 같습니다:

  1. 직관적인 그래픽 표현: 래더 다이어그램은 전기 회로와 유사한 형태로, 직관적이고 이해하기 쉽습니다. 이는 유지보수 담당자가 프로그램 로직을 쉽게 이해하고 수정할 수 있게 합니다.
  2. 표준화된 언어: 래더 다이어그램은 IEC 61131-3 표준의 일부로, 다양한 PLC 제조사 간에 호환성이 높습니다. 표준화된 언어를 사용하면 다른 시스템으로의 이식성이 높아집니다.
  3. 디버깅 편의성: 래더 프로그램은 각 단계별로 디버깅이 가능하여 문제 발생 시 원인을 쉽게 파악할 수 있습니다. 이는 문제 해결 시간을 단축시킵니다.
  4. 신뢰성: 래더 프로그램은 실시간 제어에 최적화되어 있으며, 공정 중단 없이 안정적으로 동작합니다. 이는 공정의 신뢰성을 높이는 중요한 요소입니다.
  5. 간단한 수정: 공정 변경 시 래더 프로그램의 수정이 간단하여, 공정 변화에 빠르게 대응할 수 있습니다.

Q3: C#과 PLC 래더 프로그램을 통합하여 공정 제어 시스템을 구축할 때 고려해야 할 주요 사항은 무엇인가요?

C#과 PLC 래더 프로그램을 통합하여 공정 제어 시스템을 구축할 때 고려해야 할 주요 사항은 다음과 같습니다:

  1. 통신 인터페이스: C# 프로그램과 PLC 간의 안정적인 통신을 위해 적절한 통신 인터페이스(예: OPC, Modbus, TCP/IP)를 선택해야 합니다.
  2. 실시간 처리 요구 사항: PLC는 실시간 처리가 필수인 반면, C# 애플리케이션은 덜 실시간적입니다. 따라서 실시간 데이터 처리를 위한 적절한 설계를 해야 합니다.
  3. 데이터 동기화: C#과 PLC 간의 데이터 동기화를 효율적으로 관리하여 데이터 일관성을 유지해야 합니다.
  4. 에러 처리: 통합 시스템에서 발생할 수 있는 통신 오류나 데이터 불일치 문제를 처리하기 위한 에러 처리 메커니즘을 설계해야 합니다.
  5. 보안: 두 시스템 간의 데이터 전송 보안을 강화하기 위한 암호화와 인증 방법을 적용해야 합니다.
  6. 확장성: 시스템의 확장성을 고려하여 설계함으로써 향후 공정 변화나 시스템 확장에 유연하게 대응할 수 있어야 합니다.
  7. 사용자 인터페이스: C# 애플리케이션의 UI를 통해 공정 데이터를 직관적으로 표시하고 제어할 수 있도록 설계해야 합니다.
  8. 테스트 및 검증: 통합 시스템의 모든 기능을 철저히 테스트하고 검증하여 안정성을 확보해야 합니다.

PLC에서 데이터를 받아와서 동작을 구현하는 다양한 방식 중, 일반적으로 많이 사용되는 순서대로 정리해보겠습니다.

사용 빈도는 일반적인 산업 자동화 환경에서의 실무 경험과 구현의 편의성, 효율성을 고려한 것입니다.

1. 폴링 방식 (Polling)

가장 많이 사용되는 방식으로, 주기적으로 PLC의 데이터를 읽어와서 처리합니다. 구현이 비교적 간단하고 안정적입니다.

csharp코드 복사
using System;
using System.Windows.Forms;
using System.Timers;
using ActUtlTypeLib;

namespace PLC_Polling
{
    public partial class MainForm : Form
    {
        private ActUtlType plc;
        private Timer timer;

        public MainForm()
        {
            InitializeComponent();
            InitializePLC();
            InitializeTimer();
        }

        private void InitializePLC()
        {
            plc = new ActUtlType();
            plc.ActLogicalStationNumber = 1;
            plc.Open();
        }

        private void InitializeTimer()
        {
            timer = new Timer(1000); // 1초 주기
            timer.Elapsed += TimerElapsed;
            timer.Start();
        }

        private void TimerElapsed(object sender, ElapsedEventArgs e)
        {
            int value = 0;
            plc.GetDevice("D100", out value);
            Console.WriteLine($"Sensor Value: {value}");

            if (value == 0)
            {
                plc.SetDevice("Y0", 1); // 경고등 켜기
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            timer.Stop();
            plc.Close();
            base.OnFormClosing(e);
        }
    }
}

2. 이벤트 기반 방식 (Event-based)

변경된 데이터에 대한 즉각적인 반응이 필요할 때 사용합니다. PLC 자체가 이벤트를 지원하지 않는 경우, 폴링 방식과 조합하여 사용할 수 있습니다.

csharp코드 복사
using System;
using System.Windows.Forms;
using System.Timers;
using ActUtlTypeLib;

namespace PLC_EventBased
{
    public partial class MainForm : Form
    {
        private ActUtlType plc;
        private int previousValue;
        private Timer timer;

        public MainForm()
        {
            InitializeComponent();
            InitializePLC();
            InitializeTimer();
        }

        private void InitializePLC()
        {
            plc = new ActUtlType();
            plc.ActLogicalStationNumber = 1;
            plc.Open();
            plc.GetDevice("D100", out previousValue);
        }

        private void InitializeTimer()
        {
            timer = new Timer(1000); // 1초 주기
            timer.Elapsed += TimerElapsed;
            timer.Start();
        }

        private void TimerElapsed(object sender, ElapsedEventArgs e)
        {
            int currentValue = 0;
            plc.GetDevice("D100", out currentValue);

            if (currentValue != previousValue)
            {
                Console.WriteLine($"Sensor Value Changed: {currentValue}");
                OnSensorValueChanged(currentValue);
                previousValue = currentValue;
            }
        }

        private void OnSensorValueChanged(int newValue)
        {
            if (newValue == 0)
            {
                plc.SetDevice("Y0", 1); // 경고등 켜기
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            timer.Stop();
            plc.Close();
            base.OnFormClosing(e);
        }
    }
}

3. 비동기 방식 (Asynchronous)

UI 응답성을 유지하면서 백그라운드에서 데이터를 처리해야 할 때 사용합니다. 주로 현대적인 C# 프로그램에서 많이 사용됩니다.

csharp코드 복사
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using ActUtlTypeLib;

namespace PLC_Async
{
    public partial class MainForm : Form
    {
        private ActUtlType plc;

        public MainForm()
        {
            InitializeComponent();
            InitializePLC();
            StartPollingAsync();
        }

        private void InitializePLC()
        {
            plc = new ActUtlType();
            plc.ActLogicalStationNumber = 1;
            plc.Open();
        }

        private async void StartPollingAsync()
        {
            while (true)
            {
                int value = await Task.Run(() => {
                    int val = 0;
                    plc.GetDevice("D100", out val);
                    return val;
                });
                Console.WriteLine($"Sensor Value: {value}");

                if (value == 0)
                {
                    plc.SetDevice("Y0", 1); // 경고등 켜기
                }

                await Task.Delay(1000); // 1초 주기
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            plc.Close();
            base.OnFormClosing(e);
        }
    }
}

4. 쓰레드 기반 방식 (Thread-based)

멀티쓰레딩을 사용하여 별도의 쓰레드에서 데이터를 처리합니다. 복잡한 작업을 분리하여 성능을 최적화할 때 사용됩니다.

csharp코드 복사
using System;
using System.Threading;
using System.Windows.Forms;
using ActUtlTypeLib;

namespace PLC_ThreadBased
{
    public partial class MainForm : Form
    {
        private ActUtlType plc;
        private Thread pollingThread;
        private bool running = true;

        public MainForm()
        {
            InitializeComponent();
            InitializePLC();
            StartPollingThread();
        }

        private void InitializePLC()
        {
            plc = new ActUtlType();
            plc.ActLogicalStationNumber = 1;
            plc.Open();
        }

        private void StartPollingThread()
        {
            pollingThread = new Thread(PollPLC);
            pollingThread.Start();
        }

        private void PollPLC()
        {
            while (running)
            {
                int value = 0;
                plc.GetDevice("D100", out value);
                Console.WriteLine($"Sensor Value: {value}");

                if (value == 0)
                {
                    plc.SetDevice("Y0", 1); // 경고등 켜기
                }

                Thread.Sleep(1000); // 1초 주기
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            running = false;
            pollingThread.Join();
            plc.Close();
            base.OnFormClosing(e);
        }
    }
}

5. 큐 기반 방식 (Queue-based)

데이터를 큐에 저장하고, 큐를 처리하여 데이터 손실 없이 관리할 때 사용합니다. 데이터 처리와 수집을 분리할 수 있어 유연성이 높습니다.

csharp코드 복사
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Windows.Forms;
using ActUtlTypeLib;

namespace PLC_QueueBased
{
    public partial class MainForm : Form
    {
        private ActUtlType plc;
        private ConcurrentQueue<int> sensorDataQueue;
        private Task processingTask;
        private bool running = true;

        public MainForm()
        {
            InitializeComponent();
            InitializePLC();
            sensorDataQueue = new ConcurrentQueue<int>();
            StartPolling();
            StartProcessing();
        }

        private void InitializePLC()
        {
            plc = new ActUtlType();
            plc.ActLogicalStationNumber = 1;
            plc.Open();
        }

        private void StartPolling()
        {
            Task.Run(async () =>
            {
                while (running)
                {
                    int value = 0;
                    plc.GetDevice("D100", out value);
                    sensorDataQueue.Enqueue(value);
                    await Task.Delay(1000); // 1초 주기
                }
            });
        }

        private void StartProcessing()
        {
            processingTask = Task.Run(() =>
            {
                while (running)
                {
                    if (sensorDataQueue.TryDequeue(out int value))
                    {
                        Console.WriteLine($"Sensor Value: {value}");

                        if (value == 0)
                        {
                            plc.SetDevice("Y0", 1); // 경고등 켜기
                        }
                    }
                }
            });
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            running = false;
            processingTask.Wait();
            plc.Close();
            base.OnFormClosing(e);
        }
    }
}

6. 상태 머신 기반 방식 (State Machine)

복잡한 상태 전환이 필요한 경우, 상태 머신을 사용하여 체계적으로 관리합니다. 상태에 따라 다른 동작을 수행해야 할 때 유용합니다.

csharp코드 복사
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using ActUtlTypeLib;

namespace PLC_StateMachine
{
    public partial class MainForm : Form
    {
        private ActUtlType plc;
        private int sensorValue;

        private enum State { Idle, Warning }
        private State currentState = State.Idle;

        public MainForm()
        {
            InitializeComponent();
            InitializePLC();
            StartPolling();
        }

        private void InitializePLC()
        {
            plc = new ActUtlType();
            plc.ActLogicalStationNumber = 1;
            plc.Open();
        }

        private async void StartPolling()
        {
            while (true)
            {
                sensorValue = await Task.Run(() => {
                    int val = 0;
                    plc.GetDevice("D100", out val);
                    return val;
                });
                Console.WriteLine($"Sensor Value: {sensorValue}");
                UpdateState();
                await Task.Delay(1000); // 1초 주기
            }
        }

        private void UpdateState()
        {
                        switch (currentState)
            {
                case State.Idle:
                    if (sensorValue == 0)
                    {
                        currentState = State.Warning;
                        // 예: 경고등 켜기
                        plc.SetDevice("Y0", 1);
                        Console.WriteLine("State changed to: Warning");
                    }
                    break;

                case State.Warning:
                    if (sensorValue != 0)
                    {
                        currentState = State.Idle;
                        // 예: 경고등 끄기
                        plc.SetDevice("Y0", 0);
                        Console.WriteLine("State changed to: Idle");
                    }
                    break;
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            plc.Close();
            base.OnFormClosing(e);
        }
    }
}

요약

PLC에서 데이터를 받아와서 동작을 구현하는 방식들은 사용 빈도와 용도에 따라 다릅니다. 일반적으로 많이 사용되는 순서대로 정리하면 다음과 같습니다:

  1. 폴링 방식 (Polling): 일정 주기로 데이터를 읽어와서 처리하는 기본적인 방법입니다. 구현이 쉽고 안정적입니다.
  2. 이벤트 기반 방식 (Event-based): 데이터 변경을 감지하여 처리하는 방식으로, 즉각적인 반응이 필요할 때 사용됩니다.
  3. 비동기 방식 (Asynchronous): UI 응답성을 유지하면서 백그라운드에서 데이터를 처리해야 할 때 사용됩니다.
  4. 쓰레드 기반 방식 (Thread-based): 별도의 쓰레드를 사용하여 데이터를 처리하여 성능을 최적화할 때 사용됩니다.
  5. 큐 기반 방식 (Queue-based): 데이터를 큐에 저장하고, 큐를 처리하여 데이터 손실 없이 관리할 때 사용됩니다.
  6. 상태 머신 기반 방식 (State Machine): 복잡한 상태 전환이 필요한 경우, 상태 머신을 사용하여 체계적으로 관리합니다.

이 방식들은 각기 다른 상황에서 유용하며, 프로젝트의 요구사항에 따라 적절한 방법을 선택하여 사용할 수 있습니다.