이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.

이번 글에서는 지난 글 STM32F103으로 ESP8266을 이용한 소켓 통신하기 -제1편STM32F103으로 ESP8266을 이용한 소켓 통신하기 -제2편에서 STM32F103CBT6이 서버로 동작하도록 프로그래밍했습니다. 이번 글에서는 windows 10에서 동작하는 client 프로그램을 만들어 보겠습니다.

 

프로그래밍 환경은 다음과 같습니다.

① 개발 환경 : Visual Studio Community 2019

② 프로그래밍 언어 : C++

③ MFC 라이브러리

 

1. 프로젝트 만들기

 

[파일(F)] 메뉴에서 [새로 만들기]를 클릭한 후에, [MFC 앱]을 선택하고 [다음]을 클릭합니다.

프로젝트 만들기

 

② [프로젝트 이름]을 입력하고 [만들기] 버튼을 클릭합니다.

 

③ [애플리케이션 종류]를 [대화 상자 기반]으로 바꾸고 [다음] 버튼을 클릭합니다.

 

④ [다음] 버튼을 클릭합니다.

 

⑤ [시스템 메뉴(Y)]의 체크를 해제하고 [대화 상자 제목(G)]에 프로그램 타이틀을 입력합니다.

⑥ [Windows 소켓(W)]에 체크하고 [마침] 버튼을 클릭합니다.

 

⑦ [프로젝트(P)] 메뉴의 [ESP8266ClientMFC 속성(P)]를 클릭하여 프로젝트 속성 페이지를 엽니다. 프로젝트 속성 페이지의 [구성 속성] - [고급]에서 문자 집합을 [멀티바이트 문자 집합]으로 바꾼 다음 [확인] 버튼을 누릅니다. STM32오와 같은 대부분의 MCU 프로그램에서는 ANSI 코드의 문자를 사용하기 때문에 이렇게 지정합니다. PC의 소프트웨어가 유니코드나 UTF-8 코드를 사용해야 하는 경우에는 PC의 소프트웨어에 문자 코드를 변환해 주는 루틴을 넣어야 합니다. (ex: MultiByteToWideChar(), WideCharToMultiByte())

 

⑧ [클래스 뷰]를 열고, 프로젝트명 [ESP8266ClientMFC]를 마우스 오른쪽 버튼으로 클릭한 다음 [추가], [클래스(C)]를 선택합니다.

 

⑨ 팝업 창에 다음과 같이 입력하여 CSocket 클랫래스에 기반하는 CConnectSocket 클래스를 추가합니다.

 

 

 

2. 코딩하기

 

① 위와 같이 CConnectSocket 클래스를 추가하면 소스프로그램에 ConnectSocket.h가 추가됩니다. 소켓에 데이터가 들어왔을 때에 실행되는 함수 OnReceive()와 소켓이 닫혔을 때에 실행되는 함수 OnClose()를 오버라이드 하기 위해서 ConnectSocket.h 파일이 다음과 같이 되도록 코드를 입력합니다.

#pragma once
#include <afxsock.h>
class CConnectSocket :
	public CSocket
{
public:
	void OnClose(int nErrorCode);
	void OnReceive(int nErrorCode);
};

 

② ConnectSocket.cpp에 다음과 같이 입력합니다.

#include "pch.h"
#include "ConnectSocket.h"
#include "ESP8266ClientMFCDlg.h"


void CConnectSocket::OnClose(int nErrorCode)
{
	ShutDown();
	Close();

	CSocket::OnClose(nErrorCode);
	((CESP8266ClientMFCDlg*)AfxGetMainWnd())->OnBnClickedBttnDisconnect();
	AfxMessageBox(_T("ERROR:Disconnected from server!"));
	//::PostQuitMessage(0);
}

