오늘부턴 산업용 통신 프로토콜에 대해 배운다.
프로토콜
통신규약 - 컴퓨터나 통신 장비 사이에서 메세지를 주고 받는 양식과 규칙 체계
IoT 프로토콜
- IT 벤더 주도
- 개방성/호환성 중시
- 데이터 페이로드에 관심
- 의존성 낮음
산업용 프로토콜
- 설비 벤더 주도
- 안정성 중시
- 시그널에 관심
- 의존성 높음
인터페이스
서로 다른 사물이나 시스템 간에 커뮤니케이션(소통)이 가능하도록 설계한 상호 작용 방식
이더넷
시리얼보다 속도/안정성이 더 좋음
시리얼
오래된 통신 인터페이스 방식
직렬 통신/병렬통신
동기 방식
2개의 디바이스 사이에 동기신호(클럭 신호)를 이용해 동기를 맞춰 데이터를 송수신하는 방식(SPI, I2C)
비동기 방식
스타트 비트와 스톱 비트를 붙여 데이터 신호를 송수신하는 방법(RS232, 422, 485 등)
Baud rate(BPS) : 시리얼 통신에서 1초간 신호 변경 횟수(ex. 19200 -> 1초에 19200개의 비트 전송)
동기 / 비동기 비교
전송 선로에 따른 통신 방식
단방향(Simplex) 통신 : 한쪽 방향으로만 전송할 수 있는 통신 방식
반이중(Half Duplex) 통신 : 두 디바이스 사이에서 한쪽이 송신하는 동안 다른 한쪽은 송신할 수 없음
전이중(Full Duplex) 통신 : 두 디바이스 사이의 데이터 통신을 위해 각각 독립된 회선을 쓰기 때문에 동시에 송수신 가능
RS~ : 데이터를 받음(ex. RS232)
TX~ : 데이터를 전송함
RS232
신호 연결 시 TX(송신 신호)와 RX(수신 신호) 부분을 교차시켜줘야 함
장거리에 좋지 않음(몇십미터 넘으면 통신이 잘 안됨)
point to point(하나의 송신 신호 - 하나의 수신 신호)
RS422
노이즈에 강하고, 최대 전송 길이가 늘어남(최대 1km 이상 가능)
멀티 드롭 가능(하나가 다수에게 데이터 전송 가능)
RS485
422처럼 노이즈에 강하고 전송 거리 늘어남
멀티 드롭 가능(하나가 다수에게 데이터 전송 가능)
요약
PLC
Programmable Logic Controller
- 프로그램 가능한 논리 제어 장치
- 산업 현장에서 자동 제어 및 감시하기 위한 제어 장치
PLC 장비 타입
내장 Cnet 통신
RS232나 RS422/485를 사용하는 통신 방식
XBM 기본 유닛에는 RS232와 RS285 각 1채널씩 총 2채널의 Cnet이 내장
프로토콜은 XGT Protocol, modbus, 사용자 정의 등이 있음
내장 FEnet 통신
이더넷을 사용하는 통신 방식
IP Port 기반, 고속으로 고용량 데이터 송 수신 가능
프로토콜은 XGT Protocol, modbus, 사용자 정의 등이 있음
모드버스 TCP/IP
Fenet 설정
모드버스 설정 - 주소 변경
Visual Studio
NET Framework 사용
UI 화면 편집
솔루션 탐색기 - Form1.Designer.cs에서도 수정 가능
UI 화면 편집 완료
=> 읽기를 누르면 계속 데이터를 쏘고 있는 상태이다.
코드 작성
Form1.cs
\Desktop\IOT\visual studio edukitTest\edukitTest\modbusBitRead
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace modbusBitRead
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void BT_Read_Click(object sender, EventArgs e)
{
//읽기 버튼을 눌렀을 때 하고 싶은 행동
BT_STOP.Enabled = true;
BT_Read.Enabled = false;
timer1.Enabled = true;
timer1.Interval = 100;
Console.WriteLine("읽기 버튼이 눌렸습니다.");
}
private void BT_STOP_Click(object sender, EventArgs e)
{
//정지 버튼을 눌렀을떄 하고 싶은 행동
BT_Read.Enabled =true;
BT_STOP.Enabled = false;
timer1.Enabled = false;
timer1.Interval = 100;
Console.WriteLine("정지 버튼이 눌렸습니다.");
}
private void timer1_Tick(object sender, EventArgs e)
{
//타이머가 도는 동안 무슨 행동을 할지
//1초에 한번씩 PLC에 데이터를 보낼것.
//데이터를 프로토콜에 맞춰서 보낼것.
//0x00 , 0x00 = TID
//0x00, 0x00 = PID
//0x00, 0x06 = L 길이
//0x01 = UID
//0x01 FCode 기능코드
// FCode가 bit를 읽은건지 word읽은건지
// bit를 쓸건지, word 쓸건지
//0x00, 0xA0 = StartAddress 어떤 번지수 부터 읽을지
// 0x00, 0x04 = Numpoint 갯수 몇개 읽어 올지 갯수
byte[] Senddata = new byte[] {
0x00,0x00, //TID
0x00,0x00, //PID
0x00,0x06, //L
0x01, //UID
0x01, //Fcode
0x00, 0xA0, // Start Address A=10 0=0 => A0 = 100
0x00, 0x04 //몇개 읽을건지 4개를 읽을거다
};
SendMassage(Senddata);
}
private void SendMassage(byte[] SendData)
{
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.SendTimeout = 3000;
sock.ReceiveTimeout = 3000;
IPAddress serverIP = IPAddress.Parse("192.168.0.120"); //string 을 ip주소체계로 바꿔줌
int serverPort = 502;
IPEndPoint ipep = new IPEndPoint(serverIP, serverPort);
sock.Connect(ipep); //연결 시도
if (sock.Connected)
{
sock.Send(SendData);
TB_SendFrame.Text = BitConverter.ToString(SendData); //내가 뭘 보냈는지 TB_SENDFrame라는 Textbox에 찍어줌
Thread.Sleep(10);
byte[] _data = new byte[10];
sock.Receive(_data);
DataRead(_data);
}
sock.Close();
}
private void DataRead(byte[] ReadData)
{
int bytes = ReadData.Length; //뭔가 읽어온 데이터의 길이
if(bytes >= 6) //6이하면 PLC가 준 응답이 이상하니까 6이상만
{
if(ReadData[7] != 0x81) //294page (3)에러코드 81, 82 에러
{
int Bit_Datas = ReadData[9];
int[] Data = new int[4];
for (int i =0 ; i < 4; i++)
{
Data[i] = Bit_Datas & 0x00000001; //bitmask처리
Bit_Datas = Bit_Datas >> 1;
}
TB_ReceivedFrame.Text = BitConverter.ToString(ReadData);
BitDataDisplay(Data);
}
}
}
private void BitDataDisplay(int[] Data)
{
CB_M100.Checked = Convert.ToBoolean(Data[0]); //1이면 true 0이면 false
CB_M101.Checked = Convert.ToBoolean(Data[1]);
CB_M102.Checked = Convert.ToBoolean(Data[2]);
CB_M103.Checked = Convert.ToBoolean(Data[3]);
}
}
}
Form1.Designer.cs
\Desktop\IOT\visual studio edukitTest\edukitTest\modbusBitRead
namespace modbusBitRead
{
partial class Form1
{
/// <summary>
/// 필수 디자이너 변수입니다.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 사용 중인 모든 리소스를 정리합니다.
/// </summary>
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form 디자이너에서 생성한 코드
/// <summary>
/// 디자이너 지원에 필요한 메서드입니다.
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.CB_M103 = new System.Windows.Forms.CheckBox();
this.CB_M102 = new System.Windows.Forms.CheckBox();
this.CB_M101 = new System.Windows.Forms.CheckBox();
this.CB_M100 = new System.Windows.Forms.CheckBox();
this.TB_ReceivedFrame = new System.Windows.Forms.TextBox();
this.TB_SendFrame = new System.Windows.Forms.TextBox();
this.BT_STOP = new System.Windows.Forms.Button();
this.BT_Read = new System.Windows.Forms.Button();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(39, 144);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(81, 15);
this.label1.TabIndex = 0;
this.label1.Text = "송신 Frame";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(39, 238);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(81, 15);
this.label2.TabIndex = 1;
this.label2.Text = "수신 Frame";
//
// groupBox1
//
this.groupBox1.Controls.Add(this.CB_M103);
this.groupBox1.Controls.Add(this.CB_M102);
this.groupBox1.Controls.Add(this.CB_M101);
this.groupBox1.Controls.Add(this.CB_M100);
this.groupBox1.Controls.Add(this.TB_ReceivedFrame);
this.groupBox1.Controls.Add(this.TB_SendFrame);
this.groupBox1.Controls.Add(this.BT_STOP);
this.groupBox1.Controls.Add(this.BT_Read);
this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Controls.Add(this.label2);
this.groupBox1.Location = new System.Drawing.Point(41, 21);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(725, 403);
this.groupBox1.TabIndex = 2;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Modbus TCP(01)";
//
// CB_M103
//
this.CB_M103.AutoSize = true;
this.CB_M103.Location = new System.Drawing.Point(426, 331);
this.CB_M103.Name = "CB_M103";
this.CB_M103.Size = new System.Drawing.Size(65, 19);
this.CB_M103.TabIndex = 9;
this.CB_M103.Text = "M103";
this.CB_M103.UseVisualStyleBackColor = true;
//
// CB_M102
//
this.CB_M102.AutoSize = true;
this.CB_M102.Location = new System.Drawing.Point(299, 331);
this.CB_M102.Name = "CB_M102";
this.CB_M102.Size = new System.Drawing.Size(65, 19);
this.CB_M102.TabIndex = 8;
this.CB_M102.Text = "M102";
this.CB_M102.UseVisualStyleBackColor = true;
//
// CB_M101
//
this.CB_M101.AutoSize = true;
this.CB_M101.Location = new System.Drawing.Point(169, 331);
this.CB_M101.Name = "CB_M101";
this.CB_M101.Size = new System.Drawing.Size(65, 19);
this.CB_M101.TabIndex = 7;
this.CB_M101.Text = "M101";
this.CB_M101.UseVisualStyleBackColor = true;
//
// CB_M100
//
this.CB_M100.AutoSize = true;
this.CB_M100.Location = new System.Drawing.Point(42, 331);
this.CB_M100.Name = "CB_M100";
this.CB_M100.Size = new System.Drawing.Size(65, 19);
this.CB_M100.TabIndex = 6;
this.CB_M100.Text = "M100";
this.CB_M100.UseVisualStyleBackColor = true;
//
// TB_ReceivedFrame
//
this.TB_ReceivedFrame.Location = new System.Drawing.Point(42, 278);
this.TB_ReceivedFrame.Name = "TB_ReceivedFrame";
this.TB_ReceivedFrame.Size = new System.Drawing.Size(498, 25);
this.TB_ReceivedFrame.TabIndex = 5;
//
// TB_SendFrame
//
this.TB_SendFrame.Location = new System.Drawing.Point(42, 186);
this.TB_SendFrame.Name = "TB_SendFrame";
this.TB_SendFrame.Size = new System.Drawing.Size(498, 25);
this.TB_SendFrame.TabIndex = 4;
//
// BT_STOP
//
this.BT_STOP.Location = new System.Drawing.Point(267, 60);
this.BT_STOP.Name = "BT_STOP";
this.BT_STOP.Size = new System.Drawing.Size(161, 38);
this.BT_STOP.TabIndex = 3;
this.BT_STOP.Text = "정지";
this.BT_STOP.UseVisualStyleBackColor = true;
this.BT_STOP.Click += new System.EventHandler(this.BT_STOP_Click);
//
// BT_Read
//
this.BT_Read.Location = new System.Drawing.Point(42, 60);
this.BT_Read.Name = "BT_Read";
this.BT_Read.Size = new System.Drawing.Size(154, 38);
this.BT_Read.TabIndex = 2;
this.BT_Read.Text = "읽기";
this.BT_Read.UseVisualStyleBackColor = true;
this.BT_Read.Click += new System.EventHandler(this.BT_Read_Click);
//
// timer1
//
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(852, 496);
this.Controls.Add(this.groupBox1);
this.Name = "Form1";
this.Text = "Form1";
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.TextBox TB_SendFrame;
private System.Windows.Forms.Button BT_STOP;
private System.Windows.Forms.Button BT_Read;
private System.Windows.Forms.CheckBox CB_M103;
private System.Windows.Forms.CheckBox CB_M102;
private System.Windows.Forms.CheckBox CB_M101;
private System.Windows.Forms.CheckBox CB_M100;
private System.Windows.Forms.TextBox TB_ReceivedFrame;
private System.Windows.Forms.Timer timer1;
}
}
비트 쓰기
Form1.cs
C:\Users\user\Desktop\IOT\visual studio edukitTest\bitWrite\bitWrite
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
namespace bitWrite
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void CB_M0_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x00, 0x00 }; // 시작번지(M0)
MakeSendFrame(CB_M0.Checked, startAddress);
}
private void CB_M1000_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x06, 0x40 }; // 시작번지(M1000)
MakeSendFrame(CB_M1000.Checked, startAddress);
}
private void MakeSendFrame(bool CBState, byte[] startAddress)
{
byte[] SendData = new byte[]
{
0x00, 0x00, // TID
0x00, 0x00, // PID
0x00, 0x06, // L
0x01, // UID
0x05 // Fcode 0x01=bit 값을 read 하겠다 / 0x05=bit에 write를 하겠다
};
SendData = Combine(SendData, startAddress); // combine이란 함수를 통해서 sendData랑 StartAddress 합쳐줌
byte[] BitData = new byte[0];
if (CBState == true) BitData = new byte[] { 0xFF, 0x00 }; // checkbox 상태에 따라 1을 보낼지, 0을 보낼지
else BitData = new byte[] { 0x00, 0x00 };
SendData = Combine(SendData, BitData);
SendMassage(SendData); // sendData는 위에 있는 TID+PID+L+UID+Fcode+startAddress+1/0
}
private void SendMassage(byte[] SendData)
{
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.SendTimeout = 3000;
sock.ReceiveTimeout = 3000;
IPAddress serverIP = IPAddress.Parse("192.168.0.120");
int serverPort = 502;
IPEndPoint ipep = new IPEndPoint(serverIP, serverPort);
if (sock.IsBound == true) sock.Close();
sock.Connect(ipep);
if (sock.Connected)
{
sock.Send(SendData);
TB_SendFrame.Text = BitConverter.ToString(SendData);
}
sock.Close();
}
private byte[] Combine(byte[] a, byte[] b) // 바이트 합치기
{
byte[] c = new byte[a.Length + b.Length];
Buffer.BlockCopy(a, 0, c, 0, a.Length);
Buffer.BlockCopy(b, 0, c, a.Length, b.Length);
return c;
}
}
}
Form1.Designer.cs
C:\Users\user\Desktop\IOT\visual studio edukitTest\bitWrite\bitWrite
namespace bitWrite
{
partial class Form1
{
/// <summary>
/// 필수 디자이너 변수입니다.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 사용 중인 모든 리소스를 정리합니다.
/// </summary>
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form 디자이너에서 생성한 코드
/// <summary>
/// 디자이너 지원에 필요한 메서드입니다.
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
/// </summary>
private void InitializeComponent()
{
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.CB_M1000 = new System.Windows.Forms.CheckBox();
this.CB_M0 = new System.Windows.Forms.CheckBox();
this.label3 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.TB_SendFrame = new System.Windows.Forms.TextBox();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// groupBox1
//
this.groupBox1.Controls.Add(this.CB_M1000);
this.groupBox1.Controls.Add(this.CB_M0);
this.groupBox1.Controls.Add(this.label3);
this.groupBox1.Controls.Add(this.label2);
this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Controls.Add(this.TB_SendFrame);
this.groupBox1.Location = new System.Drawing.Point(0, 0);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(311, 111);
this.groupBox1.TabIndex = 0;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Modbus TCP(05)";
//
// CB_M1000
//
this.CB_M1000.AutoSize = true;
this.CB_M1000.Location = new System.Drawing.Point(210, 29);
this.CB_M1000.Name = "CB_M1000";
this.CB_M1000.Size = new System.Drawing.Size(15, 14);
this.CB_M1000.TabIndex = 5;
this.CB_M1000.UseVisualStyleBackColor = true;
this.CB_M1000.CheckedChanged += new System.EventHandler(this.CB_M1000_CheckedChanged);
//
// CB_M0
//
this.CB_M0.AutoSize = true;
this.CB_M0.Location = new System.Drawing.Point(48, 30);
this.CB_M0.Name = "CB_M0";
this.CB_M0.Size = new System.Drawing.Size(15, 14);
this.CB_M0.TabIndex = 4;
this.CB_M0.UseVisualStyleBackColor = true;
this.CB_M0.CheckedChanged += new System.EventHandler(this.CB_M0_CheckedChanged);
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(20, 62);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(69, 12);
this.label3.TabIndex = 3;
this.label3.Text = "송신 Frame";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(166, 30);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(40, 12);
this.label2.TabIndex = 2;
this.label2.Text = "M1000";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(20, 31);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(22, 12);
this.label1.TabIndex = 1;
this.label1.Text = "M0";
//
// TB_SendFrame
//
this.TB_SendFrame.Location = new System.Drawing.Point(22, 77);
this.TB_SendFrame.Name = "TB_SendFrame";
this.TB_SendFrame.Size = new System.Drawing.Size(274, 21);
this.TB_SendFrame.TabIndex = 0;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(324, 122);
this.Controls.Add(this.groupBox1);
this.Name = "Form1";
this.Text = "Form1";
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.CheckBox CB_M1000;
private System.Windows.Forms.CheckBox CB_M0;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox TB_SendFrame;
}
}
M21(시작 푸쉬버튼), M22(정지 푸쉬버튼) 추가
Form1.cs
C:\Users\user\Desktop\IOT\visual studio edukitTest\bitWrite\bitWrite
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
namespace bitWrite
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void CB_M0_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x00, 0x00 }; // 시작번지(M0)
MakeSendFrame(CB_M0.Checked, startAddress);
}
private void CB_M1000_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x06, 0x40 }; // 시작번지(M1000)
MakeSendFrame(CB_M1000.Checked, startAddress);
}
private void CB_M21_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x00, 0x21 }; // 시작번지(M21)
MakeSendFrame(CB_M21.Checked, startAddress);
}
private void CB_M22_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x00, 0x22}; // 시작번지(M22)
MakeSendFrame(CB_M22.Checked, startAddress);
}
private void MakeSendFrame(bool CBState, byte[] startAddress)
{
byte[] SendData = new byte[]
{
0x00, 0x00, // TID
0x00, 0x00, // PID
0x00, 0x06, // L
0x01, // UID
0x05 // Fcode 0x01=bit 값을 read 하겠다 / 0x05=bit에 write를 하겠다
};
SendData = Combine(SendData, startAddress); // combine이란 함수를 통해서 sendData랑 StartAddress 합쳐줌
byte[] BitData = new byte[0];
if (CBState == true) BitData = new byte[] { 0xFF, 0x00 }; // checkbox 상태에 따라 1을 보낼지, 0을 보낼지
else BitData = new byte[] { 0x00, 0x00 };
SendData = Combine(SendData, BitData);
SendMassage(SendData); // sendData는 위에 있는 TID+PID+L+UID+Fcode+startAddress+1/0
}
private void SendMassage(byte[] SendData)
{
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.SendTimeout = 3000;
sock.ReceiveTimeout = 3000;
IPAddress serverIP = IPAddress.Parse("192.168.0.120");
int serverPort = 502;
IPEndPoint ipep = new IPEndPoint(serverIP, serverPort);
if (sock.IsBound == true) sock.Close();
sock.Connect(ipep);
if (sock.Connected)
{
sock.Send(SendData);
TB_SendFrame.Text = BitConverter.ToString(SendData);
}
sock.Close();
}
private byte[] Combine(byte[] a, byte[] b) // 바이트 합치기
{
byte[] c = new byte[a.Length + b.Length];
Buffer.BlockCopy(a, 0, c, 0, a.Length);
Buffer.BlockCopy(b, 0, c, a.Length, b.Length);
return c;
}
}
}
Form1.Designer.cs
C:\Users\user\Desktop\IOT\visual studio edukitTest\bitWrite\bitWrite
namespace bitWrite
{
partial class Form1
{
/// <summary>
/// 필수 디자이너 변수입니다.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 사용 중인 모든 리소스를 정리합니다.
/// </summary>
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form 디자이너에서 생성한 코드
/// <summary>
/// 디자이너 지원에 필요한 메서드입니다.
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
/// </summary>
private void InitializeComponent()
{
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.CB_M22 = new System.Windows.Forms.CheckBox();
this.label5 = new System.Windows.Forms.Label();
this.CB_M21 = new System.Windows.Forms.CheckBox();
this.label4 = new System.Windows.Forms.Label();
this.CB_M1000 = new System.Windows.Forms.CheckBox();
this.CB_M0 = new System.Windows.Forms.CheckBox();
this.label3 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.TB_SendFrame = new System.Windows.Forms.TextBox();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// groupBox1
//
this.groupBox1.Controls.Add(this.CB_M22);
this.groupBox1.Controls.Add(this.label5);
this.groupBox1.Controls.Add(this.CB_M21);
this.groupBox1.Controls.Add(this.label4);
this.groupBox1.Controls.Add(this.CB_M1000);
this.groupBox1.Controls.Add(this.CB_M0);
this.groupBox1.Controls.Add(this.label3);
this.groupBox1.Controls.Add(this.label2);
this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Controls.Add(this.TB_SendFrame);
this.groupBox1.Location = new System.Drawing.Point(7, 6);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(378, 111);
this.groupBox1.TabIndex = 0;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Modbus TCP(05)";
//
// CB_M22
//
this.CB_M22.AutoSize = true;
this.CB_M22.Location = new System.Drawing.Point(313, 31);
this.CB_M22.Name = "CB_M22";
this.CB_M22.Size = new System.Drawing.Size(42, 16);
this.CB_M22.TabIndex = 9;
this.CB_M22.Text = "ON";
this.CB_M22.UseVisualStyleBackColor = true;
this.CB_M22.CheckedChanged += new System.EventHandler(this.CB_M22_CheckedChanged);
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(280, 32);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(28, 12);
this.label5.TabIndex = 8;
this.label5.Text = "M22";
//
// CB_M21
//
this.CB_M21.AutoSize = true;
this.CB_M21.Location = new System.Drawing.Point(227, 30);
this.CB_M21.Name = "CB_M21";
this.CB_M21.Size = new System.Drawing.Size(42, 16);
this.CB_M21.TabIndex = 7;
this.CB_M21.Text = "ON";
this.CB_M21.UseVisualStyleBackColor = true;
this.CB_M21.CheckedChanged += new System.EventHandler(this.CB_M21_CheckedChanged);
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(196, 32);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(28, 12);
this.label4.TabIndex = 6;
this.label4.Text = "M21";
//
// CB_M1000
//
this.CB_M1000.AutoSize = true;
this.CB_M1000.Location = new System.Drawing.Point(143, 30);
this.CB_M1000.Name = "CB_M1000";
this.CB_M1000.Size = new System.Drawing.Size(42, 16);
this.CB_M1000.TabIndex = 5;
this.CB_M1000.Text = "ON";
this.CB_M1000.UseVisualStyleBackColor = true;
this.CB_M1000.CheckedChanged += new System.EventHandler(this.CB_M1000_CheckedChanged);
//
// CB_M0
//
this.CB_M0.AutoSize = true;
this.CB_M0.Location = new System.Drawing.Point(48, 30);
this.CB_M0.Name = "CB_M0";
this.CB_M0.Size = new System.Drawing.Size(42, 16);
this.CB_M0.TabIndex = 4;
this.CB_M0.Text = "ON";
this.CB_M0.UseVisualStyleBackColor = true;
this.CB_M0.CheckedChanged += new System.EventHandler(this.CB_M0_CheckedChanged);
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(20, 62);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(69, 12);
this.label3.TabIndex = 3;
this.label3.Text = "송신 Frame";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(96, 32);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(40, 12);
this.label2.TabIndex = 2;
this.label2.Text = "M1000";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(20, 31);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(22, 12);
this.label1.TabIndex = 1;
this.label1.Text = "M0";
//
// TB_SendFrame
//
this.TB_SendFrame.Location = new System.Drawing.Point(22, 77);
this.TB_SendFrame.Name = "TB_SendFrame";
this.TB_SendFrame.Size = new System.Drawing.Size(335, 21);
this.TB_SendFrame.TabIndex = 0;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(390, 122);
this.Controls.Add(this.groupBox1);
this.Name = "Form1";
this.Text = "Form1";
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.CheckBox CB_M1000;
private System.Windows.Forms.CheckBox CB_M0;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox TB_SendFrame;
private System.Windows.Forms.CheckBox CB_M22;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.CheckBox CB_M21;
private System.Windows.Forms.Label label4;
}
}
StartAddress 계산법
M1000
0x06, 0x40
1. 064 + 0(맨 끝의 0은 일단 보류)
2. HEX 064 = 100
3. 맨 끝의 0을 말 그대로 맨 끝에 붙여주면 됨 : 100 + 0 => 1000
같은 원리로
M1002
0x06, 0x42
=> 위의 계산대로 똑같이 오고 맨 끝에 2만 붙여주면 됨
몇 가지 예시 더 보기
M1015
0x06, 0x4F
064 + F
HEX 064 = 100
HEX F = 15(맨 끝)
=> 합치면 1015
M3560
0x16, 0x40
164 + 0
HEX 164 = 356
HEX 0 = 0
=> 3560
mosquitto 활용
참조 - m2mqtt 설치
mqtt 클라이언트 생성
static ~ 치고 ctrl+.(문제 fix) 해서 mqtt 라이브러리 추가
속성(파라미터) 확인하는 법
F12 / 우클 - 정의로 이동
Subscribe
해당 부분에 코드 추가
Form1.cs
\Desktop\IOT\visual studio edukitTest\edukitTest\modbusBitRead
public partial class Form1 : Form
{
static MqttClient mqttClient;
public Form1()
{
InitializeComponent();
}
맨 밑에 코드 추가
// Subscribe
private void Form1_Load(object sender, EventArgs e)
{
// form 더블클릭하여 자동생성 했음
//form load 될때 mqtt가 연결 시도를 할 수 있도록 여기 작성
mqttClient = new MqttClient("localhost", 1883, false, null, null, MqttSslProtocols.TLSv1_2); // 로컬호스트의 1883 포트에 연결
mqttClient.ProtocolVersion = MqttProtocolVersion.Version_3_1_1; // 기본값 3.1.1, 버전이 맞아야 연결된다.
mqttClient.MqttMsgPublishReceived += MqttClient_MqttMsgPublishReceived;
//mqttClient.ConnectionClosed += MqttClient_ConnectionClosed;
byte code = mqttClient.Connect(Guid.NewGuid().ToString()); // clientID
mqttClient.Subscribe(new string[] { "test" },
new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
}
private void MqttClient_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
// mqtt 데이터 신호 받았을 때(subscribe) 무슨 처리를 할 지 작성하는 메소드
string mes = Encoding.Default.GetString(e.Message);
Console.WriteLine(mes);
}
결과
Publish
읽기 버튼에 publish 추가 => 읽기 버튼 누르면 publish 됨
Form1.cs
\Desktop\IOT\visual studio edukitTest\edukitTest\modbusBitRead
private void BT_Read_Click(object sender, EventArgs e)
{
//읽기 버튼을 눌렀을 때 하고 싶은 행동
BT_STOP.Enabled = true;
BT_Read.Enabled = false;
timer1.Enabled = true;
timer1.Interval = 100;
Console.WriteLine("읽기 버튼이 눌렸습니다.");
// publish
string tempData = "publishMessage";
mqttClient.Publish("test", Encoding.Default.GetBytes(tempData),
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
}
결과
M21, M22가 true가 될 때 publish (topic, message) 하기
bitWrite 프로젝트에도 m2mqtt 설치 후
Form1.cs
\Desktop\IOT\visual studio edukitTest\bitWrite\bitWrite
맨 밑에 코드 추가
// 시작할 때(폼 로드 될 때) 체크 되어 있게 함
private void Form1_Load(object sender, EventArgs e)
{
CB_M21.Checked = true;
CB_M22.Checked = true;
}
나머지는 위와 똑같이 추가하면 된다.
내가 짠 코드
M22의 체크란이 변경되었을 때 안에다가 if문을 사용해 publish를 했다.
private void CB_M22_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x00, 0x22}; // 시작번지(M22)
MakeSendFrame(CB_M22.Checked, startAddress);
// publish
if (CB_M22.Checked == true)
{
string tempData = "M22 working";
mqttClient.Publish("test", Encoding.Default.GetBytes(tempData),
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
};
}
근데 안 됐다.
NUll 오류가 떴다.
그 이유는 밑에 그대로 붙여넣은 Form Read에서 mqtt가 연결되기 전에 M22 Checked를 true로 먼저 넣어버렸기 때문이었다.
그래서 아래와 같이 밑으로 내렸다.
private void Form1_Load(object sender, EventArgs e)
{
// form 더블클릭하여 자동생성 했음
//form load 될때 mqtt가 연결 시도를 할 수 있도록 여기 작성
mqttClient = new MqttClient("localhost", 1883, false, null, null, MqttSslProtocols.TLSv1_2); // 로컬호스트의 1883 포트에 연결
mqttClient.ProtocolVersion = MqttProtocolVersion.Version_3_1_1; // 기본값 3.1.1, 버전이 맞아야 연결된다.
mqttClient.MqttMsgPublishReceived += MqttClient_MqttMsgPublishReceived;
//mqttClient.ConnectionClosed += MqttClient_ConnectionClosed;
byte code = mqttClient.Connect(Guid.NewGuid().ToString()); // clientID
mqttClient.Subscribe(new string[] { "test" },
new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
// mqtt가 연결이 되고 나서 true가 되어야 함
CB_M21.Checked = true;
CB_M22.Checked = true;
}
이젠 잘 됐다.
근데 이렇게 짜면 true 따로 false 따로 짜줘야 하기 때문에 잘 짰다고 보긴 힘들다.(코드가 쓸데없이 길어짐)
아무래도 아는 방식만 쓰게 되는 것 같다.(아는 게 별로 없어서 그렇기도 하겠지만) 늘 코드 칠 때 더 좋은 방법은 없는 지 고심하는 습관을 들일 수 있도록 노력해야겠다.
강사님의 코드는 아래와 같다.
private void CB_M21_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x00, 0x21 }; // 시작번지(M21)
MakeSendFrame(CB_M21.Checked, startAddress);
// publish
string tempData = "M21"; // 메모리 위치
mqttClient.Publish("test", Encoding.Default.GetBytes(tempData + CB_M21.Checked), // 퍼블리싱하는 내용 : 메모리 위치 + value(boolean값)
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false); // MqttMsgBase~ : 통신의 안전성 레벨
}
private void CB_M22_CheckedChanged(object sender, EventArgs e)
{
byte[] startAddress = new byte[] { 0x00, 0x22}; // 시작번지(M22)
MakeSendFrame(CB_M22.Checked, startAddress);
// publish
string tempData = "M22 working";
mqttClient.Publish("test", Encoding.Default.GetBytes(tempData + CB_M22.Checked),
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
}
이러면 조건문을 붙이지 않아도 한번에 해결이 가능하다.
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE는 뭔가 궁금해서 구글링해봤는데 간단히 말해 사물인터넷에서 사용하는 통신 안정성 등급인 것 같다.
결과(MQTTX)
체크를 누를 때마다 잘 간다.
그리고 추가로 publish로 특정 메세지를 보내면 subscribe 했을 때 해당 동작을 수행하도록 하는 코드도 배웠다.
그냥 넣으면 쓰레드 에러가 떠서(다른 쓰레드에서 실행됨) 강사님이 구글링하여 Invoke를 넣어줬다.
맨 마지막의 Subscribe message 받았을 때 처리에다가 넣어주면 된다.
// Subscribe message 받았을 때 처리
private void MqttClient_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
// mqtt 데이터 신호 받았을 때(subscribe) 무슨 처리를 할 지 작성하는 메소드
string mes = Encoding.Default.GetString(e.Message);
Console.WriteLine(mes);
// publish로부터 M22ON 이라는 메세지를 받으면 M22의 check가 활성화된다.
if (mes == "M22ON")
{
this.Invoke(new Action(() =>
{
CB_M22.Checked = true;
}));
}
else if (mes == "M22OFF")
{
this.Invoke(new Action(() =>
{
CB_M22.Checked = false;
}));
}
}
근데 이것도 안됐다.
Plaintext도 바꿨는데 왜? 하고 다시한번 차근차근 살펴보니 토픽명을 test123으로 해서 subscribe가 안됐던 것 같다.
mqttClient.Subscribe(new string[] { "test" },
위에 코드 살펴보니 Form Load 부분의 subscribe 내용에 토픽이 test로만 되어 있는 걸 찾을 수 있었다.
test로 하니 잘 됐다.
결과
Visual Studio Git 연동
git 메뉴 - 설정 - 로그인 후
git 변경 내용에서 리퍼지토리 생성을 누르고 다시한번 로그인
느낀 점
IoT랑 백엔드랑 프론트엔드 연동을 이제서야 비로소 좀 알 것 같다.
아주 간단하게나마 원격제어 해보니까 신기했다.
C#는 처음이었는데 확실히 파이썬보다 칠게 많은 것 같다.
근데 그만큼 이게 뭔지 잘 적혀있어서 파악도 오히려 어렵지 않은 것 같기도 하고...?
'공부 > Digital Twin Bootcamp' 카테고리의 다른 글
TIL_220121_IOT (0) | 2022.01.21 |
---|---|
TIL_220120_IOT (0) | 2022.01.20 |
TIL_220118_IOT (0) | 2022.01.18 |
TIL_220117_IOT (0) | 2022.01.17 |
TIL_220114_IOT (0) | 2022.01.14 |