ESP8266 – Sming 예제 (네트워크)

 

Sming framework은 가장 진보되고 생산성 높은 ESP8266 개발환경을 제공합니다.

본 문서는 Sming 을 이용해 TCP/UDP/IP 통신을 구현하는 예제입니다.

 

 

WiFi 제어 기초

 

ESP8266 모듈의 본래 역할은 WiFi 통신이니 WiFi 통신을 컨트롤하는 기본 코드를 살펴보겠습니다. 새로운 프로젝트를 생성해서 준비하고 아래 코드를 다운로드 받아서 application.cpp 에 입력합니다.

소스코드는 init() 초기화 함수에서 WiFi 기본 설정을 하고, WiFi 이벤트가 발생하면 해당 callback 함수가 호출되는 구조로 되어 있습니다.

ESP8266 모듈이 부팅하면서 init() 함수가 호출되는 시점에도 아직 ESP8266 모듈의 통신 기능등의 초기화 과정은 끝나지 않을 수 있습니다. System.onReady() 함수를 이용해 callback 함수를 등록하면 ESP8266 자체 초기화 과정이 완전히 끝나는 시점에 callback 함수를 호출해줍니다.

init() 함수안에서 위 코드처럼 작성해주면 ESP8266 초기화가 끝났을 때 ready() 함수를 호출해줍니다. 여기에 Serial.println() 코드를 넣어 언제 초기화가 끝나는지 PC로 확인해보세요.

ESP8266 모듈은 AP(Access Point, 공유기) 로 동작할 수 있습니다. 주로 다른 WiFi 장치가 접속한 뒤 AP로 설정된 ESP8266 모듈과 1:1 통신을 할 수 있도록 해주는데 사용합니다. 아래 코드로 AP 설정이 가능합니다.

config() 함수의 파라미터는 차례대로 SSID 이름, 비밀번호, 인증 모드입니다. 자신의 공유기 설정에 맞게 바꿔주세요. 이 작업은 별도의 callback 함수 설정 과정이 없습니다.

아래 코드는 ESP8266 모듈을 Station 모드로 쓸 수 있도록 해줍니다. Station 상태일 때는 다른 AP(공유기)에 연결한 뒤 외부 인터넷 망으로 TCP/IP 접속이 가능합니다. AP+Station 모드를 동시에 할 수 있습니다.

Station 모드 설정을 위해서는 AP(공유기)의 SSID, Password 정보를 입력해야 합니다. 파일 상단에 해당 정보를 입력해서 테스트 해보세요.

아래 코드는 AP/Station 모드일 때 수동으로 ESP8266 모듈의 IP를 지정해주는 과정입니다. DHCP를 이용해 자동으로 IP를 할당받는게 아니라 수동으로 IP를 설정합니다. 개인적으로는 AP 접속할 때 IP 변경되는걸 원하지 않을 때는 공유기에서 MAC 어드레스에 따라 IP 고정되도록 설정합니다. 그래서 이 코드는 잘 안씁니다.

주변의 AP(공유기)를 스캔한 뒤 공유기들의 정보를 출력할 수 있습니다. startScan() 함수를 호출하면 이 작업을 시작합니다. 그리고 스캔이 끝나면 파라미터로 넘겨준 listNetworks() 콜백 함수를 다시 호출해줍니다.

listNetworks() 콜백 함수에서는 scan 된 공유기들의 정보를 뿌려주면 되겠죠?

이걸 잘 활용하면 주변 공유기를 검색해서 자동 접속하도록 코드를 짤 수도 있습니다.

init() 함수의 마지막 라인에서는 waitConnection() 함수를 호출합니다. 이 함수를 호출하면 ESP8266 모듈이 AP(공유기)에 접속했을 때 또는 접속 실패했을 때 등록한 콜백 함수로 알려줍니다.

접속 성공했을 때는 connectOk() 함수가 호출되고, 접속 실패했을 때는 connectFail() 함수가 호출됩니다. connectOk() 함수가 호출되면 이후부터는 다양한 WiFi 네트웍 기능들을 활용할 수 있습니다.

다음 예제부터는 TCP/UDP 통신을 구현하는 방법을 소개할 것입니다. 사전에 TCP/UDP 통신의 차이점과 특징을 알아두시는 것이 도움이 됩니다. 관련된 내용은 인터넷에서 쉽게 찾으실 수 있습니다.

 

 

