중년코딩
264
2019-05-09 08:14:30 작성 2019-05-14 11:24:52 수정됨
6
1971

[중년코딩|1-2]텃밭물주기, 코딩을 해봅니다.


https://okky.kr/article/576810?note=1685622 [중년코딩|텃밭물주기 1편] 게으른 사람이 밭에 물을 주는 법

직장생활 15년만에 Python으로 코딩을 시작한 게으른 중년 대머리 아저씨입니다. 자세한 소개는 전편을 참조해 주십시오.

오늘은 코딩을 합니다...만, 어제 과음을 한 관계로 일부 코딩은 완성하지 못했습니다. 처음부터 면목이 없네요. 주기적으로 실행하고 끄는 로직을 어떻게 설정하는 게 편할지 아직 결정을 못했습니다(좋은 의견 주시면 매우 감사합니다.)



옥상 텃밭에 물을 주는 프로그램입니다. 말씀드렸던 것처럼 기성품을 쓰면 편합니다. 구글 홈한테 말 한 마디하면 켜주고 꺼주고 스케줄링도 가능합니다.

하지만 채울 수 없는 게으름 탓에... 고생을 사서 해봅니다.


I. 목적

1. 옥상 텃밭에 아침 6시에 자동으로 물을 10분간 준다.

2. 비가 오는 날에는 안 준다.

3. 물을 주고 있는 상태가 30분 이상 계속되면 알림을 준다.

sonoff 로그인을 위한 config파일입니다. sonoff-python모듈에서 읽어 갑니다. 모듈 제작자가 만들라고 한 대로 만들어 줍니다. username에는 국가코드 앞에 +를 붙여줘야 합니다. 제작자가 이건 설명을 안 해줬는데... 지역 코드도 as로 넣어줍니다.

#config.py
username='+8210XXXXXXXX'
password='XXXXXXXXXXXXX'
api_region='as'
user_apikey=None
bearer_token=None
grace_period=600


sonoff 모듈 상태를 확인하기 위한 ewelink.py파일입니다. ...어라, onoff 함수도 만들어 놨었네요. 내가 미쳐..

어쨌든, sonoff 모듈로 "s"를 생성해주고(이거 뭐라고 불러야 하죠? 인스턴스?) 그걸로 디바이스 목록을 불러옵니다. pprint로 리턴값을 찍어보면 무지하게 많은 내용이 나옵니다... 중국제품이라 중국어도 섞여 나옵니다. 필요한 놈은 device id, 별명, 켜지고 꺼진 시간, 연결 여부, 스위치 상태입니다. ...연결 여부에 따라서 예외처리도 필요하겠군요... 쩝. 어쨌든. get_api_region은 없는 함수라서 주석처리해줍니다.

device_status()함수로 상태값을 불러와서 딕셔너리로 저장한 후 반환해줍니다.

turn_onoff()함수는 디바이스id를 넣어서 호출하면 그 디바이스id에 해당하는 스위치들을 켜주거나, 꺼줍니다. *args를 꼭 한 번 써보고 싶었습니다. 

#ewelink.py
import sonoff
import config
import pprint

# https://github.com/lucien2k/sonoff-python

s = sonoff.Sonoff(config.username, config.password, config.api_region)
devices = s.get_devices()

# update config
# config.api_region = s.get_api_region
config.user_apikey = s.get_user_apikey
config.bearer_token = s.get_bearer_token

def device_status():
    response={}
    d={}
    for device in devices:
        d={
            'deviceid':     device['deviceid'],
            'name':         device['name'],
            'offlineTime':  device['offlineTime'],
            'onlineTime':   device['onlineTime'],
            'online':       device['online'],
            'switch':       device['params']['switch']
        }
        response[device['name']]=d
    return response    

def turn_onoff(onoff,*args):
    for arg in args:
        s.switch(onoff,arg,None)
    
if __name__ == "__main__":
    pprint.pprint(device_status())

