라즈베리파이를 활용한 서버실 온/습도 제어 관리
항온 항습 관리 시스템을 사용할 수 있다면, 참 좋겠지만...
고가의 항온/항습기를 도입하기 위한 비용 문제도 있지만 좁은 서버실에 항온/항습기를 도입하자면 배보다 배꼽이 더 커져 버리게 되어... 서버실 온/습도 관리에 어려움을 겪는 곳이 많으리라 생각됩니다.
저렴한 비용에... 입맛대로 구성할 수 있는 직접 제작한 온/습도 제어 관리 시스템을 소개합니다.
고가의 항온/항습기보다 더 많은 기능과 추가적인 다양한 기능들을 덧붙일 수 있는 유연성까지 갖추고 있는 아껴둔 자작 솔루션을 공개합니다.
구성품 : 라즈베리 파이, DHT-22 온습도 센서, 릴레이, 10KΩ 저항, 전기 콘센트, 플러그, 땜납, 인두,... ( 전기가 공급되면 자동 작동하는 ) 가습기, 제습기, 냉방기, 온열기 활용 가능
[ 작업을 위한 구성품 및 도구 사진 ]
[제품 구성도]
앞의 제품 구성도를 유심히 보았다면...
DHT22 온/습도 연결에 있어서 특이한 점을 찾을 수 있을 걸로 보이는데요.
DHT22의 Vcc를 라즈베리 파이의 Vcc 핀이 아닌, Data처럼, GPIO 핀에 연결하였다는 점이 되겠는데... DHT22를 사용해 본 경험이 있다면, 아마도 잘못 연결한 사용법이라 생각할 수도 있을거라 생각되네요.
DHT22를 실제 업무에 사용해 본 경우라면, DHT22가 안정성에 문제가 있어 다운되어 버리는 치명적인 결함이 있다는 걸 파악하고 있지 않을까 싶은데... ( 사용하는 부품에 따라 몇 시간에서 수개월까지 차이는 있었지만 오래 가도 1~2개월에 한 번은 반드시 다운이 되어 버리더군요. )
DHT22의 이런 치명적인 문제점을 해결하기 위해 고뇌한 끝에 찾아낸 혼자만의 비법(?)이라 할 수 있겠네요. 여러 전문가들에게 공식 질의를 해 보기도 했지만, 부품 제조사에서 명시한 규격 방식으로만 사용할 것을 권장할 뿐, 아무도 다른 해결 방안을 제시해 주지는 않더군요. 해외 사이트에서도 불안정한 문제점에 대한 내용은 간혹 찾을 수 있었지만, 어떤 사이트에서도 해결 방법을 제시해둔 곳은 없었고요.
그 당시 진행했던 프로젝트의 성패와 관련된 문제였기 때문에… 혼자서 몇 주간 머리를 싸매고 고심 고심한 끝에… DHT22가 소모하는 전류량과 GPIO 포트가 수용할 수 있는 전체 전류량을 계산해서, 이렇게 DHT22 센서를 몇 개 더 연결해도 작동하는 데 문제가 되지 않는다는 판단으로 결정한 독특한 연결 법이라 할 수 있겠고… 현재 10개 이상의 제품을 동일 방식으로 만들어 수년간 사용해 오고 있지만 다른 문제없이 잘 작동하고 있는 상태이네요.
DHT22의 불안정한 문제를 해결할 수 있었던 획기적인 기법으로 검증된 상황이 되겠네요.
[온/습도 모니터링 및 관리용 GUI 인터페이스 화면]
[GUI 인터페이스 화면 설명]
간단한 관리용 인터페이스 화면으로 특별히 설명하지 않아도 구성, 사용법을 추정할 수 있을거라 생각되겠는데요. 콘센터1 및 콘센터2에 가습기, 제습기, 난방기, 냉방기 중 어떤 제품이 연결되었는지, 작동 및 중지되기 위한 온/습도 범위에 대한 정보와 측정한 온/습도 정보가 저장될 경로 정보, 측정할 주기 시간을 설정한다거나, 측정한 온/습도 정보를 화면에 표시해 주고, 가습기, 제습기, 냉방기, 난방기가 작동/중지되는 값을 설정할 수 있게 해주는 GUI 인터페이스 프로그램이 되겠습니다.
GUI Interface 화면은 파이썬으로 만든 프로그램이라… Linux, Window, MAC 등의 OS에 관계 없이 사용할 수 있다는 게 장점이라 할 수 있을 것 같고… Text file로 특정 폴더에 자동 생성되는 온/습도 파일을 표시해 주는 방식이기 때문에, 추가 프로그램을 덧붙여 온/습도 정보를 DB에 넣어 관리한다거나, SMS 또는 메일 등으로 알림을 보낸다거나 Kibana 등의 시각화 툴을 이용하여 멋지게 표현하는 것도 어렵지 않게 가능하리라 생각되고요.
제작한 실물은 아래의 이미지와 같이 구성하였습니다. 케이블 연장을 유동적으로 길게 또는 짧게 사용할 수 있게 하기 위해 UTP 케이블과 I 커플러를 사용하여 만들었고요. 적정한 길이의 UTP 케이블을 간단하게 교체 연결만 한다면 가습기, 제습기 등의 기기를 원하는 위치에두고 작동시킬 수 있겠고요. 센서의 민감성으로 인해 DHT22와 라즈베리 파이와의 거리에는 제약이 있어, 문제가 생기지 않는 짧은 길이 ( 10~30cm ) 로 고정시켰지만, 라즈베리파이와 릴레이 간의 거리는 수십 미터 정도로 충분히 길게해서 테스트해도 문제가 없음을 확인하였기 때문에, 큰 공간의 장비실이라도 거리에 제한 없이 사용할 수 있을 걸로 생각되네요.
[실제 제작한 구성품 사진]
[제/가습기 및 냉/난방기 하드웨어 제어 방식 설명]
전기 콘센트에 가습기, 제습기, 난방기, 냉방기 중, 2가지 종류의 기기를 릴레이로 제어되는 콘센트에 꽂아 두면, 온/습도에 따라 설정된 환경 값에 의해 기기를 작동시키거나 중지되게 하여 일정한 온/습도가 유지될 수 있게 됩니다.
여름에는 제습기와 냉방기, 겨울에는 가습기와 난방기를 연결해 두고 온/습도가 자동 제어되게 할 수도 있겠고... 에어컨으로 온도를 관리하고 있어, 온도 제어가 필요하지 않다면 제습기와 가습기를 각각 연결해서 고습일 때 제습기가, 건조할때는 가습기가 작동되게 할 수도 있겠고요.
[환경 구성을 위한 사전 설치 프로그램]
- git에 올려진 WiringPI를 다운 받기 위해 git 설치
pi@raspberrypi:~ $ sudo apt-get install git-core –y
- 라즈베리파이에서 GPIO 핀을 사용하기 위해 WiringPI 설치 pi@raspberrypi:~ $ git clone https://github.com/WiringPi/WiringPi
- 다운 받은 WiringPi Library를 Compile 및 설치. pi@raspberrypi:~ $ cd WiringPi pi@raspberrypi:~/WiringPi $ ./build
[시스템 구성 파일]
dht.c : DHT22 센서로 부터 온/습도 정보 읽어 오는 C Source
read_h_t.sh : 온/습도 정보를 읽고 기기를 제어하기 위한 쉘 스크립트 ( dht 이용 )
exe_h_t.sh : read_h_t.sh 스크립트를 실행시켜 주는 쉘 스크립트
get_stat.py : 릴레이가 on 되어야 하는지 off 되어야 하는지 계산하는 파이썬 프로그램
gui_h_t.py : GUI용 Interface program (파이썬으로 개발되어 리눅스, 윈도 모두 구동 가능 )
r-ctl.conf : 환경 설정 파일 /etc/xdg/autostart/auto-gui-dht.desktop : Desktop 로긴시 자동으로 GUI program이 실행되게하는 단축 Link
[소프트웨어 실행 방식]
- 온/습도 Data 파일 생성 처리
1. crontab에 의해 부팅시 또는 일정 주기로 exe_h_t.sh 실행됨
2. exe_h_t.sh에 의해 read_h_t.sh 실행 상태를 점검하여, 실행되지 않고 있다면 read_h_t.sh 실행
3. read_h_t.sh에서 r-ctl.conf 환경 설정 정보 기반으로 dht를 통해 일정 주기로 온/습도 정보 읽어와서 지정된 디렉터리에 온/습도 정보를 일자별 파일로 기록 남긴 후, get_stat.py를 통해 얻은 제어값으로 릴레이를 ON/OFF 처리함
-----------------------------------------------------------
GUI Interface Program
1. Desktop 로긴시 /etc/xdg/autostart/auto-gui-dht.desktop에 의해 gui_h_t.py GUI 프로그램이 작동하여 read_h_t.sh에 의해 생성된 온/습도 정보 화면 표시, 환경 설정 파일 r-ctl.conf를 수정 지원
[시스템을 구성하는 프로그램 소스 첨부]
- dht.c : DHT22 센서로 부터 온/습도 정보 읽어 오는 C Source
DHT-22로부터 온도/습도를 읽어오는 dht.c는 인터넷에 공개되어 있는 C 소스를 가져와서 일부 수정 후 컴파일하여 사용.
인터넷으로부터 받은 받은 링크 주소: https://forums.raspberrypi.com/viewtopic.php?t=284053
/* * dht.c: * read temperature and humidity from DHT11 or DHT22 sensor */
#include <wiringPi.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h>
#define MAX_TIMINGS 85 #define DHT_PIN 24
int data[5] = { 0, 0, 0, 0, 0 };
int read_dht_data() { uint8_t laststate = HIGH; uint8_t counter = 0; uint8_t j = 0, i;
data[0] = data[1] = data[2] = data[3] = data[4] = 0;
/* pull pin down for 18 milliseconds */ pinMode( DHT_PIN, OUTPUT ); digitalWrite( DHT_PIN, LOW ); delay( 18 );
/* prepare to read the pin */ pinMode( DHT_PIN, INPUT );
/* detect change and read data */ for ( i = 0; i < MAX_TIMINGS; i++ ) { counter = 0; while ( digitalRead( DHT_PIN ) == laststate ) { counter++; delayMicroseconds( 1 ); if ( counter == 255 ) { break; } } laststate = digitalRead( DHT_PIN );
if ( counter == 255 ) break;
/* ignore first 3 transitions */ if ( (i >= 4) && (i % 2 == 0) ) { /* shove each bit into the storage bytes */ data[j / 8] <<= 1; if ( counter > 16 ) data[j / 8] |= 1; j++; } }
/* * check we read 40 bits (8bit x 5 ) + verify checksum in the last byte * print it out if data is good */ if ( (j >= 40) && (data[4] == ( (data[0] + data[1] + data[2] + data[3]) & 0xFF) ) ) { float h = (float)((data[0] << 8) + data[1]) / 10; if ( h > 100 ) { h = data[0]; // for DHT11 } float c = (float)(((data[2] & 0x7F) << 8) + data[3]) / 10; if ( c > 125 ) { c = data[2]; // for DHT11 } if ( data[2] & 0x80 ) { c = -c; }
c -= 2.5;
float f = c * 1.8f + 32; printf( "Humidity = %.1f %% Temperature = %.1f *C ( %.1f *F )", h, c, f ); return 1; }else { // printf( "Data not good, skip " ); return 0; } }
int main( void ) { // printf( "Raspberry Pi DHT11/DHT22 temperature/humidity test " );
int cnt = 0;
if ( wiringPiSetup() == -1 ) exit( 1 );
while ( 1 ) { if( read_dht_data() ) break;
if( ++ cnt > 5 ) { printf( "Error!!"); return(1); break; } delay( 2000 ); /* wait 2 seconds before next read */ }
return(0); } |
소스 코드를 아래와 같이 compile하여, 컴파일된 dht 실행파일을 사용
pi@raspberrypi:~/dht $ gcc -o dht dht.c –lwiringPi
- read_h_t.sh :
1. 온/습도 정보를 읽어 오는 dht proram을 반복 실행하여 측정한 온/습도 기록
2. 측정한 온/습도 정보를 기반으로 릴레이 제어 ( 제/가습기, 냉/난방기 제어 )
#!/bin/sh scr_name=$(/usr/bin/readlink -f $0 ) #스크립트의 실행 경로 확인 scr_path=$(/usr/bin/dirname $scr_name)
#DHT 읽기중 오류 발생시 오류 발생 횟수 count를 위한 변수 초기화 err_cnt=0
#무한 반복하며 DHT로 부터 온/습도를 측정하여 그에 따른 처리를 함 while true do ###온/습도 읽어옴 #DHT 전원 공급 /usr/local/bin/gpio mode 22 out;/usr/local/bin/gpio write 22 1 #DHT로 부터 온/습도 읽어 옴 h_t=$($scr_path/dht) #측정한 습도값 h=$(/usr/bin/echo $h_t|/usr/bin/cut -d " " -f 3 ) #측정한 온도값 t=$(/usr/bin/echo $h_t|/usr/bin/cut -d " " -f 7 ) #DHT 전원 공급 차단(DHT 초기화 효과 - DHT 다운 증상 방지 효과) /usr/local/bin/gpio write 22 0
#DHT로 측정값을 정상적으로 읽어 오지 못했을 경우 if [ "$h_t" = "Error!!" ]; then err_cnt="expr $err_cnt + 1" #오류 count 횟수 1증가 시킴 else err_cnt=0 #DHT로 부터 값을 제대로 읽어 왔을 경우에는 오류 count를 초기화시킴 fi
# 연속 오류 횟수가 2번 이상 반복될 경우에는 라즈베리파이를 재부팅 시켜 오류 복구 if [ $err_cnt -ge 2 ]; then /usr/bin/sudo /sbin/reboot fi
#실행된 스크립트와 같은 경로에 있는 r-ctl.conf 환경 설정 파일로 부터 #온/습도값 기록할 data file 경로 확인 dat_path=$(/usr/bin/cat $scr_path/r-ctl.conf | /usr/bin/grep -v ^# | /usr/bin/grep dat_path= |/usr/bin/cut -f 2 -d "=") #설정 파일에 지정한 디렉터리가 없을 경우에는 디렉터리를 새로 생성함 [ ! -d "$dat_path" ] && /usr/bin/sudo /usr/bin/mkdir –p $dat_path;/usr/bin/sudo /usr/bin/chown pi $dat_path
# 온/습도 값을 기록할 파일명 ( 일자별로 파일 기록함 ) o_file=$dat_path/$(/usr/bin/date +%Y%m%d).txt
#일시 습도, 온도를 일자별 file에 기록함 /usr/bin/echo $(/usr/bin/date +%Y%m%d_%H%M%S) 습도= $h , 온도= $t >> $o_file
###relay 1에 대한 처리 # 릴레이 1에 대한 설정값 가져옴 r1=$(/usr/bin/cat $scr_path/r-ctl.conf | /usr/bin/grep -v ^# | /usr/bin/grep r1=) #첫번째 릴레이에 설정된 장치값 읽어옴( H: 제/가습기, T: 냉/난방기 ) r1_d=$(/usr/bin/echo $r1 | /usr/bin/cut -f 2 -d '='| /usr/bin/cut -f 1 -d ' ') #장치를 작동시킬 경계값 r1_on=$(/usr/bin/echo $r1 | /usr/bin/cut -f 2 -d '='| /usr/bin/cut -f 2 -d ' ') #장치를 작동 중지 시킬 경계값 r1_off=$(/usr/bin/echo $r1 | /usr/bin/cut -f 2 -d '='| /usr/bin/cut -f 3 -d ' ')
if [ "$r1_d" = "H" ]; then #릴레이1을 통해 가습기 또는 제습기를 제어할 경우라면 r1_act=$($scr_path/get_stat.py $r1_on $r1_off $h ) #측정한 습도값을 전달하여 제어값 얻음 fi
if [ "$r1_d" = "T" ]; then #릴레이1을 통해 냉방기 또는 난방기를 제어할 경우라면 r1_act=$($scr_path/get_stat.py $r1_on $r1_off $t ) #측정한 온도값을 전달하여 제어값 얻음 fi
if [ "$r1_act" = "1" ]; then #릴레이1을 작동시킬때 /usr/local/bin/gpio mode 28 out /usr/local/bin/gpio write 28 1 fi
if [ "$r1_act" = "0" ]; then #릴레이1을 작동 중지시킬때 /usr/local/bin/gpio mode 28 out /usr/local/bin/gpio write 28 0 fi
###relay 2에 대한 처리 # 릴레이 2에 대한 설정값 가져옴 r2=$(/usr/bin/cat $scr_path/r-ctl.conf | /usr/bin/grep -v ^# | /usr/bin/grep r2=) #첫번째 릴레이에 설정된 장치값 읽어옴(H:가/제습기, T:냉/난방기) r2_d=$(/usr/bin/echo $r2 | /usr/bin/cut -f 2 -d '='| /usr/bin/cut -f 1 -d ' ') #장치를 작동시킬 경계값 r2_on=$(/usr/bin/echo $r2 | /usr/bin/cut -f 2 -d '='| /usr/bin/cut -f 2 -d ' ') #장치를 작동 중지 시킬 경계값 r2_off=$(/usr/bin/echo $r2 | /usr/bin/cut -f 2 -d '='| /usr/bin/cut -f 3 -d ' ')
if [ "$r2_d" = "H" ]; then #릴레이2을 통해 가습기 또는 제습기를 제어할 경우라면 r2_act=$($scr_path/get_stat.py $r2_on $r2_off $h ) #측정한 습도값을 전달하여 제어값 얻음 fi
if [ "$r2_d" = "T" ]; then #릴레이2을 통해 냉방기 또는 난방기를 제어할 경우라면 r2_act=$($scr_path/get_stat.py $r2_on $r2_off $t ) #측정한 온도값을 전달하여 제어값 얻음 fi
if [ "$r2_act" = "1" ]; then #릴레이2을 작동시킬때 /usr/local/bin/gpio mode 29 out /usr/local/bin/gpio write 29 1 fi
if [ "$r2_act" = "0" ]; then #릴레이2을 작동 중지시킬때 /usr/local/bin/gpio mode 29 out /usr/local/bin/gpio write 29 0 fi
#설정된 시간(초) 간격으로 반복되게 함 /usr/bin/sleep $(/usr/bin/cat $scr_path/r-ctl.conf|/usr/bin/grep -v ^# | /usr/bin/grep r_sec= |/usr/bin/cut -f 2 -d "=") done |
- exe_h_t.sh :
read_h_t.sh 스크립트가 자동 실행되어, 계속 작동할 수 있게 해주는 스크립트
crontab에 의해 초기 부팅시 및 일정 시간 간격 실행되어 read_h_t.sh 실행 상태 체크
#!/usr/bin/sh scr_name=$(/usr/bin/readlink -f $0 ) #스크립트의 실행 경로 확인 scr_path=$(/usr/bin/dirname $scr_name)
/usr/bin/ps -ef | /usr/bin/grep -v grep | /usr/bin/grep read_h_t.sh > /dev/null if [ $? != 0 ] then $scr_path/read_h_t.sh & fi |
crontab에 아래와 같이 등록 pi@raspberrypi:~/dht $ crontab –e
# m h dom mon dow command # 켜질때마다 자동 실행되게 @reboot /home/pi/dht/exe_h_t.sh # 매 시간마다 실행 상태 점검하여 미 작동시 실행될 수 있게 0 */1 * * * /home/pi/dht/exe_h_t.sh |
- get_stat.py :
릴레이가 on 되어야 하는지 off 되어야 하는지 계산하는 파이썬 프로그램 ( read_h_t.sh 에서 호출하여 사용함 )
#!/usr/bin/python3 #입력 Parameter: 작동시작 작동중지 측정된값 #출력 - 1: 작동, 0:중지, 2: 현상태유지, 9:오류 #판단을 위한 값을 입력 받아, 릴레이 작동 상태값 Return import sys if len(sys.argv) == 4: #전달받아야 할 인수 갯수가 자신의 이름 + 3개 인지 확인 pass else : #전달 받아야할 인수 개수가 맞지 않을 경우 print( 9 ) # 오류 표시 quit() # 종료 try: on=float(sys.argv[1]) #첫번째 인수는 on 값 off=float(sys.argv[2]) #두번째 인수는 off 값 value=float(sys.argv[3]) #세번째 인수는 측정값 except: # 잘못된 값을 전달 받았을 경우 print(9) # 오류 표시 quit() # 종료
if on > off : #냉방기, 제습기에 대한 처리 if value > on : # 작동되게 알림(측정값이 on 값보다 클때) print( 1 ) elif value < off:# 작동 중지되게 알림(측정값이 off 값보다 작을때) print( 0 ) else : # 현상태 유지 print( 2 ) else : #난방기 또는 가습기 if value < on : # 작동되게 알림(측정값이 on 값보다 작을때) print( 1 ) elif value > off : #작동 중지되게 알림(측정값이 off 값보다 클때 ) print( 0 ) else : #그외의 상황에서는 현상태 유지 print ( 2 ) |
- r-ctl.conf :
환경 설정 정보가 저장되어 있는 파일 기기 제어를 위해 read_h_t.sh 쉘 스크립트에서 사용
설정 변경 및 제어를 위해 gui_h_t.py 파이썬 GUI 프로그램에서 사용
#####Relay 설정 # 구분 ON OFF r1=H 40 50 r2=T 16 20 ##### 온/습도 Data 저장 경로 dat_path=/home/pi/dht/data ##### 온/습도 측정 시간 간격(초) r_sec=100 |
설정 파일에서
r1 : 릴레이1에 대한 제어 설정 값
r2 : 릴레이2에 대한 제어 설정 값
릴레이 설정 변수에 H는 습도 제어, T는 온도 제어
H/T 다음에 첫 번째 숫자는 릴레이를 켜는(ON) 값, 두 번째 값은 릴레이를 끄는(OFF) 값
r1=H 40 50 의미는 습도 40%에서 켜고, 50%에는 끄도록 설정 ( 가습기 ),
제습기: H 다음에 큰 값, 작은 값 순으로 큰 값에서 작동시키고, 작은 값에서 중지
난방기: T 다음에 작은 값, 큰 값의 순으로, 작은 값에서 켜고, 큰 값에서 중지
냉방기: T 다음에 큰 값, 작은 값 배치로 큰 값에서 작동시키고, 작은 값에서 중지
기준에 따라 조금씩의 차이는 있겠지만, 서버실의 온도는 16℃ ~ 28℃ 정도가 적정할 것 같고, 습도는 40% ~ 70% 정도가 적절할 걸로 보임.
dat_path 는 측정한 온도/습도 자료를 저장할 경로에 대한 설정
r_sec 값은 온/습도를 측정하는 반복 주기(초)
- gui_h_t.py :
온/습도 화면 표시 및 환경 설정 관리용 GUI 프로그램 read_h_t.sh에서 생성한 온/습도 파일 화면 표시 r-ctl.conf 환경 설정 파일 관리
#!/usr/bin/python3 from tkinter import * import tkinter.ttk as ttk # 콤보 박스 import os from datetime import datetime import time def get_h_t(): global conf_file_name #환경 설정 정보가 저장된 파일명 global r_sec #온습도 data를 반복 읽어 오는 주기 global r1_f #r1 relay에 대한 장치 구분값 global r2_f #r2 Relay에 대한 장치 구분값 global r1_on #r1 relay의 작동 시작 경계값 global r2_on #r2 relay의 작동 시작 경계값 global r1_off #r1 relay의 작동 중지 경계값 global r2_off #r2 relay의 작동 중지 경계값 global dat_path #측정한 온/습도 데이터가 저장되어 있는 경로
global c_date #측정한 일시 global t #측정된 온도 수치 global h #측정된 습도 수치
# 스크립트가 위치하는 경로 확인 (scr_path, scr_name)=os.path.split(os.path.abspath( __file__ ))
# 환경 파일의 위치 확인 conf_file_name = scr_path + "/r-ctl.conf"
c_file = open( conf_file_name, "r" )
for l in c_file.readlines(): if l[0] == "#" : pass else : (e_name,e_val) = l.split('=') if e_name == "dat_path" : dat_path = e_val elif e_name == "r1" : r1_f=e_val.split()[0] #Relay 1의 구분값 r1_on=e_val.split()[1] #Relay 1의 ON 경계값 r1_off=e_val.split()[2] #Relay 2의 OFF 경계값 elif e_name == "r2" : r2_f=e_val.split()[0] #Relay 2의 구분값 r2_on=e_val.split()[1] #Relay 2의 ON 경계값 r2_off=e_val.split()[2] #Relay 2의 OFF 경계값 elif e_name == "r_sec" : r_sec = e_val else : pass c_file.close() #Data가 기록된 파일명 얻음 dat_file=dat_path.rstrip(" ")+"/"+ datetime.today().strftime("%Y%m%d") + ".txt" #마지막 기록된 온/습도 Data 읽어옴 d_file = open( dat_file, "r" ) l_dat=d_file.readlines()[-1] d_file.close()
c_date=datetime.strptime( l_dat.split()[0], "%Y%m%d_%H%M%S") #측정한 일시 h=l_dat.split()[2] # 습도 t=l_dat.split()[5] # 온도
def get_d_name( gbn, on, off ): on=float(on) off=float(off) if gbn == "T" : #난방기 또는 냉방기 if on > off : get_d_name = "냉방기" else : get_d_name = "난방기" else : if on > off: get_d_name = "제습기" else : get_d_name = "가습기" return get_d_name
def get_unit( gbn ) : if gbn == "T": return "˚C" else: return "%"
def r_check(): try: get_h_t()
lbl_ctime.config( text = c_date ) lbl_temp.config( text = t + "˚C") lbl_humi.config( text = h+'%' )
combo_gbn1.set( get_d_name( r1_f, r1_on, r1_off ) ) combo_gbn2.set( get_d_name( r2_f, r2_on, r2_off ) )
ent_on1.delete( 0, END ) ent_on1.insert( 0, r1_on )
lbl_on1_unit.config( text=get_unit( r1_f ) )
ent_off1.delete( 0, END ) ent_off1.insert( 0, r1_off )
lbl_off1_unit.config( text=get_unit( r1_f ) )
ent_on2.delete( 0, END ) ent_on2.insert( 0, r2_on )
lbl_on2_unit.config( text=get_unit( r2_f ) )
ent_off2.delete( 0, END ) ent_off2.insert( 0, r2_off)
lbl_off2_unit.config( text=get_unit( r2_f ) )
ent_path.delete(0,END) ent_path.insert( 0, dat_path.rstrip())
ent_rpt.delete( 0, END ) ent_rpt.insert( 0, int(r_sec) ) except : pass
dht.after( int(r_sec) * 1000, r_check ) #지정된 시간 후 다시 실행되게 함
def get_dev_gbn( str ): if str == "가습기" or str == "제습기" : return "H" else : return "T"
def write_config() : c_file = open( conf_file_name, "r" ) config_file=c_file.readlines() w_config_file=[] for l in config_file: if l[0] == "#" : w_config_file.append( l ) else: (e_name,e_val) = l.split('=') if e_name == "dat_path" : w_config_file.append( e_name + "=" + ent_path.get() + " " ) elif e_name == "r1" : w_config_file.append( e_name + "=" + get_dev_gbn( combo_gbn1.get() ) + " " + ent_on1.get() + " " + ent_off1.get() + " " ) elif e_name == "r2" : w_config_file.append( e_name + "=" + get_dev_gbn( combo_gbn2.get() ) + " " + ent_on2.get() + " " + ent_off2.get() + " " ) elif e_name == "r_sec" : w_config_file.append( e_name + "=" + ent_rpt.get() + " " ) c_file.close()
with open( conf_file_name, "w" ) as c_file: c_file.writelines( w_config_file ) dht=Tk() dht.title( "온도/습도 현황" ) dht.geometry( "600x330+10+50") i_x=20 i_y=20 o_x=200 o_y=30 p_x=i_x + 10 p_y=i_y lbl_time_title=Label( dht, text="측정 일시" ) lbl_time_title.config(font=("Courier", 17, "bold")) lbl_time_title.place(x=p_x + 70, y=p_y ) p_x=p_x+o_x + 100 lbl_temp_title=Label( dht, text="온도" ) lbl_temp_title.config(font=("Courier", 17, "bold")) lbl_temp_title.place(x=p_x, y=p_y ) p_x=p_x + 150 lbl_humi_title=Label( dht, text="습도" ) lbl_humi_title.config(font=("Courier", 17, "bold")) lbl_humi_title.place(x=p_x, y=p_y ) #, width=100, height=50 ) p_x=i_x p_y=p_y+o_y lbl_ctime=Label( dht, text="") lbl_ctime.config(font=("Courier", 15 ) ) lbl_ctime.place(x=p_x, y=p_y ) p_x=p_x+o_x + 100 lbl_temp=Label( dht, text="") lbl_temp.config(font=("Courier", 15 ) ) lbl_temp.place(x=p_x, y=p_y ) p_x=p_x + 150 lbl_humi=Label( dht, text="") lbl_humi.config(font=("Courier", 15 ) ) lbl_humi.place(x=p_x, y=p_y ) p_x=i_x+120 p_y=p_y+60 lbl_gbn_title=Label( dht, text="장치구분" ) lbl_gbn_title.config(font=("Courier", 17, "bold")) lbl_gbn_title.place(x=p_x, y=p_y ) #, width=100, height=50 ) p_x=p_x+130 lbl_on_title=Label( dht, text="가동시작값" ) lbl_on_title.config(font=("Courier", 17, "bold")) lbl_on_title.place(x=p_x, y=p_y ) #, width=100, height=50 ) p_x=p_x+170 lbl_off_title=Label( dht, text="가동중지값" ) lbl_off_title.config(font=("Courier", 17, "bold")) lbl_off_title.place(x=p_x, y=p_y ) #, width=100, height=50 ) p_x=i_x p_y=p_y+o_y + 10 lbl_con1_title=Label( dht, text="콘센트1:" ) lbl_con1_title.config(font=("Courier", 17, "bold")) lbl_con1_title.place(x=p_x, y=p_y ) #, width=100, height=50 ) p_x=p_x + 120 values = [ "가습기", "제습기", "난방기", "냉방기" ] combo_gbn1 = ttk.Combobox( dht, height=4, width=8, values=values, state="readonly" ) combo_gbn1.config(font=("Courier", 15 )) combo_gbn1.place( x=p_x, y=p_y ) p_x=p_x + 130 ent_on1 = Entry( dht, width=5 ) ent_on1.config(font=("Courier", 15 )) ent_on1.place( x=p_x, y=p_y ) p_x=p_x + 70 lbl_on1_unit = Label( dht, text="") lbl_on1_unit.config(font=("Courier", 15 )) lbl_on1_unit.place( x=p_x, y=p_y ) p_x=p_x + 105 ent_off1 = Entry( dht, width=5 ) ent_off1.config(font=("Courier", 15 )) ent_off1.place( x=p_x, y=p_y ) p_x=p_x + 70 lbl_off1_unit = Label( dht, text="") lbl_off1_unit.config(font=("Courier", 15 )) lbl_off1_unit.place( x=p_x, y=p_y ) p_x=i_x p_y=p_y+o_y+10 lbl_con2_title=Label( dht, text="콘센트2:" ) lbl_con2_title.config(font=("Courier", 17, "bold")) lbl_con2_title.place(x=p_x, y=p_y ) #, width=100, height=50 ) p_x=p_x + 120 #values = [ "가습기", "제습기", "난방기", "냉방기" ] combo_gbn2 = ttk.Combobox( dht, height=4, width=8, values=values, state="readonly" ) combo_gbn2.config(font=("Courier", 15 )) combo_gbn2.place( x=p_x, y=p_y ) p_x=p_x + 130 ent_on2 = Entry( dht, width=5 ) ent_on2.config(font=("Courier", 15 )) ent_on2.place( x=p_x, y=p_y ) p_x=p_x + 70 lbl_on2_unit = Label( dht, text="") lbl_on2_unit.config(font=("Courier", 15 )) lbl_on2_unit.place( x=p_x, y=p_y ) p_x=p_x + 105 ent_off2 = Entry( dht, width=5 ) ent_off2.config(font=("Courier", 15 )) ent_off2.place( x=p_x, y=p_y ) p_x=p_x + 70 lbl_off2_unit = Label( dht, text="") lbl_off2_unit.config(font=("Courier", 15 )) lbl_off2_unit.place( x=p_x, y=p_y ) p_x=i_x p_y=p_y+o_y+10 lbl_path_title=Label( dht, text="저장경로:" ) lbl_path_title.config(font=("Courier", 17, "bold")) lbl_path_title.place(x=p_x, y=p_y ) #, width=100, height=50 ) p_x=p_x + 120 ent_path = Entry( dht, width=33 ) ent_path.config(font=("Courier", 15 )) ent_path.place( x=p_x, y=p_y ) p_x=i_x p_y=p_y+o_y+10 lbl_rpt_title=Label( dht, text="측정주기:" ) lbl_rpt_title.config(font=("Courier", 17, "bold")) lbl_rpt_title.place(x=p_x, y=p_y ) #, width=100, height=50 ) p_x=p_x + 120 ent_rpt = Entry( dht, width=5 ) ent_rpt.config(font=("Courier", 15 )) ent_rpt.place( x=p_x, y=p_y ) p_x=p_x + 70 lbl_sec=Label( dht, text="(초)" ) lbl_sec.config(font=("Courier", 15)) lbl_sec.place(x=p_x, y=p_y ) p_x=p_x + 180 btn_save = Button( dht, width=8, text="설정저장", command=write_config ) btn_save.config(font=("Courier", 15 )) btn_save.place( x=p_x, y=p_y ) dht.after( 0, r_check) dht.mainloop() |
- auto-gui-dht.desktop :
GUI Interface gui_h_t.py를 Desktop 로긴시 자동 실행하는 데스크탑 링크 /etc/xdg/autostart/auto-gui-dht.desktop 에 저장
[Desktop Entry] Type=Application Name=Auto-GUI-DHT Comment=GUI Humidity and Temperature NoDisplay=false Exec=/home/pi/dht/gui_h_t.sh NotShowIn=GNOME;KDE;XFCE; |
본 글은 "wansoo" 회원님께서 기고해 주셨습니다.
기업의 IT운영에 대한 나만의 노하우나 경험이 있으시다면 [email protected]로 기고해 주세요
선정되신 분께는 5만원 상당의 백화점 상품권을 보내드립니다.
5개의 댓글이 있습니다.
라즈베리파이로 할수 있는게 다양하네요.
Reply잘 읽었습니다.
댓글 남기기
댓글을 남기기 위해서는 로그인이 필요합니다.
로그인 회원가입이런 정보 공유 좋아요... 공유 해주신 wansoo께도 감사드립니다.
Reply댓글 남기기
댓글을 남기기 위해서는 로그인이 필요합니다.
로그인 회원가입좋은 정보 참고하겠습니다.
Reply댓글 남기기
댓글을 남기기 위해서는 로그인이 필요합니다.
로그인 회원가입이 정도까지 디테일 하게 구성하는 줄을 몰랐습니다. !
Reply좋은 정보 감사합니다 !
댓글 남기기
댓글을 남기기 위해서는 로그인이 필요합니다.
로그인 회원가입온/습도 센서 DHT22를 제어하는 부분은 C 언어와 Shell Script를 이용해서 만들었고, GUI 부분과 계산에 복잡성이 있는 부분에 대해서만 Python을 이용하여 구현했습니다.
스크립트 중심으로 작업을 많이 하던 시기에 만들었던 시스템이라...
스크립트를 이용한 부분이 많습니다.
지금 새롭게 다시 만든다면 스크립트를 최소화하고 파이썬 중심으로 만들게 될 것 같네요.
서버실 항온/항습기를 이런 방식으로 구현할 수도 있다는데 참고하시면 도움될 것 같습니다.
Reply댓글 남기기
댓글을 남기기 위해서는 로그인이 필요합니다.
로그인 회원가입