void CConnectSocket::OnReceive(int nErrorCode)
{
	DWORD Len;

	IOCtl(FIONREAD, &Len);
	unsigned char* pBuffer = new unsigned char[Len + 1];
	memset(pBuffer, 0, Len + 1);
	Len = Receive(pBuffer, Len);
	if (Len > 0)
    	((CESP8266ClientMFCDlg*)AfxGetMainWnd())->AddListFromServer((CString)pBuffer);
	delete[] pBuffer;
	CSocket::OnReceive(nErrorCode);
}

 

OnBnClickedBttnDisconnect();와 AddListFromServer(); 부분에 빨간 밑줄이 나타날 것입니다. 이것은 아직 위의 두 함수를 만들지 않았기 때문에 나타나는 현상입니다. 나중에 위의 함수들을 만들면 에러는 사라집니다.

 

OnClose() 함수는 소켓 연결이 끊어지거나 종료되었을 때에 호출되는 함수입니다. 위의 프로그램에서는 BttnDisconnect 버튼이 눌린 것과 같은 동작을한 후에, 화면에 "ERROR:Disconnected from server!"라는 내용의 MessageBox를 출력합니다.

 

OnReceive() 함수는 소켓으로 데이터가 들어왔을 때에 호출되는 함수입니다. 위 프로그램에서는 CESP8266ClientMFCDlg 클래스의 AddListFromServer() 함수를 호출하여, 수신한 내용을 메인 대화 상자 내에 있는 ListBox에 추가합니다.

 

③ [리소스 뷰]에서 [ESP8266ClientMFC] - [ESP8266ClientMFC.rc] - [Dialog] - [IDD_ESP8266CLIENTMFC_DIALOG]를 선택하여 화면을 다음 그림과 같이 구성합니다.

 

서버의 주소를 입력하는 EditBox를 오른쪽 마우스 버튼으로 클릭하여 나타나는 팝업창에서 속성을 선택한 후에 ID를 IDC_EDIT_SERVER로 입력합니다. 동일한 EditBox를 다시 마우스 오른쪽 버튼으로 클릭하여 나타나는 팝업창에서 [변수 추가(B)]를 클릭합니다.

 

④ 다음의 그림에서와 같이 CEdit형 변수 m_EditServer를 추가합니다.

 

⑤ 위 ④와 같은 방법으로 Port를 입력하는 EditBox의 ID는 IDC_EDIT_PORT로 지정하고, 이름을 m_EditPort로 하는 변수를 추가합니다.

같은 방법으로 서버 주소와 포트를 입력하는 EditBox 아래의 비활성화 된 EditBox는 ID를 IDC_EDIT_SEND로 지정하고, 이름을 m_EditSend로 하는 변수를 추가합니다. 다른 EditBox와 달리 이 EditBox는 시작할 때에 비활성화 되도록 속성값의 Disable를 True로 설정합니다. [Send] 버튼을 누르면 이 EditBox에 입력한 내용을 서버로 전송합니다.

 

⑥ IDC_EDIT_SEND 밑의 큰 컨트롤은 ListBox로 ID는 IDC_LIST_FROMSERVER로 지정하고, ④와 같은 방법으로 [컨트롤 형식]은 LISTBOX, 변수명은 m_ListFromServer, 변수 형식은 CListBox로 하는 변수를 추가합니다. IDC_LIST_FROMSERVER의 속성 중 [Sort]를 False로 설정합니다. 이렇게해야 서버로부터 오는 메시지를 시간 순서대로 출력합니다.

 

⑦ 다섯 개의 버튼 중 [Exit] 버튼을 제외한 네 개의 버튼들은 각각의 ID를 IDC_BTTN_CONNECT, IDC_BTTN_DISCONNECT, IDC_BTTN_SEND, IDC_BTTN_CLEAR로 지정합니다. 버튼들 중 IDC_BTTN_DISABLE과 IDC_BTTN_SEND의 [Disabled] 속성을 True로 설정합니다. 서버와 연결되지 않은 상태에서는 [Disconnect], [Send]가 동작할 필요가 없기 때문입니다. 아래의 코딩 작업에서 서버와 연결되면 이 두 버튼을 활성화시키도록 할 것입니다.

 

