반응형

아두이노나 ESP8266 관련 프로그래밍을 하다가 보면 가끔 현재 시간 정보가 필요한 경우가 있다. 이럴 경우 쉽게 사용할 수 있는 방법은 예전에 소개한 바 있는 RTC 모듈(Real Time Clock)이라는 장치를 이용하는 것이다. 하지만 장치가 인터넷에 연결되어 있다면 RTC가 필요 없다. 간단하게 현재의 시각을 알 수 있는 방법이 따로 있기 때문이다. 바로 웹서버에 요청(Request)해서 응답(Response)이 오는 HTTP 헤더 정보에 바로 Date, Time 정보가 있는 것이다. 이 정보를 파싱해서 자신의 코드에 삽입해서 이용하면 된다. 


 

그럼 HTTP 헤더란? 


우리가 사용하는 인터넷은 HTTP (Hyper Text Transfer Protocol) 라는 규약(프로토콜)을 이용해서 서버와 브라우저간에 통신을 하게 되고 정보를 교환하게 되는데 이 때 서버에서 브라우저 쪽으로 덧붙여서 보내주는 정보라고 보면 된다. 이 정보 안에는 Date/Time 정보가 있어서 이 것을 이용하면 현재의 시간을 구할 수 있는 것이다. 실제로 구글 크롬에서 F12 버튼을 누르면 HTTP 헤더 정보를 볼 수 있다.


Chrome 에서 F12 를 눌러서 개발자 도구에서 HTTP 헤더를 보는 모습


ESP8266 을 이용해서 서버에 빈(Empty) 요청을 날리면 위와 같이 웹서버에서 Date 라는 Name 으로 날짜, 시간 정보를 준다. 기준은 그리니치 표준시 기준인 GMT 기준으로 제공하기 때문에 우리나라 시간으로 변환을 원한다면 GMT+9시간을 해야한다.


■ 소스


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- retrieve the current time from Google
 