TCP Client 구현 예제

 

TCP Client를 구현해서 외부의 TCP server에 접속하고 데이터를 주고받는 예제입니다. 아래 링크에서 소스를 받으실 수 있습니다.

 

굉장히 간단한 프로젝트이므로 바로 소스코드 리뷰로 들어가겠습니다. 이클립스에서 [app/application.cpp] 파일을 엽니다. 그리고 초기화 함수인 init() 함수부터 봐야겠죠.

Sming 예제들을 순서대로 리뷰해 왔다면 이미 익숙한 코드일겁니다. init() 함수 안에서는 순서대로 아래 작업을 합니다.

  • Serial 통신 초기화 : Serial.begin(115200)
  • Station 모드 활성화 : WifiStation.config(), WifiStation.enable()
  • 공유기에 접속 : WifiStation.waitConnection()

그럼 공유기에 접속 되었을 때 connectOk() 콜백함수가 호출될겁니다.

 

connectOk() 함수가 호출되면 ESP8266 모듈은 공유기에 붙어서 IP 까지 받은 상태이므로 TCP 클라이언트로 동작할 준비를 해줍니다.

코드 중 주요한 내용은 마지막 2 라인입니다.

  • procTimer.initializeMs() : 주기적으로 TCP 통신으로 데이터를 보낼 수 있도록 타이머를 시작합니다. 6분 간격으로 sendData() 콜백함수가 호출되도록 설정해 줬습니다.
  • sendData() : 지금 바로 데이터 전송을 하기 위해 sendData() 함수를 강제로 한번 호출해줍니다.

 

당연히 sendData() 함수에서는 TCP 통신으로 외부 TCP 서버에 데이터를 보내야겠죠.

tclient 는 TcpClient 의 인스턴스로 TCP client 역할을 하는데 필요한 변수와 함수를 담고 있습니다. 단순히 tclient.connect() 함수를 호출하는걸로 TCP 연결 후 데이터 전송까지 처리됩니다.

단, TCP 연결과 데이터 전송이 제대로 처리되었는지 확인할 방법이 필요하겠죠? 그래서 tclient 인스턴스를 선언할 때 해당 결과를 받을 콜백함수도 같이 지정해줍니다.

  • TcpClient tclient(onCompleted, onReadyToSend, onReceive);

데이터 전송 할 때 onReadyToSend(), 데이터를 받을 때 onReceive(), 데이터 전송이 완료되었을 때 onCompleted() 콜백 함수가 호출됩니다.

 

onReadyToSend() 콜백함수를 살펴 보겠습니다.

이 함수가 호출될 때 TcpConnectionEvent 파라미터가 같이 전달되는 점을 눈여겨보세요. 이 이벤트 값을  검사하면 TCP 연결에 문제가 있는지 알 수 있습니다. 문제가 있다면 sendData() 함수를 강제로 다시 호출하는 등의 조치를 취해서 재시도 해야겠죠.

 

onReceive() 함수는 TCP 서버에서 온 데이터가 있을 때 호출됩니다.

buf 파라미터가 수신된 데이터를 가지고 있으니 이 데이터를 적절히 처리해주면 되겠습니다.

보시다시피… TCP Client 예제를 활용하면 손쉽게 TCP Client 기능을 구현할 수 있습니다. 테스트를 위해서는 펌웨어를 모듈에 올리고 PC나 모바일 폰에 [네트웍 테스트용 앱]을 설치하세요. PC 용 허큘리스 같은 프로그램 설치해서 TCP 서버 설정 후 확인하면 됩니다.

 

 

TCP Server 구현 예제

 

TCP Client 예제와 유사한 방식으로 TCP Server도 구현할 수 있습니다. 뿐만 아니라 이 예제는 Telnet 서버도 함께 구동합니다. TCP 통신 뿐 아니라 PC에서 Telnet 으로 접속해서 명령어를 전송하고 결과를 받아볼 수도 있습니다. 아래에서 프로젝트 파일을 받아서 이클립스에서 불러오세요.

파일 상단에 공유기 ID/PW 설정하는 코드가 있습니다. 이 부분 자신의 설정에 맞게 바꾸세요. 이후에 나오는 예제들 모두 이 부분을 같은 방식으로 바꿔줘야 동작합니다.

 

 