⑧ [클래스 뷰]에서 [ESP8266ClientMFC] 아래의 [ESP8266ClientMFCDlg]를 마우스 오른쪽 버튼으로 클릭한 다음 [함수 추가(U)]를 클릭한 다음 AddListFromServer() 함수를 추가합니다.

ESP8266ClientMFCDlg.cpp에 생성된 AddListFromServer() 함수의 내용을 다음과 같이 만듭니다.

void CESP8266ClientMFCDlg::AddListFromServer(CString str)
{
	m_ListFromServer.AddString(str);
	m_ListFromServer.SetCurSel(m_ListFromServer.GetCount() - 1);
}

 

⑨ [Connect], [Disconnect], [Send], [Clear] 버튼을 각각 더블 클릭하여 OnBnClickedBttnConnect(), OnBnClickedBttnDisconnect(), OnBnClickedBttnSend(), OnBnClickedBttnClear() 함수들을 만든 다음에, 각각의 함수 내용들을 다음과 같이 채웁니다.

void CESP8266ClientMFCDlg::OnBnClickedBttnConnect()
{
	int nPort;
	CString str;


	m_EditPort.GetWindowText(str);
	nPort = _ttoi(str);
	m_EditServer.GetWindowText(str);
	m_ConnectSocket.Create();
	if (m_ConnectSocket.Connect(str, nPort) == FALSE) {
		AfxMessageBox(_T("ERROR : Failed to connect Server"));
	}
	else {
		((CButton*)GetDlgItem(IDC_BTTN_SEND))->EnableWindow(TRUE);
		((CButton*)GetDlgItem(IDC_BTTN_DISCONNECT))->EnableWindow(TRUE);
		((CButton*)GetDlgItem(IDC_BTTN_CONNECT))->EnableWindow(FALSE);
		((CButton*)GetDlgItem(IDCANCEL))->EnableWindow(FALSE);
		m_EditServer.EnableWindow(FALSE);
		m_EditPort.EnableWindow(FALSE);
		m_EditSend.EnableWindow(TRUE);
	}

}


void CESP8266ClientMFCDlg::OnBnClickedBttnDisconnect()
{
	m_ConnectSocket.ShutDown();
	m_ConnectSocket.Close();
	((CButton*)GetDlgItem(IDC_BTTN_SEND))->EnableWindow(FALSE);
	((CButton*)GetDlgItem(IDC_BTTN_DISCONNECT))->EnableWindow(FALSE);
	((CButton*)GetDlgItem(IDC_BTTN_CONNECT))->EnableWindow(TRUE);
	((CButton*)GetDlgItem(IDCANCEL))->EnableWindow(TRUE);
	m_EditServer.EnableWindow(TRUE);
	m_EditPort.EnableWindow(TRUE);
	m_EditSend.EnableWindow(FALSE);
}



void CESP8266ClientMFCDlg::OnBnClickedBttnSend()
{
	CString str;
	DWORD Len;

	((CEdit*)GetDlgItem(IDC_EDIT_SEND))->GetWindowText(str);
	Len = str.GetLength();
	char* pBuffer = new char[Len];
	//WideCharToMultiByte(CP_UTF8, 0, str, -1, pBuffer, Len, NULL, NULL);
	//m_ConnectSocket.Send(pBuffer, Len);
	m_ConnectSocket.Send(str, Len);
	((CEdit*)GetDlgItem(IDC_EDIT_SEND))->SetWindowText(_T(""));
	delete[] pBuffer;
}


void CESP8266ClientMFCDlg::OnBnClickedBttnClear()
{
	m_ListFromServer.ResetContent();
}

 