...다 쓸 데 없습니다. 어차피 물 켜고 끄는 스위치는 하나이고, 디바이스id 정해져 있고... 그냥 아래 코드만 넣어도 충분합니다.

import sonoff
import config

DEVICE_ID='xxxxxxxxxxxxx'

s = sonoff.Sonoff(config.username, config.password, config.api_region)
devices = s.get_devices()
s.switch('on',DEVICE_ID,None)

쩝...

아, 처음 디바이스 id를 알아내려면 한 번은 디바이스 값을 출력했어야 했다는 걸로 위안을 삼읍시다. 그래도 그냥 pprint 하나만 해봤으면 되는 거긴 한데, 엎질러진 물입니다.

이제 날씨를 알아보러 갑니다. getweather.py입니다.

#getweather.py
from bs4 import BeautifulSoup
import urllib.request as MyURL
import pprint

# http://www.kma.go.kr/images/weather/lifenindustry/timeseries_XML.pdf

CODE_CONV = {
    'sky': ['맑음','구름조금','구름많음','흐림'],
    'pty': ['없음','비','비/눈','눈/비','눈'],
    'wd' : ['북','북동','동','남동','남','남서','서','북서'], 
    'day': ['오늘','내일','모레']
}
CODE_DICT = {
    'tm':'발표시간',
    'hour':'시간',
    'day':'날짜',
    'temp':'현재온도',
    'tmx':'최고온도',
    'tmn':'최저온도',
    'sky':'하늘',
    'pyt':'강수상태',
    'wfkor':'날씨',
    'pop':'강수확률',
    'r06':'6시간강수량',
    's06':'6시간적설량',
    'r12':'12시간강수량',
    's12':'12시간적설량',
    'wdkor':'풍향',
    'reh':'습도'
}

def rss_read():
    RSS_URL='http://www.kma.go.kr/wid/queryDFSRSS.jsp?zone=OOOOOOOO'
    response = MyURL.urlopen(RSS_URL)
    weather = BeautifulSoup(response,"html.parser")
    return weather

def print_weather(weather, *args):
    for data in weather.findAll('data'):
        for arg in args:
            if arg in CODE_CONV.keys():
                print(f"{CODE_DICT[arg]}: {CODE_CONV[arg][int(data.find(arg).string)]}")
            else:
                print(f"{CODE_DICT[arg]}: {data.find(arg).string}")
        print("")
        #print("시간:",data.tmef.string)

def get_rain(weather):
    # 6시간 강수량
    response=float(weather.find('data',{'seq':0}).r06.string)
    return response

def main():
    weather = rss_read()

if __name__ == "__main__":
    main()

1편에서 말씀드린 것처럼 feedparser, elementTree 사용법을 몰라서, 그냥 해봤던 뷰티풀 숲으로 갑니다. 아이고... 

아까 테스트로 스위치로 물을 틀어놓고 까먹고 있었네요. 아이고... 수도요금...

엎질러진 물입니다.

진짜 물이네요..

어쨌든.

기상청 API는 가입해야 하고 귀찮아서, RSS 가져옵니다. RSS는 아주 불친절하게도 값들이 코드로 되어 있습니다. 저는 사람이니까 읽기 편하게 변환해주기 위해 딕셔너리를 두 개 만듭니다. CODE_CONV는 0,1,2,3 등 숫자로 값을 주는 하늘 상태, 날씨, 풍향, 날짜 값을 변환하기 위한 놈입니다. CODE_DICT는 tm, temp, r06 등으로 표시되는 놈을 변환하기 위한 놈입니다.

rss_read 함수에서는 urllib.request로 rss를 읽어와서 뷰티풀 숲에 파싱해달라고 패싱해줍니다. 아하하하. 리턴값은 weather 변수에 저장합니다.

print_weather함수에서는 weather 변수를 받아서 필요한 필드들만 출력해줍니다. CODE_CONV와 CODE_DICT로 보기 좋게 변환도 해줍니다. *args 구문을 꼭 써보고 싶어서 역시 또 써봅니다.

오류가 납니다... 