function queryTime()
  conn=net.createConnection(net.TCP, 0
 
  conn:on("connection",function(conn, payload)
    conn:send("HEAD / HTTP/1.1\r\n".. 
               "Host: google.com\r\n"..
               "Accept: */*\r\n"..
               "User-Agent: Mozilla/4.0 (compatible; esp8266 Lua;)"..
               "\r\n\r\n"
                end)
            
  conn:on("receive", function(conn, payload)
    print('Retrieved in '..((tmr.now()-t)/1000)..' milliseconds.')
    print('Google says it is '..string.sub(payload,string.find(payload,"Date: ")
           +6,string.find(payload,"Date: ")+35))
    conn:close()
    end
  t = tmr.now()    
  conn:connect(80,'google.com')
end
 
tmr.alarm(010 * 10001, function() queryTime() end )
 
print("Queries every 10 sec the time from Google server")
print("Stop this by tmr.stop(0)")
cs


소스는 간단하다. google.com 으로 요청을 날리고 응답받은 HTTP 헤더 정보인 payload 에서 Date 부분을 추출해서 표시해주는 소스이다. 여기서는 google 에 요청했지만 다른 웹서버 URL 을 사용해도 상관이 없다.

 

 

실행결과는 위와 같다. 10초 마다 google 서버의 시간을 받아와서 print 해 주고 있다.


이 정보는 HTTP 헤더 정보인 payload 변수에 담겨져 있는 내용인데 payload 정보도 print 해 보았다. 위와 같이 payload 의 마지막 부분에 Date 가 있는 것을 알 수 있다.


소스에 GMT+9 를 하고 적절한 디스플레이를 연결하면 정확한 시간을 보여줄 수 있는 인터넷 시계도 만들 수 있을 것 같다.

반응형
반응형

'NodeMCU Lua 사용해보기 #12 - 인터넷을 통한 릴레이 제어 #1' 글에서는 어떻게 인터넷을 통해서 릴레이(Relay)를 제어할 수 있는지 알아보았다. 그런데 사용을 하다보면 원격지에서 어떤 릴레이가 현재 ON 상태이고 OFF 상태인지 알 수 있는 방법이 없다. 만약 릴레이에 선풍기를 연결해 두었으면 현재 꺼진 상태인지 켜진 상태인지 알 수가 없는 것이다. 그래서 이번에는 NodeMCU 의 gpio 모듈을 이용해서 현재 릴레이의 상태를 알 수 있도록 소스를 수정해서 개선해 보았다. 이전 글에서 모든 연결과 조건은 동일하며 internet_relay.lua 의 소스만 수정하면 된다.

 

■ NodeMCU gpio Module Documentation

https://nodemcu.readthedocs.io/en/dev/en/modules/gpio/

 

위의 NodeMCU Documentation 사이트에 들어가면 gpio 모듈에 대한 설명이 나온다. 이 중에서 gpio.read() 를 이용해서 현재 릴레이의 상태를 알 수 있다.

 

위의 사이트에 들어가면 나오는 gpio.read() 의 사용설명이다. gpio.read(핀번호) 로 현재 해당 gpio 단자의 상태를 알 수 있다. 0 이면 Low, 1 이면 High 의 상태인 것이다. 릴레이에서는 High가 OFF, Low 가 ON 상태 이므로 릴레이 제어에 사용하는 4개의 gpio 핀의 상태를 읽어서 HTML 에서 출력해 주도록 internet_relay.lua 소스를 수정하면 될 것이다. 

 

■ 소스

인터넷 연결을 위한 credentials.lua 와 init.lua 의 소스는 이전 1편의 글과 동일하다.  internet_relay.lua 소스만 수정이 되었다. 그래도 모든 소스를 다시 올려 본다.

 

credentials.lua

1
2
3
4
-- WiFi Connect information
SSID = "WiFi Name(SSID)"
PASSWORD = "WiFi Password"
 
cs

 

init.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- http://deneb21.tistory.com/
-- load credentials, 'SSID' and 'PASSWORD' declared and initialize in there
dofile("credentials.lua")
 
function startup()
    if file.open("init.lua"== nil then
        print("init.lua deleted or renamed")
    else
        print("WiFi Connected...")
        file.close("init.lua")
        -- the actual application is stored in 'application.lua'
        --dofile("webserver.lua")
        dofile("internet_relay.lua")
    end
end
 
print("Connecting to WiFi access point...")
wifi.setmode(wifi.STATION)
wifi.sta.config(SSID, PASSWORD)
wifi.sta.connect()
tmr.alarm(110001, function()
    if wifi.sta.getip() == nil then
        print("Waiting for IP address...")
    else
        tmr.stop(1)
        print("WiFi connection established, IP address: " .. wifi.sta.getip())
        print("You have 3 seconds to abort")
        print("Waiting...")
        tmr.alarm(030000, startup)
    end
end)
 
cs

 

internet_relay.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
--http://deneb21.tistory.com/
--Configure relay ouutput pins, pins are floating and relay opto needs ground to be activated. So pins are kept high on startup.
Relay1 = 1
Relay2 = 2
Relay3 = 3
Relay4 = 4
 
gpio.mode(Relay1, gpio.OUTPUT)
gpio.write(Relay1, gpio.HIGH);
gpio.mode(Relay2, gpio.OUTPUT)
gpio.write(Relay2, gpio.HIGH);
gpio.mode(Relay3, gpio.OUTPUT)
gpio.write(Relay3, gpio.HIGH);
gpio.mode(Relay4, gpio.OUTPUT)
gpio.write(Relay4, gpio.HIGH);
 
print("internet relay standby...")
 
--Create server and send html data, process request from html for relay on/off.
srv=net.createServer(net.TCP)
srv:listen(8080,function(conn) --change port number if required. Provides flexibility when controlling through internet.
    conn:on("receive", function(client,request)
        local html_buffer = "";
        local html_buffer1 = "";
        local html_buffer2 = "";
        local gpio1, gpio2, gpio3, gpio4
        
        local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP");
        if(method == nil)then
            _, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP");
        end
        local _GET = {}
        if (vars ~= nil)then
            for k, v in string.gmatch(vars, "(%w+)=(%w+)&*"do
                _GET[k] = v
            end
        end
 
        html_buffer = html_buffer.."<html><head><meta http-equiv=\"Content-Language\" content=\"en-us\"><meta http-equiv=\"Content-Type\" content=\"text/html;\">";
        html_buffer = html_buffer.."</head><body><font size=5>Internet Relay1 Control (NodeMCU/Lua, ESP8266)</font></br>";
        html_buffer1 = html_buffer1.."<a href=\"?pin=ON1\"><button style=\"width:300\">Relay1 ON</button></a><a href=\"?pin=OFF1\"><button style=\"width:300\">Relay1 OFF</button></a><br/>";
        html_buffer1 = html_buffer1.."<a href=\"?pin=ON2\"><button style=\"width:300\">Relay2 ON</button></a><a href=\"?pin=OFF2\"><button style=\"width:300\">Relay2 OFF</button></a><br/>";
        html_buffer1 = html_buffer1.."<a href=\"?pin=ON3\"><button style=\"width:300\">Relay3 ON</button></a><a href=\"?pin=OFF3\"><button style=\"width:300\">Relay3 OFF</button></a><br/>";
        html_buffer1 = html_buffer1.."<a href=\"?pin=ON4\"><button style=\"width:300\">Relay4 ON</button></a><a href=\"?pin=OFF4\"><button style=\"width:300\">Relay4 OFF</button></a><br/>";
    
    
        local _on,_off = "",""
        if(_GET.pin == "ON1")then
              gpio.write(Relay1, gpio.LOW);
        elseif(_GET.pin == "OFF1")then
              gpio.write(Relay1, gpio.HIGH);
        elseif(_GET.pin == "ON2")then
              gpio.write(Relay2, gpio.LOW);
        elseif(_GET.pin == "OFF2")then
              gpio.write(Relay2, gpio.HIGH);
        elseif(_GET.pin == "ON3")then
              gpio.write(Relay3, gpio.LOW);
        elseif(_GET.pin == "OFF3")then
              gpio.write(Relay3, gpio.HIGH);
        elseif(_GET.pin == "ON4")then
              gpio.write(Relay4, gpio.LOW);
        elseif(_GET.pin == "OFF4")then
              gpio.write(Relay4, gpio.HIGH);                            
        end
 
        -- Relay Status Read
        if(gpio.read(1)==0) then gpio1 = "ON" else gpio1 = "OFF" end
        if(gpio.read(2)==0) then gpio2 = "ON" else gpio2 = "OFF" end
        if(gpio.read(3)==0) then gpio3 = "ON" else gpio3 = "OFF" end
        if(gpio.read(4)==0) then gpio4 = "ON" else gpio4 = "OFF" end
 
        -- Relay Status print
        print("gpio1:"..gpio1)
        print("gpio2:"..gpio2)
        print("gpio3:"..gpio3)
        print("gpio4:"..gpio4)
 
        -- Relay Status print html
        html_buffer2 = html_buffer2.."<br/>Relay1 : "..gpio1;
        html_buffer2 = html_buffer2.."<br/>Relay2 : "..gpio2;
        html_buffer2 = html_buffer2.."<br/>Relay3 : "..gpio3;
        html_buffer2 = html_buffer2.."<br/>Relay4 : "..gpio4;
        html_buffer2 = html_buffer2.."</body></html>";
        
        --Buffer is sent in smaller chunks as due to limited memory ESP8266 cannot handle more than 1460 bytes of data.
        client:send(html_buffer);
        client:send(html_buffer1);
        client:send(html_buffer2);        
        client:close();
        collectgarbage();
    end)
end)
 
cs

 

internet_relay.lua 소스를 보면 65행 에서부터 gpio.read() 모듈을 사용해서 해당 릴레이의 상태값을 얻어온다. 얻어온 값 (0 은 ON, 1 은 OFF) 에 따라 'ON' 또는 'OFF' 문자열을 할당한다. 그리고 html_buffer2 에 릴레이 상태 표시를 위한 HTML 을 만들어 준다. 기존의 html_buffer 와 html_buffer1 에 html_buffer2 를 붙여서 클라이언트(브라우저) 요청 시 HTML 을 출력해 준다.

 

 

https://youtu.be/oa-s8caFprI 

동작 화면을 캡처해 보았다. 릴레이가 동작하는 장면도 같이 찍고 싶었지만 손이 3개면 가능하겠는데 2개라 찍지 못했다. 아무튼 잘 작동한다. 브라우저를 닫았다가 다시 열어도 잘 작동한다.

 

이것을 조금 더 발전시킨다면 인터넷으로 켜고 끌 수 있는 멀티콘센트도 충분히 만들 수 있을 것 같다. 물론 샤오미를 비롯해서 이미 많은 기업에서 Smart Plug 또는 WiFi Plug 라는 이름으로 이미 나와 있지만 말이다.

 

Xiaomi Smart Plug

 

 

 

▶추가사항(2016.09.30) : 위의 소스를 작성하면서 참고한 소스와 사이트를 정리해 본다.

http://www.instructables.com/id/WiFi-Internet-Controlled-Relays-using-ESP8266-Quic/

http://robokits.co.in/download/init.zip

init.zip
다운로드

반응형

+ Recent posts