์ด๋ฒ ํฌ์คํ ์์๋ ์ด์ mqtt์์ ์ฌ์ฉํ ์์ ๋ฐ์ดํฐ๋ณด๋ค 2๋ง ๋ฐฐ ์ ๋ ๋ง์ ๋ฐ์ดํฐ๋ฅผ DAQ์์ ๋ฐ์์ค๋ UDP ์์ ๋ฅผ ๋ง๋ค ๊ฒ์ด๋ค. ๊ตฌํํ๋ ค๋ ์น ์๋น์ค์ ์จ๋ ๋ฐ์ดํฐ ๋ง๊ณ ์ง๋ ๋ฐ์ดํฐ๋ ๋ฃ์ด๋ณด๊ณ ์ถ์๋ฐ, ๊ทธ๊ฒ์ ์ํด์ ์ฐ์ ๋์ฉ๋ ๋ฐ์ดํฐ์ด๊ธฐ ๋๋ฌธ์ ๋ณต์กํด์ง๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์๋ฒ๋ฅผ ์ ์ธํ๊ณ ์์ผ ํต์ ํ ๊ฒฐ๊ณผ๋ฅผ matplotlib๋ก ๋ก์ปฌ์ ๊ฐ๋จํ๊ฒ ๋์๋ณด๋๋ก ํ๊ฒ ๋ค.
1. UDP ํต์
UDP๋ ์์ผ ํต์ ์ ์ผ์ข ์ผ๋ก, ๋ค๋ฅธ ์์ผ ํต์ ๋ฐฉ๋ฒ์ธ TCP์๋ ๋ค๋ฅด๊ฒ ์ฐ๊ฒฐ์ ๋ณด์ฅํด์ฃผ์ง ์๋๋ค. ๋์๊ฒ ๋ค๋ฆด ์๋ ์๊ฒ ์ผ๋ client์ server์ ์ฐ๊ฒฐ์ ์์ฒญํ๊ณ , ์น์ธํ๊ณ ์ฐ๊ฒฐํ ํ ํต์ ์ ์์ํ๋ TCP์ ๋ฌ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ๋งค๋ฒ ๋ณด๋ผ ๋๋ง๋ค ๋ฐ์ดํฐ์ ๋ชฉ์ ์ง(ip, port๋ฒํธ)๋ฅผ ํจ๊ป ์ค์ด์ ์ผ๋จ ๋ ๋ค ๋ณด๋ด๋ ๋ฐฉ์์ด๋ค. ๊ทธ๋ฌ๋ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์๋์ง ๊ฒ์ฆ๋ ํด์ฃผ์ง ์๋๋ค. ์ฐ๊ฒฐ์ด ์ง์์ ์ด์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ฅ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ง ๋ชปํ ๊ฒฝ์ฐ์๋ ์ฌ์๋ ํด์ฃผ๋ TCP์ ๋ฌ๋ฆฌ ๋ณด๋ด๋ฉด ๋์ด๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ ์์ด ์ด๋ง๋ฌด์ํ๊ฒ ๋ฐฉ๋ํ๊ณ ์ค์๊ฐ์ผ๋ก ์์ฐฝ ๋ณด๋ด์ผํ๋ฉฐ, ๋ช ๊ฐ ์์ด๋ฒ๋ ค๋ ํฌ๊ฒ ์๊ด์๋ ๊ฒฝ์ฐ์๋ UDP๊ฐ ๋น ๋ฅด๊ณ ์ข์ ์ ์๋ค. ๋ฐ๋ก ์ง๊ธ! ์ด๋น 2๋ง์ฌ ๊ฐ์ ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ๋ ์ง๋ ์ผ์์ ๊ฐ์ ๊ฒฝ์ฐ๊ฐ ๊ทธ๋ ๋ค.
์ง๋ ๋ฐ์ดํฐ ๊ด๋ จ ์ด๋ก ์ ...์์ง ์ ์ผ๊ตฌ๋(๊นจ๋ฌ์) ์กฐ๋ง๊ฐ ์ฌ๋ ค์ ๋งํฌ๋ฅผ ๊ฑธ๋๋ก ํ๊ฒ ๋ค. ์ฌ์ค ์์ง ์ ๋ชจ๋ฅด๊ฒ ๋ค^^; ๊ฐ์คํ๊ณ ์ฝ๋๋ฅผ ๋ณด์.
Python ์์
- Server.py
import socket
server = ('YOUR_IP_ADDRESS', 2001)
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverSocket.bind(server)
data = serverSocket.recv(1024)
- Client.py
import socket
server = ('YOUR_IP_ADDRESS', 2001)
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
data = dummy_data
clientSocket.sendto(data, server)
๊ฐ๋จํ๋ค. YOUR_IP_ADDRESS
์ server(๋ฐ์ดํฐ ๋ฐ๋ ์ชฝ)์ ip๋ฅผ ๋ฃ๊ณ , client์์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๊ณ server์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด๋ค์ธ๋ค. client์ฝ๋์ sendto
๋ถ๋ถ์ ๋ณด๋ฉด ip์ port๋ฒํธ๋ฅผ ํจ๊ป ์ ์กํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ฃผ์ํด์ผํ ์ ์, server์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๋ ์ ํด๋ ๋ฒํผ์ฌ์ด์ฆ๋ณด๋ค ๋ฐ์ดํฐ์ ํฌ๊ธฐ๊ฐ ํฌ๋ฉด ์๋๋ค. ํ์ฌ ์์ ์์๋ 1024 byte๋ก ํด๋์์ผ๋, ํ ๋ฒ์ ๋ฐ๋ ๋ฐ์ดํฐ๊ฐ ์ด ์ฌ์ด์ฆ๋ฅผ ๋์ด๊ฐ์๋ ์๋๋ค๋ ๊ฒ์ด๋ค.
2. ๋ฐ์ดํฐ ์ก์์
์ฌ๊ธฐ์๋ถํฐ๋ ๊ฐ์ ๊ฐ์ง๊ณ ์๋ DAQ ์ฅ์น์ sampling rate ๋ฑ๋ฑ ์ฌ๋ฌ ํ๋์จ์ด์ ์ธ ์คํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์๋ค. ๋ง์ผ dummy data๋ก ํ๋ค๋ฉด 5kHz ์ ๋๋ก ์์ฑํด๋ ์ข์ ๊ฒ์ด๋ค. ์ด๋น 5000๊ฐ ์ ๋์ ๋ฐ์ดํฐ์ด๋ค.
ํ์ฌ ์ฌ์ฉํ๊ณ ์๋ DAQ ์ฅ์น๋ ๋ผ์ฆ๋ฒ ๋ฆฌ ํ์ด ์์ ์ฅ์ฐฉํ๋ ํํ๋ก ๋ mcc172 DAQ hat์ด๋ค. ์ด๋น ์ฝ 18000๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ํ๋งํ๋๋ก ์ค์ ํ์๋ค.
๋ฐ์ดํฐ์ sampling rate๋ ๊ทธ๋ ์ง๋ง ์ค์ ๋ก ๋ฐ์ดํฐ๊ฐ 1์ด์ ํ ๋ฒ ๋ค์ด์ค์ง๋ ์๋๋ค. ์ด ์์ ์ ๊ฒฝ์ฐ ํ ๋ฒ์ ์ฝ 2045๊ฐ ๊ฐ๋์ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๊ณ , ์์ ํ๋ค. ๋ฐ์ดํฐ์ ์์ด ๋ง์ผ๋ฉด float array ํํ๋ณด๋ค๋ byte๋ก packingํด์ ๋ณด๋ด๊ณ , ๋ฐ์์ unpackํ๋ ๊ฒ์ด ๋ ์ฉ์ดํ๊ธฐ ๋๋ฌธ์ ๊ทธ๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
- Client.py
import socket, struct
server = ('YOUR_IP_ADDRESS', 2001)
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
...
data = struct.pack('%sf' %len(signal), *signal)
socket.sendto(data, server)
clientSocket.sendto(data, server)
float array๋ฅผ byteํํ๊ธฐ ์ํด์ struct
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์๋ค.
struct.pack('%sf' %len(signal), *signal)
์ด๋ signal์ ๊ธธ์ด์ ๊ฐ์ ๊ฐ์์ float ๊ฐ๋ค์ byteํํ๋ค๋ ๋ป์ด๋ค.
- Server.py
import socket, array
server = ('YOUR_IP_ADDRESS', 2001)
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverSocket.bind(server)
data = serverSocket.recv(8192)
signals = array.array('f')
signals.frombytes(data)
์๋ฒ ์ชฝ์์๋ ๋ฒํผ ์ฌ์ด์ฆ๋ฅผ ์ ํด์ฃผ์ด์ผํ๋ค. ๋ณด๋ด๋ ๋ฐ์ดํฐ๊ฐ ์ต๋ 2045๊ฐ float ๋ฐ์ดํฐ์ด๋ฏ๋ก 4byte์ฉ ๊ณฑํ๊ณ ์ฌ์ ๋ฅผ ์ก์ 8192๋ก ์ค์ ํ์๋ค. ๋ด ๊ฒฝ์ฐ์๋ ๋ฐ์ดํฐ์ ์์ด ๋งค๋ฒ ์ผ์ ํ์ง ์์ struct์ unpack์ ํ์ฉํด์ unpackingํ ์ ์์๋ค.
๊ทธ๋ฐ๋ฐ ์๋์ฐ ์ด์์ฒด์ ๊ฐ server์ธ ๊ฒฝ์ฐ, ์ด๋ ๊ฒ ํ์ ๋ ๋ฒํผ ์ฌ์ด์ฆ ์ค๋ฒ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๋ฆฌ๋ ์ค-๋ฆฌ๋ ์ค์์๋ ๋ฌธ์ ๊ฐ ์์๋๋ฐ, ์ด์ ๋ ์ ๋ชจ๋ฅด๊ฒ ๋ค. ์ด์ ๋ฅผ ์์๋ ๋ถ์ ๋๊ธ๋ก.......
๊ทธ๋ฆฌ๊ณ array ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ float ๋จ์๋ก ์๋ผ์ฃผ๊ณ , byte๋ฅผ ๋ค์ ๋ณํํ๋ฉด ๋๋ค.
3. Plotting
๋ฌดํ๋ฃจํ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ค๋ณด๋ฉด ํ๋ก์ธ์ค๊ฐ ๋ฐ์ดํฐ๋ฅผ ์์ฐฝ ์ฒ๋ฆฌํ๋๋ผ ๋ฐ๋น ์ ๊ทธ๋ํ๊น์ง ๊ทธ๋ฆด ์ฌ์ ๊ฐ ์๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด multi-thread๋ฅผ ํ์ฉํ๋ค. ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ๊ฐฑ์ ํ๋ ์ชฝ์ thread๋ก ๋ถ๊ธฐ์ํค๊ณ , ๋ฉ์ธ thread์์ ๊ทธ๋ํ๋ฅผ ๊ทธ๋ฆฐ๋ค.
3.1 thread ์์ฑํ๊ธฐ
import socket, array
from threading import Thread
def run() -> None:
print("Connected", flush=True)
while True:
data = serverSocket.recv(8192)
signals = array.array('f')
signals.frombytes(data)
entire_data.extend(signals)
if __name__ == "__main__":
# define socket setting and bind
server = ('YOUR_IP_ADDRESS', 2001)
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverSocket.bind(server)
entire_data = [0]
# start UDP in another thread
t = Thread(target=run, args=())
t.start()
๋จผ์ thread๋ฅผ ๋ง๋ค์๋ค. entire_data
์ ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ ์ง์์ ์ผ๋ก ์ ์ฅํ๋๋ก ํด์ฃผ์๋ค.
3.2 real time plotting - matplotlib.animation
plotting์ matplotlib์ animation์ ํ์ฉํด์ ๊ทธ๋ฆฌ๊ธฐ๋ก ํ์๋ค.
https://matplotlib.org/stable/api/animation_api.html
import matplotlib.pyplot as plt
import matplotlib.animation as animation
class RealTimeAnimation:
"""
Animate original voltage signal graph and fft result.
"""
def __init__(self):
# define plot
self.fig = plt.figure(figsize=(16, 6))
self.original = self.fig.add_subplot(1, 1, 1)
# connect animation method
self.animation = animation.FuncAnimation(self.fig, self.update, interval=100)
def update(self, i) -> None:
"""
Run every interval and update graphs.
Using lock.
Args:
i : interval
"""
# update original graph
if len(entire_data) > 110000:
graph_data = entire_data[len(entire_data) - 100000:]
else:
graph_data = entire_data
self.original.clear()
self.original.plot(graph_data)
ํด๋์ค๋ฅผ ์์ฑํด์ animation์ ์ ์ํด์ฃผ์๋ค. FuncAnimation
์๋ plot์ figure, ๊ทธ๋ฆฌ๊ณ update function, interval์ด ์ฃผ์ด์ ธ์ผํ๋ค. ์ฐจ๋ก๋ก animation์ ๊ทธ๋ฆด ๊ณต๊ฐ, interval๋ง๋ค ์คํ๋ ํจ์์ด๋ค.
update
ํจ์๋ฅผ ๋ณด๋ฉด animation์ ์ ์ฒด ๋ค ๊ทธ๋ฆฌ๋ ๊ฒ ์๋๋ผ 100000๊ฐ๋ง ๊ทธ๋ฆฌ๋๋ก ์๋ผ์ฃผ์๋ค. ์ด๋น 2๋ง์ฌ ๊ฐ์ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์ค๊ธฐ ๋๋ฌธ์ ์ฝ 5์ด ๋จ์๋ก ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด๋ค. ๋๋ฌด ์์ฃผ ๋ถ๋ ค์ ์๋๋ฅผ ๋๋ฆฌ๊ฒ ํ ๊ฒ์ ์ผ๋ คํ์ฌ 11๋ง๊ฐ๊ฐ ๋์ผ๋ฉด ์๋ฅด๋ ๊ฒ์ผ๋ก ํด์ฃผ์๋ค.
์ด ์ ๋ ๋๋ฉด ์ด์ ์ฌ์ฌ ๋ญ๊ฐ ํ๋ ๋น ์ง ๊ธฐ๋ถ์ด ๋ ๋ค. ๋ฐ๋ก entire_data
์ ์ ๊ทผ ๋ฌธ์ ์ด๋ค. entire_data
๋ ์์ผ ํต์ ์ ํ๋ thread์์ ์ง์์ ์ผ๋ก ์
๋ฐ์ดํธ๋ฅผ ํด์ฃผ๊ณ ์๋๋ฐ, ๋์์ animation์์ ์ฝ์ด๋ค์ฌ์ ์๋ฅด๊ฑฐ๋ ๋ณต์ฌํ๋ค. ๋ thread ๋ชจ๋ ์ค์๊ฐ์ผ๋ก ๋น ๋ฅด๊ฒ ์คํ๋๊ธฐ ๋๋ฌธ์, ๋์์ ์ ๊ทผํ๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด lock์ ๊ฑธ์ด์ค ๊ฒ์ด๋ค. ๊ณง์ฅ ๊ฑธ๊ณ , ์ ์ฒด ์ฝ๋๋ฅผ ๋ณด์.
4. ์์ฑ
import socket, array
from threading import Lock, Thread
import matplotlib.pyplot as plt
import matplotlib.animation as animation
class RealTimeAnimation:
"""
Animate original voltage signal graph and fft result.
"""
def __init__(self):
# define plot
self.fig = plt.figure(figsize=(16, 6))
self.original = self.fig.add_subplot(1, 1, 1)
# connect animation method
self.animation = animation.FuncAnimation(self.fig, self.update, interval=100)
def update(self, i) -> None:
"""
Run every interval and update graphs.
Using lock.
Args:
i : interval
"""
# update original graph
data_lock.acquire()
if len(entire_data) > 110000:
graph_data = entire_data[len(entire_data) - 100000:]
else:
graph_data = entire_data
data_lock.release()
self.original.clear()
self.original.plot(graph_data)
def run() -> None:
"""
UDP data receive function.
Using lock.
"""
print("Connected", flush=True)
while True:
data = serverSocket.recv(16384)
signals = array.array('f')
signals.frombytes(data)
data_lock.acquire()
entire_data.extend(signals)
data_lock.release()
if __name__ == "__main__":
# define socket setting and bind
server = ('10.0.0.119', 2001)
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverSocket.bind(server)
# set data / data lock
entire_data = [0]
data_lock = Lock()
# start UDP in another thread
t = Thread(target=run, args=())
t.start()
# start animation
animation = RealTimeAnimation()
plt.show()
y์ ๋ํด auto scaling์ด ๋๊ณ , 10๋ง ๊ฐ ์ฉ ๋ฐ์ดํฐ๋ฅผ plotting ํด์ฃผ๋ udp ๊ธฐ๋ฐ ํ๋ก๊ทธ๋จ์ด ์์ฑ๋์๋ค.
์ฐธ๊ณ ๋ก keyboard interrupt ๋ฑ์ ํตํด์๋ ์ด ํ๋ก๊ทธ๋จ์ ์ข ๋ฃํ ์ ์๋ค(์ฐฝ์ ๊บผ์ง๋ค) ์๋ํ๋ฉด thread ๋ค์ชฝ์์ ์์ผ ํต์ ์ด ๋ฌดํ๋ฃจํ๋ฅผ ๋๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค(...) ์ง์ ์คํ ์ค์ธ ์ปค๋์ ์ญ์ ํด์ ์ข ๋ฃํด์ฃผ์ด์ผํ๋ค. ์ข ๋ ์ ์ฐํ ๊ธฐ๋ฒ์ผ๋ก thread๋ฅผ join ์ํค๋ ๋ฐฉ๋ฒ์... ์ฐพ์๋ณด๋๋ก ํ๊ฒ ๋ค.
๋ค์ ํฌ์คํ ์์๋ ์ค์๊ฐ fft, ๊ทธ๋ฆฌ๊ณ ์ ์ง/์ด์ด์ ์คํ, ๋ฐ์ดํฐ ์ ์ฅ, ์ ์ฅํ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก fft, stft๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ถ๊ฐ ํ๋ก๊ทธ๋จ์ ์์ฑํด๋ณด๋๋ก ํ๊ฒ ๋ค.