(macOS)[python] Arduino DAQ 실행파일 만들기 with 가상환경(venv), pyinstaller
Intro
OS : MacOS, Ventura, v13.0.1
HW : Arduino UNO R3 board, DHT11 Sensor, LM35 Sensor
SW : Visual Studio Code, so-called vscode
python : 3.10.8
- Arduino 연결 부터 확인하려면 다음 포스트 참고
Arduino UNO DHT11 sensor 연결 및 모니터링
- Arduino Sensor DAQ 확인하려면 다음 포스트 참고
- Arduino Sensor multi DAQ 확인하려면 다음 포스트 참고
Arduino UNO DHT11/LM35 sensor multi DAQ
- 선행필요 : Arduino DAQ 관련 포스트 참고
(macOS)[python] Arduino DHT11/LM35 DAQ Monitoring with tkinter
Requirements
- vscode
- vscode extension : Arduino
- python packages
pyserial
numpy
tkinter
matplotlib
pyinstaller
# pyinstaller 실행파일 만들기의 필수사항이 아님 : 3번 참고, 4번 항목 넘어가도 좋음
1. python 가상환경(venv: virtual environment)
zsh path 설정 : 가상환경 바로가기....
$ nano ~/.zshrc
....
alias venv='python3 -m venv'
## 참고: nano 환경 빠져나오기
key: control + 'o'
enter
key: control + 'x'
macOS: conrol... cmd가 아님....
zshrc 변경 적용
$ source ~/.zshrc
# project 개발 시 version 관리 용이함
가상환경 만들기 : venv
가상환경 만들기 : venv
$ venv persona_daq
$ source /bin/activate
(persona_daq) $
cmd+shift+p
## 참고 : 가상환경 활성/비활성/삭제
활성
$ source /yourProject/bin/activate
비활성
(yourProject)$ deactivate
삭제 : macOS finder 직접 삭제 가능
$ rm -rf [yourProject]
-> 안되면.... sudo rm -rf [yourProject]
2. vscode : 가상환경 진입
project 열기 : persona_daq
python: select interpreter
venv 상태 확인
3. python Code : 이전 포스트 code 일부 수정
persona_daq.py
import os
import serial
import numpy as np
import time
import tkinter as tk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
## initial params.
global ard, sw, y_sum
sw = 0
y_sum = np.empty((0,3), dtype=float)
save_data = np.empty((0,5))
## initial tkinter graph
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
ax2 = ax1.twinx()
ax1.set_xlim([0,10])
ax1.set_ylim([0,100])
ax2.set_ylim([0,70])
## daq_ard plot function : Data AcQuisition from Arduino Board
def daq_ard(i):
global sw, y_sum, save_data
if(sw == 1):
td = time.strftime('%Y-%m-%d', time.localtime())
tt = time.strftime('%H:%M:%S', time.localtime())
datas_raw = ard.readline()
## ard.decode() : readline을 decoding
datas = datas_raw.decode()[:len(datas_raw)-2]
print('Decoded data:{}, type:{}:'.format(datas,type(datas)))
T1S1 = float(datas[5:10])
T2S2 = float(datas[10:15])
H1S1 = float(datas[0:5])
y_new = np.array([[T1S1,T2S2,H1S1]])
print(y_new[0],type(y_new[0]))
print('y_new(float):{}, type:{}'.format(y_new,type(y_new)))
y_sum = np.append(y_sum,y_new,axis=0)
print('DAQ count: {}'.format(len(y_sum)))
print('Data Stack: T1S1 T2S2 HumS1\n{}'.format(y_sum))
## axes clear/limit
ax1.clear()
ax2.clear()
# ax1.set_xlim([int(len(y_sum)-10),int(len(y_sum))])
ax1.set_xlim([int(0),int(len(y_sum)+2)])
plt.xticks(range(0,len(y_sum),1))
ax1.set_ylim([0.0,100.0])
ax2.set_ylim([0.0,70.0])
## plot 3-datas from sensor 2EA,
ax1.plot([i for i in range(int(len(y_sum)))],y_sum[:,0],'ro-',label='Temp.1')
ax1.plot([i for i in range(int(len(y_sum)))],y_sum[:,1],'go-',label='Temp.2')
ax2.plot([i for i in range(int(len(y_sum)))],y_sum[:,2],'b*--',label='Humidity')
## title/label
ax1.set_title('Temp. vs Time')
ax1.set(xlabel='Time[sec]',ylabel='Temp.[oC]')
## legend/grid
ax1.legend(loc="upper left")
ax2.legend(loc="upper right")
plt.grid()
databysave = np.array([[td,tt,y_new[0][0],y_new[0][1],y_new[0][2]]])
save_data = np.append(save_data,databysave,axis=0)
else:
print("Please Push the START button")
#### tkinter loop
root = tk.Tk()
root_title = 'Temperature DAQ System'
root.title(root_title)
# win_size : width x height + x_position + y_position
root_size = '200x100+650+20'
root.geometry(root_size)
## .resizable : move (vertical, horizontal) True or False
root.resizable(False, False)
## start btn : Arduino set-up
def start():
global ard, sw
## arduino board set-up
serial_port = '/dev/tty.usbmodem141101'
serial_baudrate = 9600
ard = serial.Serial(serial_port,serial_baudrate)
for tt in range(5,0,-1):
test_raw = ard.readline()
test = test_raw.decode()[:len(test_raw)-2]
print("starting...#{} | testing....:{} ".format(tt,test))
time.sleep(1)
print("START")
sw = 1
## stop btn
def save():
print("Start Save....")
global save_data
spath = './save_daq'
sname = 'data_save.csv'
if not os.path.exists(spath):
os.mkdir(spath)
csv_rows = ["{},{},{},{},{}".format(i,j,k,l,m) for i,j,k,l,m in save_data]
csv_text = "\n".join(csv_rows)
with open(os.path.join(spath,sname), 'w') as cf:
cf.write(csv_text)
cf.close()
for tt in range(3,0,-1):
print("saving....#{} ".format(tt))
time.sleep(1)
print("save_path:{}{}".format(os.getcwd(),spath[1:]))
print("Complete Save!!")
## stop btn
def stop():
global ard, sw
ard = None
sw = 0
print("stop")
ax1.clear()
ax2.clear()
plt.close()
root.destroy()
btn_start = tk.Button(root,text='START',overrelief='groove',fg='black',width=10,command=start,repeatdelay=1000,repeatinterval=100)
btn_start.pack()
btn_save = tk.Button(root,text='SAVE',overrelief='groove',fg='blue',width=10,command=save,repeatdelay=1000,repeatinterval=100)
btn_save.pack()
btn_stop = tk.Button(root,text='STOP',overrelief='groove',fg='red',width=10,command=stop,repeatdelay=1000,repeatinterval=100)
btn_stop.pack()
anim = animation.FuncAnimation(fig,daq_ard,interval=1000)
plt.show()
root.mainloop()
#### tkinter loop
python 파일 실행.... 누르면 당연하지만 실행이 안된다.
이전 포스트에서 작성 시 설치했던 package는 가상환경이 아닌 macOS 전역 공간에 설치되었기 때문
실행하면서 에러가 발생한 line의 필요한 package를 설치한다. 물론 가상환경 활성상태 vscode 터미널이나, iterm에서....
package(or module or library) install
(persona_daq)$ pip3 install --upgrade pip
(persona_daq)$ pip3 install pyserial
(persona_daq)$ pip3 install numpy
(persona_daq)$ pip3 install matplotlib
가상환경에 설치된 Package 확인
(persona_daq)$ pip3 list
Package Version
--------------- -------
contourpy 1.0.6
cycler 0.11.0
fonttools 4.38.0
kiwisolver 1.4.4
matplotlib 3.6.2
numpy 1.23.4
packaging 21.3
Pillow 9.3.0
pip 22.3.1
pyparsing 3.0.9
pyserial 3.5
python-dateutil 2.8.2
setuptools 65.4.1
six 1.16.0
requirements.txt 생성
(persona_daq)$ pip3 freeze > requirements.txt
## 참고 배포 시 설치 방법 : pip3 install -r requirements.txt
(persona_daq)$ cat requirements.txt
contourpy==1.0.6
cycler==0.11.0
fonttools==4.38.0
kiwisolver==1.4.4
matplotlib==3.6.2
numpy==1.23.4
packaging==21.3
Pillow==9.3.0
pyparsing==3.0.9
pyserial==3.5
python-dateutil==2.8.2
six==1.16.0
README.md : options... 굳이 필요는 없으나 실행파일 설명
아두이노 센서 2개 데이터를 수집하는 실행파일
1. python3.10.8
2. 필요한 python package : requirements.txt 확인
3. Arduino 연결 방법 및 python code 제작 관련 문서 사이트
https://jinozblog.tistory.com/190
4. arduino file : ard_tkinter_dht11_lm35.ino
5. dist/persona_daq/src
- ard_tkinter_dht11_lm35.ino
- requirements.txt
- README.md
4. 실행파일 만들기 : pyinstaller
pyinstaller 설치
(persona_daq)$ pip3 install pyinstaller
실행파일 생성
src 폴더에 필요한 파일 모으기
- Arduino.ino : ard_tkinter_dht11_lm35.ino : 링크
- requirements.txt
- README.md
(persona_daq)$ pyinstaller --add-data "src/*:src" persona_daq.py
2개의 폴더와 1개의 파일이 생성
build
dist
persona_daq.spec
패포판 : dist폴더에 생성
## 참고. add-data path: dist/persona_daq/src
실행파일 확인
exec, 즉 실행파일 생성
dist/persona_daq/persona_daq
>> 실행 결과 : terminal, figure, tkinter 3종 window 생성
## 참고: onefile 실행파일 생성 / terminal 없애기
onefile...
(persona_daq)$ pyinstaller --onefile persona_daq.py
dist 폴더에 파일이 persona_daq 1개만 존재
terminal(console) window 실행이 안되게....
(persona_daq)$ pyinstaller -w persona_daq.py