init() 함수부터 보죠. 앞선 예제와 중복되거나 불필요한 부분은 빼고 보겠습니다.

Station 모드로 설정해서 공유기에 접속 시킵니다. 그리고 telnetServer 로 받은 커맨드를 처리해주는 CommandHandler 에 커맨드 [appheap] 을 인식하도록 등록합니다. 아직 Telnet 서버는 시작된 상태가 아닙니다.

  • commandHandler.registerCommand(…, appheapCommand)

만약 appheap 이라는 커맨드가 인식되면 자동으로 appheapCommand() 콜백함수가 호출되도록 설정한겁니다. 그리고 heap 메모리 사이즈를 주기적으로 체크해서 알려주기위해 250ms 간격으로 타이머를 실행합니다. 타이머는 checkHeap() 콜백함수를 호출합니다.

 

공유기에 접속되면 connectOk() 콜백함수가 호출되고, 여기서 startServers() 함수를 호출합니다.

주요 코드를 보면 TCP 서버를 위해 8023 포트를 사용했습니다. 그리고 Telnet 서버를 23번 포트에서 시작했습니다. 추가로 CommandHandler에 [application] 커맨드를 등록하고 이 커맨드가 인식될 때 applicationCommand() 콜백함수가 호출되도록 설정했습니다.

Telnet 서버는 별다른 설정 없이도 알아서 동작합니다. 그리고 CommandHandler에 등록된 커맨드 문자열이 인식되면 자동으로 해당 콜백함수를 호출해줍니다. Telent 서버가 동작하면 2개의 커맨드(appheap, application)를 인식하는 상태가 됩니다. 원하시면 커맨드를 CommandHandler에 몇 개 더 등록해주세요.

주의!!! 텔넷 서버가 동작하므로 Telnet 클라이언트로 접속해서 CommandHandler에 등록된 커맨드를 확인해 볼 수 있습니다. Putty 혹은 터미널 프로그램을 사용해서 ESP8266 모듈 IP로 접속해보세요.

 

TcpServer 인스턴스인 tcpServer를 선언할 때 3개의 콜백함수도 같이 등록해 줬습니다.

  • tcpServerClientConnected() : 새로운 TCP 연결이 생성되었을 때 호출됨
  • tcpServerClientReceive() : TCP 데이터를 받았을 때 호출
  • tcpServerClientComplete() : TCP 연결이 종료될 때 호출

tcpServerClientReceive() 콜백함수가 호출되면 원격에서 전송된 데이터도 함께 들어옵니다. 따라서 이 함수 안에서 원하는 데이터 처리를 해주면 됩니다.

tcpServerClientReceive() 에는 데이터 수신 후 다시 TCP 로 데이터를 송신하는 코드도 함께 있으므로 참고하세요.

  • client.sendString(“sendString data\r\n”, false);
  • client.writeString(“writeString data\r\n”,0 );

 

TCP client, TCP server 예제를 천천히 훝어보시면 ESP8266 모듈 간 TCP 통신을 구현하실 수 있으실겁니다. 특히 한 쪽 ESP8266 모듈을 AP(공유기) 모드로 돌려서 1:1 직접 연결 후 TCP 통신이 가능합니다.

 

 

UDP server, client 예제

 

UDP 서버, 클라이언트 구현 예제는 TCP 보다 훨씬 단순합니다. 아래 프로젝트 파일들을 받아서 이클립스에서 불러오세요.

 

application.cpp 파일에서 init() 함수부터 살펴보면…

Serial 통신 초기화하고 WifiStation 모드 활성화, 공유기에 접속을 시도합니다. 접속되면 onConnect() 콜백 함수가 호출됩니다.

 

onConnect() 콜백함수가 호출되면 바로 1234번 포트로 UDP 서버를 실행합니다.

upd.listen(EchoPort) 함수가 UDP 서버를 실행합니다. udp 는 UdpConnection 객체의 인스턴스인데 선언할 때 onReceive() 콜백함수를 지정해 뒀습니다.

  • UdpConnection udp(onReceive);

이름처럼 onReceive() 콜백함수는 UDP 포트로 데이터를 받았을 때 호출됩니다.

받은 데이터를 그대로 상대편에게 돌려주는 코드가 들어가 있습니다. 실제 데이터를 다시 전송하는 부분은 아래 코드입니다.

  • udp.sendStringTo(remoteIP, EchoPort, text);

 

