MQTT Dashboard 연동
1축(y) 메모리 위치 : K0424
2축(x) 메모리 위치 : K0444
Form1.cs
\Desktop\IOT\visual studio edukitTest\Mqtt_Dashboard\Mqtt_Dashboard
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.Tasks;
using System.Windows.Forms;
namespace Mqtt_Dashboard
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Form(true);
}
private void BT_Read_Click(object sender, EventArgs e)
{
Form(false);
timer1.Enabled = true;
timer1.Interval = 100;
}
private void BT_Stop_Click(object sender, EventArgs e)
{
Form(true);
timer1.Enabled = false;
}
private void Form(bool t)
{
BT_Read.Enabled = t;
BT_Stop.Enabled = !t;
}
private void timer1_Tick(object sender, EventArgs e)
{
byte[] DataFrame = MakeDataFrame();
byte[] SendFrame = MakeCompanyFrame(DataFrame);
SendFrame = Combine(SendFrame, DataFrame);
SendMessage(SendFrame);
}
private byte[] MakeDataFrame() // command에 해당
{
byte[] DataFrame = new byte[]
{
0x54, 0x00, // 명령어(읽기)
0x02, 0x00, // 데이터 타입 = Word
0x00, 0x00, // 예약 영역(고정)
0x02, 0x00 // 읽어올 변수개수 = 2
};
byte[] VariableBlock = MakeVariableBlock();
DataFrame = Combine(DataFrame, VariableBlock);
return DataFrame;
}
private byte[] MakeVariableBlock()
{
byte[] VariableBlock = new byte[0];
VariableBlock = MakeBlock("%KW424", VariableBlock); // bit면 X, Word면 W
VariableBlock = MakeBlock("%KW444", VariableBlock);
return VariableBlock;
}
private byte[] MakeBlock(string VariableName, byte[] VariableBlock)
{
byte[] VariableLength = new byte[2] { 0x06, 0x00 }; // 변수명 길이 최대 총 8글자
VariableBlock = Combine(VariableBlock, VariableLength);
byte[] Variable = Encoding.Default.GetBytes(VariableName);
VariableBlock = Combine(VariableBlock, Variable);
return VariableBlock;
}
private byte[] MakeCompanyFrame(byte[] DataFrame) // Company
{
string Company_ID = "LSIS-XGT";
byte[] CompanyFrame = Encoding.Default.GetBytes(Company_ID);
byte[] CPU_Info = new byte[]
{
0x00, 0x00, // 예비
0x00, 0x00, // PLC 정보
0xB0, // XGB(MK)
0x33, // 프레임 방향 = PC->PLC
0x00, 0x00 // 프레임 순서 번호 = 0번
};
CompanyFrame = Combine(CompanyFrame, CPU_Info);
byte[] Length = new byte[] { (byte)DataFrame.Length, 0x00 }; // 데이터의 길이(바이트 수)
CompanyFrame = Combine(CompanyFrame, Length);
byte[] FEnetPosition = new byte[] { 0x01 }; // 0번 베이스, 1번 슬롯
CompanyFrame = Combine(CompanyFrame, FEnetPosition);
byte[] BCC = new byte[1];
ushort crc = Calc(CompanyFrame);
BCC[0] = (byte)crc; // 맨 밑의 crc 체크섬
CompanyFrame = Combine(CompanyFrame, BCC);
return CompanyFrame;
}
private void SendMessage(byte[] SendFrame)
{
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 = 2004; // modbus는 502
IPEndPoint ipep = new IPEndPoint(serverIP, serverPort);
if (sock.IsBound == true) sock.Close();
sock.Connect(ipep);
if (sock.Connected)
{
sock.Send(SendFrame);
byte[] Rdata = new byte[100];
sock.Receive(Rdata);
ReceivedDataProcessing(Rdata);
}
sock.Close();
}
private void ReceivedDataProcessing(byte[] RData)
{
short Datas = BitConverter.ToInt16(RData, 32); // 앞의 데이터는 다 쓸모 없어서 스킵
TB_K424.Text = Datas.ToString();
Datas = BitConverter.ToInt16(RData, 36); // 4씩 더하면 됨
TB_K444.Text = Datas.ToString();
}
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;
}
private static ushort Calc(byte[] data) // CRC코드 생성
{
ushort crc = 0xFFFF;
for (int pos = 0; pos < data.Length; pos++)
{
crc ^= (UInt16)data[pos];
for (int i = 8; i != 0; i--)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else crc >>= 1;
}
}
return crc;
}
}
}
그런데 수치가 똑같지 않음
=> 모터가 정밀하여 단위가 너무 커서 word기 표현할 수 있는 값을 넘어서서 실제랑 다르게 보임
정상적인 값을 얻으려면 32bit(더블워드)로 봐야함(424+425 같이 봐야함)
그래서 부분부분 수정해줌
private byte[] MakeDataFrame() // command에 해당
{
byte[] DataFrame = new byte[]
{
0x54, 0x00, // 명령어(읽기)
0x02, 0x00, // 데이터 타입 = Word
0x00, 0x00, // 예약 영역(고정)
0x04, 0x00 // 읽어올 변수개수 = 4
};
byte[] VariableBlock = MakeVariableBlock();
DataFrame = Combine(DataFrame, VariableBlock);
return DataFrame;
}
private byte[] MakeVariableBlock()
{
byte[] VariableBlock = new byte[0];
VariableBlock = MakeBlock("%KW424", VariableBlock); // y축 위 아래로 움직이는 값
VariableBlock = MakeBlock("%KW425", VariableBlock); // y축 위 아래로 움직이는 값 * 66536
VariableBlock = MakeBlock("%KW444", VariableBlock);
VariableBlock = MakeBlock("%KW445", VariableBlock);
return VariableBlock;
}
private void ReceivedDataProcessing(byte[] RData)
{
Int16 Datas1 = BitConverter.ToInt16(RData, 32); // 앞의 데이터는 다 쓸모 없어서 스킵
Int16 Datas2 = BitConverter.ToInt16(RData, 36); // 4씩 더하면 됨
Int16 Datas3 = BitConverter.ToInt16(RData, 40);
Int16 Datas4 = BitConverter.ToInt16(RData, 44);
TB_K424.Text = (Datas1 + (Datas2 * 65536)).ToString();
TB_K444.Text = (Datas3 + (Datas4 * 65536)).ToString();
}
65536를 곱하는 이유
16비트의 최댓값 : 65535 => 0부터 65535까지 십진수로 총 65536
워드가 표현할 수 있는 최댓값을 넘어선 경우
값을 다음 위치의 메모리에도 넣어준다. (K444에도 넣고 K445에도 넣고)
이때 값을 최댓값 * 다음 메모리값 + 나머지값(메모리값)으로 표현한다.
=> 65536을 1(K445)에 곱하고 + 나머지값(K444의 30402)을 더한다. 그러면 실제 값인 96,938이 나온다.
읽어온 값을 json 형식으로 만들어서 MQTT 메세지 전송하기
MQTT 연동
Form1.cs
\Desktop\IOT\visual studio edukitTest\Mqtt_Dashboard\Mqtt_Dashboard
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.Tasks;
using System.Windows.Forms;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
namespace Mqtt_Dashboard
{
public partial class Form1 : Form
{
static MqttClient mqttClient;
public Form1()
{
InitializeComponent();
//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 Form1_Load(object sender, EventArgs e)
{
Form(true);
}
private void BT_Read_Click(object sender, EventArgs e)
{
Form(false);
timer1.Enabled = true;
timer1.Interval = 1000;
}
private void BT_Stop_Click(object sender, EventArgs e)
{
Form(true);
timer1.Enabled = false;
}
private void Form(bool t)
{
BT_Read.Enabled = t;
BT_Stop.Enabled = !t;
}
private void timer1_Tick(object sender, EventArgs e)
{
byte[] DataFrame = MakeDataFrame();
byte[] SendFrame = MakeCompanyFrame(DataFrame);
SendFrame = Combine(SendFrame, DataFrame);
SendMessage(SendFrame);
}
private byte[] MakeDataFrame() // command에 해당
{
byte[] DataFrame = new byte[]
{
0x54, 0x00, // 명령어(읽기)
0x02, 0x00, // 데이터 타입 = Word
0x00, 0x00, // 예약 영역(고정)
0x04, 0x00 // 읽어올 변수개수 = 4
};
byte[] VariableBlock = MakeVariableBlock();
DataFrame = Combine(DataFrame, VariableBlock);
return DataFrame;
}
private byte[] MakeVariableBlock()
{
byte[] VariableBlock = new byte[0];
VariableBlock = MakeBlock("%KW424", VariableBlock); // y축 위 아래로 움직이는 값
VariableBlock = MakeBlock("%KW425", VariableBlock); // y축 위 아래로 움직이는 값 * 66536
VariableBlock = MakeBlock("%KW444", VariableBlock);
VariableBlock = MakeBlock("%KW445", VariableBlock);
return VariableBlock;
}
private byte[] MakeBlock(string VariableName, byte[] VariableBlock)
{
byte[] VariableLength = new byte[2] { 0x06, 0x00 }; // 변수명 길이 최대 총 8글자
VariableBlock = Combine(VariableBlock, VariableLength);
byte[] Variable = Encoding.Default.GetBytes(VariableName);
VariableBlock = Combine(VariableBlock, Variable);
return VariableBlock;
}
private byte[] MakeCompanyFrame(byte[] DataFrame) // Company
{
string Company_ID = "LSIS-XGT";
byte[] CompanyFrame = Encoding.Default.GetBytes(Company_ID);
byte[] CPU_Info = new byte[]
{
0x00, 0x00, // 예비
0x00, 0x00, // PLC 정보
0xB0, // XGB(MK)
0x33, // 프레임 방향 = PC->PLC
0x00, 0x00 // 프레임 순서 번호 = 0번
};
CompanyFrame = Combine(CompanyFrame, CPU_Info);
byte[] Length = new byte[] { (byte)DataFrame.Length, 0x00 }; // 데이터의 길이(바이트 수)
CompanyFrame = Combine(CompanyFrame, Length);
byte[] FEnetPosition = new byte[] { 0x01 }; // 0번 베이스, 1번 슬롯
CompanyFrame = Combine(CompanyFrame, FEnetPosition);
byte[] BCC = new byte[1];
ushort crc = Calc(CompanyFrame);
BCC[0] = (byte)crc; // 맨 밑의 crc 체크섬
CompanyFrame = Combine(CompanyFrame, BCC);
return CompanyFrame;
}
private void SendMessage(byte[] SendFrame)
{
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 = 2004; // modbus는 502
IPEndPoint ipep = new IPEndPoint(serverIP, serverPort);
if (sock.IsBound == true) sock.Close();
sock.Connect(ipep);
if (sock.Connected)
{
sock.Send(SendFrame);
byte[] Rdata = new byte[100];
sock.Receive(Rdata);
ReceivedDataProcessing(Rdata);
}
sock.Close();
}
private void ReceivedDataProcessing(byte[] RData)
{
Int16 Datas1 = BitConverter.ToInt16(RData, 32); // 앞의 데이터는 다 쓸모 없어서 스킵
Int16 Datas2 = BitConverter.ToInt16(RData, 36); // 4씩 더하면 됨
Int16 Datas3 = BitConverter.ToInt16(RData, 40);
Int16 Datas4 = BitConverter.ToInt16(RData, 44);
TB_K424.Text = (Datas1 + (Datas2 * 65536)).ToString();
TB_K444.Text = (Datas3 + (Datas4 * 65536)).ToString();
}
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;
}
private static ushort Calc(byte[] data) // CRC코드 생성
{
ushort crc = 0xFFFF;
for (int pos = 0; pos < data.Length; pos++)
{
crc ^= (UInt16)data[pos];
for (int i = 8; i != 0; i--)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else crc >>= 1;
}
}
return crc;
}
private void MqttClient_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
// mqtt 데이터 신호 받았을 때(subscribe) 무슨 처리를 할 지 작성하는 메소드
string mes = Encoding.Default.GetString(e.Message);
Console.WriteLine(mes);
}
}
}
클래스 추가
클래스 코드 작성
EdukitData.cs
\Desktop\IOT\visual studio edukitTest\Mqtt_Dashboard\Mqtt_Dashboard\EdukitData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mqtt_Dashboard
{
public class EdukitData
{
public string X_axis { get; set; }
public string Y_axis { get; set; }
}
}
ReceivedDataProcessing 수정
Form1.cs
\Desktop\IOT\visual studio edukitTest\Mqtt_Dashboard\Mqtt_Dashboard
private void ReceivedDataProcessing(byte[] RData)
{
Int16 Datas1 = BitConverter.ToInt16(RData, 32); // 앞의 데이터는 다 쓸모 없어서 스킵
Int16 Datas2 = BitConverter.ToInt16(RData, 36); // 4씩 더하면 됨
Int16 Datas3 = BitConverter.ToInt16(RData, 40);
Int16 Datas4 = BitConverter.ToInt16(RData, 44);
TB_K424.Text = (Datas1 + (Datas2 * 65536)).ToString();
TB_K444.Text = (Datas3 + (Datas4 * 65536)).ToString();
// 읽어온 x축 값, y축 값을 json 형식으로 만들어서 publish
EdukitData edukit = new EdukitData();
edukit.X_axis = TB_K424.Text;
edukit.Y_axis = TB_K444.Text;
string jsonData = JsonConvert.SerializeObject(edukit, Formatting.Indented); // Json 형식의 데이터 / Indented 띄어쓰기 옵션
mqttClient.Publish("metacamp/sensor", Encoding.Default.GetBytes(jsonData),
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
}
Mosquitto 수정
뷰에서 데이터를 받을 수 있도록 하기 위함
1. 작업관리자 - 서비스탭에서 mosquitto 중지
2. mosquitto.conf 수정
C\Program Files\mosquitto
수정사항 적용 확인
mosquitto.exe -v -c mosquitto.conf
vue index 파일 수정
태그를 위에서 설정한 X_axis, Y_axis로 변경
\Desktop\IOT\MQTT Dashboard Connect\metacamp-frontend\src\views\dashboard\index.vue
data() {
return {
selected: {
// 선택된 장비 정보
deviceId: 1, // TODO: 현재 화면에서 사용할 장비ID(선택 가능하도록 변경하도록 한다.)
deviceName: 'Edge1', // TODO: 현재 화면에서 출력할 장비이름(deviceId선택 시 자동 세팅되도록 한다.)
tagList: ['X_axis', 'Y_axis'] // TODO: 현재 화면에서 출력할 태그 이름(deviceId선택 시 해당 장비의 태그를 설정할 수 있도록 한다.),
// tagList: ['tag1', 'tag2'] // TODO: 현재 화면에서 출력할 태그 이름(deviceId선택 시 해당 장비의 태그를 설정할 수 있도록 한다.)
},
결과
느낀 점
실시간으로 모터가 움직이는 걸 보면서 동시에 데이터가 들어오는 것도 볼 수 있어서 정말 신기했다.
IoT 수업은 손으로 조작하는 게 많아서 나름 현장감이 들고 재미도 있고 좋았다.
중간에 이진법이 좀 헷갈렸지만 얼추 이해가 됐다.
'공부 > Digital Twin Bootcamp' 카테고리의 다른 글
TIL_220125_Backend_CRUD (0) | 2022.01.25 |
---|---|
TIL_220124_Backend (0) | 2022.01.25 |
TIL_220120_IOT (0) | 2022.01.20 |
TIL_220119_IOT (0) | 2022.01.19 |
TIL_220118_IOT (0) | 2022.01.18 |