숫자로 된 값들은 CODE_CONV로 변환해야 하는데,... if문을 하나 추가해서 CODE_CONV에 있는 필드는 CODE_CONV로 변환하고, 아니면 그냥 돌리는 걸로 함수를 짭니다. 뭔가 잘 짠 구조인 거 같아 뿌듯합니다.

한 줄짜리 get_rain()함수는 제가 필요한 6시간 예상 강수량을 가져오는 함수입니다.

그렇습니다. 이거 한 줄이면 되는 거였습니다. 변환이고 뭐고 다 필요없었는데... 왜 저걸 다 짜고 있었을까요.


실제 물을 주는 모듈을 만듭니다.

import ewelink
import getweather

rss = getweather.rss_read()
rain = getweather.get_rain(rss)
device_id=ewelink.device_status()['옥상물주기']['deviceid']

if rain < 20:
    ewelink.turn_onoff('on',device_id)

왜 굳이 모듈을 분리했느냐... 제가 만든 모듈을 import하는 거를 한 번 해보고 싶었습니다. 

날씨를 불러와서 rss에 넣고, rss에서 강수량을 불러오고(이거 더 간단히 할 수 있었을 거 같다는 생각이 확 드네요...), ewelink에서 이름이 '옥상물주기'인 놈의 deviceid를 가져옵니다. 그리고 강수량이 20mm 이하이면, 디바이스를 켭니다. 이제 저 놈을 crontab으로 매일 아침 실행해주면 됩니다....(앗???!!!! 이거 윈도우 서버인데???)

그냥 정해진 device_id 집어 넣으면 더 간단하긴 할 것을....

이러다 보니 시간이 부족합니다.

아직 펜딩 사항들은 다음과 같습니다.

1. on 스케줄링: 서버가 리눅스가 아니라 윈도우입니다. 크론탭이 없다!!!

2. off 스케줄링: 끄는 걸 안 만들었습니다... 수도요금 어쩔...

a. 파이썬 모듈을 계속 돌아가게 하고 threading을 써서 끈다

b. 파이썬 모듈을 10분에 한 번씩 실행시키고 기기 상태값+구동시간을 txt 파일로 저장해서 그 값에 따라 끈다.

뭘로 할지 모르겠습니다(좋은 의견 부탁드립니다)


긴 글 읽어주셔서 감사합니다. 이제, 출근하러 갑니다.

p.s. 2-2편 혹은 3편은 코딩에 대한 주시는 선배님들의 지도편달을 반영한 최종코드로 작성할 예정입니다. 굽신굽신

3편을 추가하였습니다. https://okky.kr/article/578305

9
7
  • 댓글 6

  • feel
    141
    2019-05-09 08:59:13

    대단하시군요.


    github에 open source로 올리셔서 계속 작업하시면 

    뜻있는 분들이 contributor가 되어줄 지도 모릅니다.

    0
  • 삼이
    606
    2019-05-09 09:07:28

    감사합니다 매번 자극을 주셔서

    0
  • olivvve
    188
    2019-05-09 10:15:49

    대단하시네요.. 응원합니다!

    0
  • tteakuk
    38
    2019-05-09 14:19:56

    좋은 내용 읽고 갑니다.

    적용될수 있는 분야가 많을것 같습니다.

    0
  • Heo Jun
    55
    2019-05-10 15:08:07

    윈도우에서도 작업 스케쥴러라는 리눅스 크론탭과 동일한 역할을 하는 프로그램이 있습니다~

    0
  • 중년코딩
    264
    2019-05-11 18:20:19

    @feel 감사합니다. private으로 열심히 올리고는 있는데, 나중에 몇 개는 열어볼까 싶습니다.

    @삼이 자극적인가요(으하하하하하)

    @olivvve 감사합니다. v가 세 개이시군요.

    @tteakuk 실용적이지는 않은 것 같습니다. ㅎ

    @Heo Jun 넵, 둘 다 써보겠습니다.

    0
  • 로그인을 하시면 댓글을 등록할 수 있습니다.