UDP 서버로 동작하는 코드는 위에 있는 코드만 보시면 됩니다. 그럼 UDP 클라이언트로 동작할 때는 어떻게 하는지 보겠습니다.

앞선 리뷰에서 공유기에 연결되었을 때 onConnect() 콜백함수가 호출된다고 했습니다. 이 함수안에 주석처리된 코드가 있었던걸 보셨나 모르겠네요.

UDP client 로 외부의 UDP 서버에 연결할 때는 위 코드처럼 사용하시면 됩니다.

  • UdpConnection udp(onReceive);

이렇게 udp 인스턴스를 선언해주고 udp.connect(), udp.sendString() 함수를 이용해서 데이터를 보내면 됩니다.

이상의 코드를 활용하면 2대의 ESP8266 모듈을 UDP 통신으로 직접 연결할 수 있습니다.

 

 

TCP/IP 통신으로 MQTT client 만들기

 

MQTT는 IoT 세계에 쓰기 적당한 메시징 프로토콜로, 흔히 컴퓨터를 위한 트위터라 불려집니다. 사람들은 트위터 서버에 접속해서 팔로우를 하고 메시지 작성을 하죠? 작성한 메시지는 팔로워들에게 전달되구요. 마찬가지로 라즈베리파이같은 서버에(혹은 PC 등등) MQTT Broker 라 불리는 서버를 설치한 뒤, 다양한 컴퓨터들이 여기에 접속해서 팔로우(메시지 받기, subscribe)나 메시지 작성(publish)을 할 수 있습니다.

비록 라즈베리파이 같은 MQTT 브로커용 서버가 필요하긴 하지만, MQTT를 이용하면 임베디드 장치에서 생성한 메시지를 데이터가 필요한 다양한 곳에 손쉽게 뿌릴 수 있습니다. 대부분의 일은 브로커가 다 처리해주기 때문에 임베디드 장치에서는 간단히 client를 만들어 넣으면 됩니다.그래서 MQTT를 이용한 IoT 프로젝트들을 만들어 공개한는 사례가 꽤 많습니다.

MQTT 프로토콜에 대한 자세한 소개와 라즈베리파이에 설치하는 방법은 아래 링크를 참고하세요. 라즈베리파이가 없으시면 PC에 MQTT 브로커(서버)를 설치해서 테스트하면 됩니다. 링크에서 Mosquitto MQTT broker 윈도우용을 받으세요.

MQTT 브로커가 준비되셨으면 아래 링크에서 예제 프로젝트를 다운로드 받아 이클립스에 불러오세요.

 

이 예제는 ESP8266 모듈에 DHT22 온습도 센서와 OLED 디스플레이를 장착한 뒤, 센서로 측정한 온습도 값을 MQTT 브로커로 전송하도록 만든 예제입니다. 그리고 MQTT 브로커에서 메시지를 받으면 OLED 디스플레이에 표시합니다. 예제를 통해 데이터 흐름을 제어하는 방법을 익히시면 다양한 플랫폼과 연동이 가능한 IoT 서비스를 구축할 수 있습니다.

ESP8266 모듈에 DHT22 센서를 연결합니다.

  • ESP8266 ==> DHT22
  • 3V ==> VCC
  • GND ==> GND
  • GPIO14 (D5) ==> DAT : 데이터 라인

ESP8266 모듈에 OLED 디스플레이를 아래와 같이 연결합니다. OLED 디스플레이는 I2C 방식, SSD1306 드라이버 칩을 사용한 모델입니다.

  • ESP8266 ==> OLED (I2C)
  • 3V ==> VCC
  • GND ==> GND
  • D3(GPIO0) ==> SCL
  • D4(GPIO2) ==> SDA

mqtt_exam

 

이제 소스 분석을… application.cpp 파일을 엽니다. 먼저 파일 상단에 정의되어 있는 WIFI_SSID, WIFI_PWD 값을 자신의 공유기 설정에 맞게 바꿔줍니다.

init() 함수부터 보겠습니다.

Serial 통신 초기화하고, OLED 디스플레이와 DHT22 초기화를 합니다. WiFi Station 모드 활성화 하고 공유기에 연결합니다. 공유기 연결이 성공하면 connectOk() 콜백 함수가 호출되도록 설정했습니다.

  • WifiStation.waitConnection(connectOk, 20, connectFail);

 