⑩ ESP8266ClientMFCDlg 클래스에 CConnectSocket 멤버 변수를 만들기 위해 ESP8266ClientMFCDlg.h의 맨 위 부분에서 "ConnectSocket.h" 파일을 포함시키고, ESP8266ClientMFCDlg 클래스에 CConnectSocket 클래스인 m_ConnectSocet 변수를 추가합니다. 

// ESP8266ClientMFCDlg.h: 헤더 파일
//

#pragma once

#include "ConnectSocket.h"
protected:
	CConnectSocket m_ConnectSocket;
	CListBox m_ListFromServer;
	CEdit m_EditServer;
	CEdit m_EditPort;
	CEdit m_EditSend;

CConnectSocket m_ConnectSocket; 부분만 입력한 것이고 나머지 변수들은 앞에서 코딩하는 과정에서 visual studio가 입력해 준 것들인데, m_ConnectSocket을 정의할 위치를 예시하기 위해 같이 나열했습니다.

 

⑪ 서버 주소와 포트의 초기값을 주기 위해서 ESP8266ClientMFCDlg.cpp 상단에 다음의 매크로 상수를 입력합니다.

#define DEFAULT_IP		"172.30.1.99"
#define DEFAULT_PORT		"15598"

ESP8266ClientMFCDlg.cpp 안에 있는 BOOL CESP8266ClientMFCDlg::OnInitDialog() 함수를 다음과 같이 만듭니다. // TODO 다음의 두 행을 입력했습니다.

BOOL CESP8266ClientMFCDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 이 대화 상자의 아이콘을 설정합니다.  응용 프로그램의 주 창이 대화 상자가 아닐 경우에는
	//  프레임워크가 이 작업을 자동으로 수행합니다.
	SetIcon(m_hIcon, TRUE);			// 큰 아이콘을 설정합니다.
	SetIcon(m_hIcon, FALSE);		// 작은 아이콘을 설정합니다.

	// TODO: 여기에 추가 초기화 작업을 추가합니다.
	m_EditServer.SetWindowText(DEFAULT_IP);
	m_EditPort.SetWindowText(DEFAULT_PORT);

	return TRUE;  // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.
}

 

앞의 제1편과 제2편에서 만든 장치의 ESP8266 모듈 초기화에 대략 5초 ~ 10초 정도 소요됩니다. 초기화가 끝난 다음에 이글에서 만든 프로그램을 실행시키고 [Connect] 버튼을 누릅니다. 정상적이라면 [Connect], [Exit] 버튼이 비활성화되면서, [Disconnect], [Send] 버튼과 Send할 내용을 입력할 EditBox가 활성화됩니다.

 

 EditBox에 서버로 전송할 내용을 입력하고 [Send] 버튼을 누르면, 전송한 내용 앞에 "From Server : "가 붙은 내용이 ListBox에 추가 됩니다.

 

다음 그림은 위 프로그램과 STM32F103이 연동하여 동작한 결과 화면입니다. 화면의 예는 KT에서 제공한 공유기의 내부망에서 통신한 결과입니다만, 외부망에서도 정상적으로 통신이 되는 것을 확인했습니다. 다만, 외부에서 통신하려면 공유기에서 포트포워딩을 설정해 주어야 합니다. 또한 DDNS 서비스를 이용하면 172.30.1.99와 같은 ip가 아니라 도메인명으로도 접속할 수 있습니다.

 

현재 진행하고 있는 이 프로젝트는 ESP8266 모듈의 AT 명령어를 통한 전달이므로 암호화 등 보안 조치가 전혀 없다는 사실을 다시 한 번 밝힙니다.

 

 

소스프로그램을 zip 파일로 압축하여 첨부합니다.

ESP8266ClientMFC.zip
0.13MB

 

다음 글에서는 android studio에서 이 글의 프로그램과 비슷한 기능을 실행하는 앱을 만들어 볼 예정입니다. 즉, 스마트 폰에서 ESP8266 모듈을 통해 STM32F103과 통신을 해 보겠습니다.

 

블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,