공유기에 연결되면 connectOk() 콜백 함수가 호출됩니다.

중요한 부분은 startMqttClient() 함수를 호출하는 부분입니다. 여기서 MQTT 브로커에 연결하고 메시지를 받고 싶은 토픽(topic)을 구독(subscribe)합니다.

그리고 타이머를 하나 생성하는데, 타이머는 주기적으로 센서의 값을 읽어 MQTT 브로커에 전송하는 역할을 합니다. 시간 간격이 무척 길게 설정되어 있는데, 테스트를 위해 이 간격을 짧게 수정해서 쓰세요.

  • procTimer.initializeMs(30*60*1000, publishMessage).start();

 

startMqttClient() 함수를 자세히보죠.

mqtt 는 MqttClient 객체입니다. MQTT client 기능을 수행합니다. 파일 상단에 mqtt 인스턴스를 선언하는 부분에 접속을 위한 정보들이 기재되어 있어야 합니다. 서버(혹은 IP)를 정확히 기재해줘야 합니다.

위 코드에는 MQTT 메시지를 받았을 때 onMessageReceived() 콜백함수가 호출되도록 설정되어 있습니다.

startMqttClient() 함수는 차례대로 MQTT 동작을 위한 설정을 합니다.

  • mqtt.setWill() : ESP8266 모듈이 예기치않게 접속이 끊어졌을 때 다른 기기들에게 해당 사실을 알리는 역할을 합니다. 이 기능을 LWT(Last Will and Testament)라고 부릅니다. 이 기능을 위해 특정한 토픽(last/will)을 사용합니다.
  • mqtt.connect(“client_name”) : 브로커에 접속하는 역할을 합니다. 등록될 때 사용될 client 이름을 적절히 지정해주면 됩니다.
  • mqtt.subscribe(“your_topic”) : 특정 토픽을 구독(subscribe) 합니다. 해당 토픽으로 발행(publish)되는 메시지들을 모두 받을 수 있습니다. 토픽 이름을 자신의 설정에 맞게 바꾸세요. 여러개의 토픽을 구독해도 됩니다.

 

앞서 connectOk() 콜백 함수에서 타이머를 실행했죠? 타이머 콜백 함수인 publishMessage() 함수를 살펴보겠습니다.

만약 현재 MQTT 브로커와의 연결이 끊어진 상태라면 startMqttClient() 함수를 다시 호출합니다.

그리고 DHT22 센서에서 온습도 값을 읽고 전송할 메시지를 준비합니다. [T=xx.xx, H=xx.xx] 형태의 문자열입니다.

메시지를 MQTT 브로커로 전송합니다. topic 이름에 다른 기기와 공유하는 적당한 토픽 이름을 넣으세요.

  • mqtt.publish(“your_topic”, message);

 

주기적으로 MQTT 메시지를 전송하는 기능까지는 완료되었습니다. 이제 구독(subscribe)한 토픽의 메시지가 도착했을 때의 처리 루틴인 onMessageReceived() 함수를 보겠습니다.

간단합니다. Serial 통신으로 디버그 메시지를 출력해주고 OLED 디스플레이에도 출력해줬습니다.

아래 if 조건문이 들어간 이유는 만약 구독(subscribe)하는 토픽과 발행(publish)하는 토픽이 같은 경우, 발행한 메시지가 다시 자신에게로 돌아오기 때문입니다. 내가 발행한 메시지는 표시하지 않고 다른 기기에서 보내준 메시지만 OLED에 표시하기 위해 넣어둔 조건문입니다.

  • if(message.indexOf(“T=”) != 0) // do not display my message

 

자, 이제 프로젝트를 빌드하고 PC, 모바일 폰, 라즈베리파이 등 다른 장치와 연동해서 메시지를 주고 받는지 테스트 해보세요. 제 경우, 꽤나 만족스러운 성능을 보여줬습니다!!

mqtt_exam

 

 

 

 

참고자료

 

 

 

Post Author: TORTUGA

TORTUGA
궁금하신 점은 새로 개편한 홈페이지의 QnA 게시판을 이용해주세요!!!!!!! http://www.hardcopyworld.com/gnuboard5/bbs/board.php?bo_table=qna

댓글 남기기

이메일은 공개되지 않습니다.